Files
sxchnge/docs/local-development.md
T

184 lines
9.3 KiB
Markdown

# Local development
Task [#42](https://git.sendico.io/tech/sxchnge/issues/42),
[PR #43](https://git.sendico.io/tech/sxchnge/pulls/43), branch `dev-env-42`.
## Build and bootstrap
Requires Docker with Compose v2 and BuildKit, Python 3.10+ and Git. Docker builds
Go, protobuf bindings and Flutter web; host Go, protoc and Flutter installations
are not required for `make build && make up`. The first Flutter SDK download can
take several minutes. Both ARM64 and AMD64 build from the same source definitions.
```sh
make build && make up
```
`make build` resolves the current stable upstream releases, pulls infrastructure
and builds NATS packaging, notifications, BFF and frontend images. Vault and Mongo
use upstream images directly. Resolution excludes prereleases and selects Nginx's
stable channel. The exact versions and manifest digests are saved in the ignored
`.local/dev/images.env`; Flutter's stable release is pinned by Git commit. Build
fails if upstream metadata cannot be resolved. Run `make build` again to refresh
versions. Review database release upgrade requirements before updating a populated
local volume across major versions; the tooling does not perform data migrations.
`make up` starts persistent Vault, initializes/unseals it, provisions secrets and
AppRoles, initializes an authenticated single-member MongoDB replica set, configures
Vault's MongoDB database plugin, and starts authenticated NATS JetStream. It then
starts notifications, BFF and the frontend when the required email settings exist.
**On the first run, `make up` intentionally exits with an error after infrastructure
bootstrap because Resend credentials have not been supplied.** No placeholder API
key, mock delivery mode or embedded credential is installed. The three infrastructure
services remain running so you can configure Vault, then rerun `make up`.
## Configure Resend in Vault
The default email driver is `resend`. In the Vault UI at
[http://127.0.0.1:18200](http://127.0.0.1:18200), edit the KV v2 secret **`kv/email`**
(API path `kv/data/email`):
| Field | Value |
| --- | --- |
| `driver` | `resend` |
| `api_key` | Your Resend API key |
| `sender_address` | Your verified Resend sender email |
| `sender_name` | `SXCHNGE` or your desired display name |
Local administrative login material is in the private, ignored
`.local/dev/admin/init.json` (`root_token`). Keep that file private. Product
containers never mount it. You can also provision the same secret with a hidden
terminal prompt, without placing the API key in shell history:
```sh
python3 dev/local.py email
make up
```
The startup check rejects an empty key or the bootstrap sender placeholder. It
does not validate the key against Resend or prove domain verification; an invalid
key fails at delivery. Vault Agent fetches the key and email settings and injects
them into the notifications process. There is no `.env` secret fallback. Changing
`kv/email` restarts the notification process automatically within about 10 seconds.
Bootstrapping again preserves your existing email secret.
## Open the product
After providing Resend credentials:
- Frontend and proxied API: [https://localhost:5126](https://localhost:5126).
- BFF health: [https://localhost:5126/api/v1/health](https://localhost:5126/api/v1/health).
- Vault UI/API: [http://127.0.0.1:18200](http://127.0.0.1:18200).
Trust `.local/dev/ca.pem` in your local browser/OS certificate store to use HTTPS
without a certificate error. Trust installation is a separate local action, not
performed by the bootstrap. For a command-line check without changing trust:
```sh
curl --cacert .local/dev/ca.pem https://localhost:5126/api/v1/health
```
HTTPS keeps the existing Secure/HttpOnly refresh cookie contract. Nginx serves
Flutter and proxies `/api/` to the BFF on the same origin. The bundle selects the
existing backend auth adapter with the public client ID `sxchnge-local-web`.
Bootstrap inserts this public browser client if absent; it has no client secret.
No frontend widget, service or provider architecture changes are needed.
The rest of the app retains its existing capabilities and limitations: wallet,
KYC and settings fixtures are not turned into production integrations by this
container setup. Full email signup/login requires a working Resend account.
## Secret and connection lifecycle
| Consumer | Vault access |
| --- | --- |
| BFF | `database/creds/bff`, JWT key, notification caller token and CA |
| Notifications | `database/creds/notifications`, NATS token, caller token, TLS certificate/key and email settings |
| NATS | NATS authentication token only |
| Bootstrap | Infrastructure administration, PKI and initial identities |
The database engine is configured at `database/config/mongo` with
`mongodb-database-plugin`. It uses its own `vault-manager` Mongo user, whose
password is rotated by Vault immediately after configuration. The manager has
`userAdminAnyDatabase`; applications never receive that credential. The independent
local Mongo administrator and replica-set key come from Vault's random generator
and are retained in `kv/bootstrap/mongo` for local provisioning.
The BFF role has `readWrite` only on `sxchnge`; notifications has `readWrite` only
on `sxchnge_notifications`. Mongo URIs come from dynamic Vault credentials, not
static application users. Leases default to one hour with a 24-hour maximum.
Vault Agent renews leases, obtains replacement credentials, and restarts its child
process when credentials change. Apps retain their existing shared database and
messaging connection wrappers. No new direct application database path is added.
Each agent authenticates using its own AppRole with a renewable periodic token.
Local SecretIDs are reusable for container restarts; each container mounts only
its own identity directory under `.local/dev/agents/`. Revoking an AppRole SecretID
requires removing its matching local `secret-id` file and rerunning `make up` to
issue a replacement. Revoking an active token/lease terminates its credentials;
Agent's retries and the container restart policy recover when Vault is reachable.
The notification gRPC connection uses a Vault-issued certificate for
`notifications` and the BFF validates the Vault CA and hostname. Frontend HTTPS
certificates also come from Vault PKI. Leaf certificates last 30 days; `make up`
refreshes them when less than seven days remain and reloads Nginx. Run it regularly
for a development stack kept online for weeks. Agents restart when their TLS KV
secret changes. Application secrets are never put into the frontend bundle.
Vault, Mongo and JetStream data use project-scoped named Docker volumes. Local
Vault unseal material, administrator login, per-service bootstrap identities and
frontend TLS material are under ignored `.local/dev/`, outside image build contexts.
The local Vault automatically unseals using its local key on container restart.
These convenience credentials and loopback HTTP Vault access are for this local
machine; this is not a production Vault deployment. Do not share `.local/dev/`.
Keep it paired with its matching Docker volumes for recovery.
Mongo and NATS expose no host ports. The `sxchnge-local` network, volumes and
container names are independent of existing Sendico stacks. Only the frontend
and Vault bind host loopback ports. No Sendico database, message bus or secrets
are consumed.
## Manage and verify
```sh
make ps
make logs
make down # preserves named volumes and local bootstrap material
make up # idempotent provisioning and readiness checks
make test-dev-infra # works before Resend provisioning
make test-dev # requires all six services running
```
The infrastructure check exercises AppRole access restrictions, real Vault
MongoDB credential issuance, per-database access, lease renewal and user deletion
on revocation. The complete check also verifies service health and frontend/API
HTTPS with the generated CA. These checks never send a real email.
Backend regression tests use fresh `mongo:latest` and `nats:latest` official
stable images, pulled before `make test-backend`. Normal validation remains
`make test` and `make lint-backend` and requires the host SDKs described in the
repository guides.
Deleting Docker volumes removes local data; `make down` deliberately keeps them.
If starting from scratch, stop the stack and explicitly remove this project's
three volumes **and** its matching `.local/dev/` bootstrap state together. Never
reuse old unseal material with a newly initialized Vault volume, or retain Mongo
users while discarding the Vault data that manages their leases.
## Sources and checked releases
On 2026-09-12 the resolver selected Vault 2.1.0, MongoDB 8.3.9, NATS 2.14.6,
Go 1.27.1, Nginx stable 1.30.4, Ubuntu 26.04 and Flutter stable 3.47.4. These are
verification records, not fixed future defaults. Release selection follows the
[Vault releases](https://releases.hashicorp.com/vault/),
[MongoDB stable release notes](https://www.mongodb.com/docs/manual/release-notes/),
[NATS releases](https://github.com/nats-io/nats-server/releases/latest),
[Nginx downloads](https://nginx.org/en/download.html),
[Go releases](https://go.dev/dl/) and
[Flutter stable channel](https://github.com/flutter/flutter/tree/stable).
The implementation follows HashiCorp's
[MongoDB database plugin](https://developer.hashicorp.com/vault/docs/secrets/databases/mongodb)
and [Agent process supervisor](https://developer.hashicorp.com/vault/docs/agent-and-proxy/agent/process-supervisor)
contracts.