remove user-secrets approach & prepare a pilot deploy
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# Deploying Balinyaar
|
||||
|
||||
A first, shareable deployment of the whole stack under **balinyaar.ir**, in Docker, behind an existing
|
||||
Caddy reverse proxy that terminates TLS.
|
||||
|
||||
| Host | Serves | Container |
|
||||
| --- | --- | --- |
|
||||
| `balinyaar.ir`, `www.balinyaar.ir` | Next.js web client | `balinyaar-web:3000` |
|
||||
| `api.balinyaar.ir` | ASP.NET Core API | `balinyaar-api:8080` |
|
||||
| *(internal only)* | Telegram OTP relay | `balinyaar-otp-relay:5010` |
|
||||
|
||||
The **database is not containerised** — it is the remote SQL Server already configured in
|
||||
[server/src/API/Baya.Web.Api/appsettings.Development.json](server/src/API/Baya.Web.Api/appsettings.Development.json).
|
||||
Nothing needs to be provisioned for it; the API just needs network reach to `87.107.152.16:1433`.
|
||||
|
||||
---
|
||||
|
||||
## Configuration model
|
||||
|
||||
**There is no `dotnet user-secrets` any more.** The `<UserSecretsId>` was removed from
|
||||
`Baya.Web.Api.csproj`, so the API no longer reads that store at all — a stale `secrets.json` on a dev
|
||||
machine is now inert and can be deleted. Every value lives in a file in the repo:
|
||||
|
||||
| What | Where |
|
||||
| --- | --- |
|
||||
| API config + secrets (DB, JWE keys, field-encryption keys, Telegram key, CORS, trusted proxies) | `server/src/API/Baya.Web.Api/appsettings.Development.json` |
|
||||
| The two values that differ between a laptop and the container network | `docker-compose.yml` → `api.environment` |
|
||||
| Client build-time config (API URL, site origin) | `client/.env.production` |
|
||||
| Telegram relay config (bot token, chat ids, API key, proxy) | `docker-compose.yml` → `otp-relay.environment` |
|
||||
|
||||
The API runs as **`ASPNETCORE_ENVIRONMENT=Development`**, so `appsettings.Development.json` is the file
|
||||
that actually loads. An `appsettings.Production.json` would be ignored — put changes in the Development
|
||||
file, or change the environment name first.
|
||||
|
||||
The relay's shared secret appears twice and the two must match: `Seams:Sms:Telegram:ApiKey` in the
|
||||
appsettings file and `API_KEY` in the compose file. It was rotated away from the value in
|
||||
`telegram-otp-bot/.env.example`, which is published in git and in that project's README —
|
||||
`TelegramSmsSender` now refuses to authenticate with it. **If you run the relay locally**, copy the
|
||||
appsettings value into your own `telegram-otp-bot/.env`.
|
||||
|
||||
> ⚠️ **`Seams:FieldEncryption:Key` and `:HashKey` must never change.** Every encrypted column in that
|
||||
> database — phone numbers, addresses, IBANs, clinical notes — was written with those exact values, and
|
||||
> `users.PhoneHash`, which every login looks up, is derived from `HashKey`. Rotating either makes the
|
||||
> existing data unreadable and locks every account out. The JWE keys (`IdentitySettings:SecretKey` /
|
||||
> `Encryptkey`) are safe to rotate; doing so only signs everyone out.
|
||||
|
||||
---
|
||||
|
||||
## What running as Development means
|
||||
|
||||
This was a deliberate choice so the demo and lifecycle seeders populate the shared database and the
|
||||
screens aren't empty. It has real consequences, all of which are fine for a pre-launch demo among
|
||||
people you trust, and none of which are acceptable once strangers can reach the site:
|
||||
|
||||
- **The developer exception page is public.** Any unhandled 500 on `api.balinyaar.ir` returns a stack
|
||||
trace and configuration detail to the caller.
|
||||
- **`GET /api/v1/dev/last_otp/{phone}` is live.** Anyone who knows a registered phone number can read
|
||||
its login code and sign in as that user. This is the single biggest exposure.
|
||||
- **Swagger is served** at `api.balinyaar.ir/swagger`.
|
||||
- **The seeders re-run on every container boot** (idempotent, so this is safe — they no-op on data that
|
||||
already exists) and **migrations auto-apply on boot** rather than as a separate step.
|
||||
- **gRPC reflection is enabled**, and the demo `bookings/convert` payment-capture simulator is wired.
|
||||
|
||||
### Going to Production later
|
||||
|
||||
1. Set `ASPNETCORE_ENVIRONMENT: Production` in `docker-compose.yml`.
|
||||
2. Create `appsettings.Production.json` with the same content as the Development file, but with **real**
|
||||
`IdentitySettings:SecretKey` / `Encryptkey` — `StartupSecretsGuard` rejects anything containing
|
||||
`not-for-production` outside Development, so the current dev keys will refuse to boot (by design).
|
||||
Keep `Seams:FieldEncryption` byte-identical.
|
||||
3. Run migrations as a one-shot instead of on boot:
|
||||
`docker compose run --rm api dotnet Baya.Web.Api.dll migrate`
|
||||
4. Swap the OTP rail: `Seams:Sms:Provider` → `kavenegar`, with `Seams:Sms:ApiKey`/`Sender` filled in.
|
||||
The Telegram relay broadcasts every code to a fixed recipient list, which stops being acceptable the
|
||||
moment someone outside that list can request one.
|
||||
|
||||
---
|
||||
|
||||
## First deploy
|
||||
|
||||
### 1. Confirm the Caddy network exists
|
||||
|
||||
The compose file joins `caddy_net` as an **external** network — it does not create it.
|
||||
|
||||
```bash
|
||||
docker network ls | grep caddy_net
|
||||
```
|
||||
|
||||
### 2. Add the Balinyaar block to your Caddyfile
|
||||
|
||||
Copy from [deploy/Caddyfile](deploy/Caddyfile) into the Caddyfile your Caddy container already loads:
|
||||
|
||||
```caddyfile
|
||||
balinyaar.ir, www.balinyaar.ir {
|
||||
encode zstd gzip
|
||||
reverse_proxy balinyaar-web:3000
|
||||
}
|
||||
|
||||
api.balinyaar.ir {
|
||||
encode zstd gzip
|
||||
reverse_proxy balinyaar-api:8080
|
||||
}
|
||||
```
|
||||
|
||||
Caddy obtains and renews the certificates for both hostnames itself. Reload it:
|
||||
|
||||
```bash
|
||||
docker exec <caddy-container> caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
### 3. Point DNS at the host
|
||||
|
||||
`balinyaar.ir`, `www.balinyaar.ir` and `api.balinyaar.ir` all need an A record on the server's public IP
|
||||
**before** Caddy can complete the ACME challenge.
|
||||
|
||||
### 4. Confirm the proxy container is up
|
||||
|
||||
The relay's hop to `api.telegram.org` is filtered in Iran and goes out through the proxy already on
|
||||
`caddy_net`, configured as `TELEGRAM_PROXY_URL: http://hysteria-client:8081`. If that container has a
|
||||
different name or port, change it in `docker-compose.yml` — a wrong value fails the relay at boot with a
|
||||
clear message rather than silently per-OTP.
|
||||
|
||||
### 5. Build and start
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
docker compose logs -f api
|
||||
```
|
||||
|
||||
The API's first boot applies any pending migrations and runs the seeders against the remote database, so
|
||||
it takes noticeably longer than later ones.
|
||||
|
||||
---
|
||||
|
||||
## Verifying
|
||||
|
||||
```bash
|
||||
curl https://api.balinyaar.ir/healthz/live # process is up
|
||||
curl https://api.balinyaar.ir/healthz/ready # + database and object storage reachable
|
||||
curl -I https://balinyaar.ir # the public landing page
|
||||
docker compose logs otp-relay | head # should print the bot's @username and the proxy label
|
||||
```
|
||||
|
||||
A full login round-trip is the real check: request an OTP from the site and confirm the code arrives in
|
||||
the Telegram chat. If it doesn't, `docker compose logs otp-relay` names the failing hop — a proxy error
|
||||
and a Telegram API rejection look different.
|
||||
|
||||
---
|
||||
|
||||
## Redeploying
|
||||
|
||||
```bash
|
||||
git pull
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Rebuild the client whenever a `NEXT_PUBLIC_*` value in `client/.env.production` changes — those are
|
||||
compiled into the browser bundle, so restarting the container alone changes nothing.
|
||||
|
||||
## Persisted state
|
||||
|
||||
Two named volumes survive rebuilds. Uploaded verification documents live in the first one; losing it
|
||||
means the admin verification queue shows broken documents.
|
||||
|
||||
| Volume | Holds |
|
||||
| --- | --- |
|
||||
| `api-object-storage` | Uploaded verification documents (local-disk `IObjectStorage` seam) |
|
||||
| `api-logs` | Serilog JSON file sink |
|
||||
Reference in New Issue
Block a user