From a3119d3f907168e3da07fecb546d1b45706fb960 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Sat, 23 May 2026 14:55:19 -0600 Subject: [PATCH] Doc updates --- docs/guides/backend-linear-tickets.md | 35 +++-- docs/guides/ops-backend-deploy.md | 176 ++++++++++++++++++++------ docs/relaunch-brief.md | 7 +- 3 files changed, 165 insertions(+), 53 deletions(-) diff --git a/docs/guides/backend-linear-tickets.md b/docs/guides/backend-linear-tickets.md index 3147966..791fdf0 100644 --- a/docs/guides/backend-linear-tickets.md +++ b/docs/guides/backend-linear-tickets.md @@ -668,9 +668,9 @@ All six are titled `[Backend] …`, assigned to Vinod, in the **community-rule** | # | Linear | Title | Depends on | | - | ------ | ----- | ---------- | -| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `[Backend] Bridge CLOUDRON_* env vars to canonical names` | none — can ship now | -| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | `[Backend] Container image registry: choose, build, push` | registry decision (handoff §5) | -| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` | CR-96 + CR-97 + Cloudron CLI access + staging DNS | +| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `[Backend] Cloudron-native env vars` | **Done** — merged | +| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | `[Backend] Container image registry: choose, build, push` | **Done** — first image `0.1.0` verified | +| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` | Cloudron CLI token (§2) — **next** | | 4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | `[Backend] Cloudron production install + apex cutover` | CR-98 green for the agreed overlap window | | 5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | `[Backend] Steady-state operator runbook` | CR-98 (write what we actually did) | | 6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | `[Backend] Decommission legacy CommunityRule LAMP app` | CR-99 + sign-off window | @@ -682,21 +682,28 @@ All six are titled `[Backend] …`, assigned to Vinod, in the **community-rule** | Order | Linear | Repo PR / branch | Kind | Status | Blocked by | | ----- | ------ | ---------------- | ---- | ------ | ---------- | -| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `adilallo/Backend/BridgeCloudronEnv` — *[Backend] Cloudron-native environment variables* | repo | **Open** | — | -| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | TBD — registry choice + build/push (Dockerfile / CI) | repo | **Next** | CR-96 merged + registry decision ([ops-backend-deploy.md](ops-backend-deploy.md) §6) | +| 1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | `adilallo/Backend/BridgeCloudronEnv` — *[Backend] Cloudron-native environment variables* | repo | **Done** | — | +| 2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container registry packaging + `docker-release.sh` | repo | **Done** | — | | — | [CR-102](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export) | TBD — optional repo PR if export tooling/docs needed | product / repo | **Parallel** | row count from legacy MySQL (pre–CR-99 backup) | -| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | — (ops checklist; doc tweaks only if smoke finds gaps) | ops | Backlog | CR-96 + CR-97 + Cloudron CLI token + staging DNS | +| 3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | — (ops checklist; [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98)) | ops | **Next** | Cloudron CLI token only | | 4 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | TBD — `docs/guides/ops-runbook.md` | docs | Backlog | CR-98 (write what we actually did) | | 5 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | — (ops; maintenance window) | ops | Backlog | CR-98 green + CR-102 resolved | | 6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | — (ops; uninstall LAMP slot) | ops | Backlog | CR-99 + sign-off window | -**What's next:** merge **CR-96** PR, then open **CR-97** on its own branch. Start **CR-102** product decision in parallel so it is resolved before the CR-99 cutover window. +**What's next:** **CR-98** — staging install + smoke at `staging.communityrule.info` +([ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98)). +Start **CR-102** product decision in parallel so it is resolved before the CR-99 +cutover window. **Per-ticket detail:** -1. **Cloudron-native env vars (CR-96).** **Shipped in repo** on branch `adilallo/Backend/BridgeCloudronEnv` (PR open). App reads `CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only (no `DATABASE_URL` / `SMTP_URL` shim). Local dev uses the same names in `.env`. SMTP URL assembled in [`lib/server/env.ts`](../../lib/server/env.ts); mail senders use `getSmtpUrl()`. Acceptance: with only `CLOUDRON_*` set, app connects to DB and sends mail; unit tests in `tests/unit/env.test.ts`. -2. **Container image registry: choose, build, push.** Acceptance: `docker pull /communityrule:` works from a Cloudron-reachable network. CI builds and pushes on merge to `main` (stretch). -3. **Cloudron staging install + smoke.** Acceptance: `curl https:///api/health` returns `{"ok":true,"database":"connected"}`; magic-link request → click link → `GET /api/auth/session` returns a user; publishing a rule succeeds. +1. **Cloudron-native env vars (CR-96).** **Done.** App reads `CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only (no `DATABASE_URL` / `SMTP_URL` shim). Prisma datasource uses `CLOUDRON_POSTGRESQL_URL`. Local dev uses the same names in `.env`. SMTP URL assembled in [`lib/server/env.ts`](../../lib/server/env.ts); mail senders use `getSmtpUrl()`. `scripts/start.sh` does not bridge env names. +2. **Container image registry (CR-97).** **Done.** Gitea registry on `git.medlab.host`; repo `CommunityRule/community-rule` is **public** (package visibility inherits from repo). Image `git.medlab.host/communityrule/community-rule:0.1.0`, built `linux/amd64` via `./scripts/docker-release.sh`. Anonymous pull verified. CI on merge to `main` deferred (no hosted runners). +3. **Cloudron staging install + smoke (CR-98).** **Next.** Full checklist in [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98). Summary: + - **Prereqs:** Cloudron CLI token (only outstanding item); CR-96 + CR-97 done. + - **Install:** `cloudron install --location staging.communityrule.info` from repo root (manifest supplies `dockerimage`; migrations run in `start.sh`). + - **Configure:** `SESSION_SECRET`, `SMTP_FROM`, `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true`, `UPLOAD_ROOT=/app/data/uploads`. + - **Acceptance:** `GET /api/health` → `{"ok":true,"database":"connected"}`; magic-link sign-in end-to-end; publish a rule succeeds. 4. **Cloudron production install + DNS cutover.** Acceptance: production subdomain resolves to the new app; old subdomain still works during overlap; sign-in + publish succeed against production; backups confirmed. 5. **Steady-state operator runbook.** Lives at `docs/guides/ops-runbook.md` (sibling to the handoff). Covers deploy a new version, rollback, restore drill cadence, multi-instance limitations from [`backend-roadmap.md`](backend-roadmap.md) §5/§7. Acceptance: a fresh reader can deploy + roll back using only this doc. 6. **Decommission legacy Express/MySQL backend.** Acceptance: old Cloudron app stopped + uninstalled; old MySQL addon backed up once and removed; legacy Gitea repo README updated to point at this app. Priority: Low. @@ -822,7 +829,7 @@ Tickets **10–11** can be deferred without blocking the core “auth + drafts + ## Linear (Community-rule team) -**Main chain (historical):** **CR-72 → CR-83** was the original **strict sequence**; **repo + Linear status today:** **CR-72–CR-79**, **CR-83**, **CR-84**, **CR-85**, **CR-88**, **CR-89** are **Done**; **CR-77** (publish) **Done**; **CR-80–CR-81** remain **Backlog** (web vitals, public rule detail). **CR-82** covered by local `migrate:smoke` (see Ticket 11). **CR-83** (admin handoff) shipped as a narrow handoff sheet; the Cloudron deployment pipeline is split into **`[Backend]` follow-ups CR-96–CR-102** — see [PR plan (CR-96 – CR-102)](#pr-plan-cr-96--cr-102) under Ticket 12 (**CR-96** repo PR open; **CR-97** next). **Parallel (still open):** **CR-86** / Ticket 15 (**Backlog** — publish **not** a blocker); **CR-103** / Ticket 20 (change account email); **CR-93** (**Backlog**); **CR-90** / Ticket 18 (stakeholder invites); **CR-91** / Ticket 19 (`Add` button behavior); **Ticket 21 / [CR-113](https://linear.app/community-rule/issue/CR-113/backend-create-flow-file-uploads-community-photo-custom-method)** (create-flow file uploads). +**Main chain (historical):** **CR-72 → CR-83** was the original **strict sequence**; **repo + Linear status today:** **CR-72–CR-79**, **CR-83**, **CR-84**, **CR-85**, **CR-88**, **CR-89** are **Done**; **CR-77** (publish) **Done**; **CR-80–CR-81** remain **Backlog** (web vitals, public rule detail). **CR-82** covered by local `migrate:smoke` (see Ticket 11). **CR-83** (admin handoff) shipped as a narrow handoff sheet; the Cloudron deployment pipeline is split into **`[Backend]` follow-ups CR-96–CR-102** — see [PR plan (CR-96 – CR-102)](#pr-plan-cr-96--cr-102) under Ticket 12 (**CR-96** + **CR-97** **Done**; **CR-98** **next**). **Parallel (still open):** **CR-86** / Ticket 15 (**Backlog** — publish **not** a blocker); **CR-103** / Ticket 20 (change account email); **CR-93** (**Backlog**); **CR-90** / Ticket 18 (stakeholder invites); **CR-91** / Ticket 19 (`Add` button behavior); **Ticket 21 / [CR-113](https://linear.app/community-rule/issue/CR-113/backend-create-flow-file-uploads-community-photo-custom-method)** (create-flow file uploads). | Doc ticket | Linear | Title (short) | Deploy PR / tracking | | ---------: | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------- | @@ -838,9 +845,9 @@ Tickets **10–11** can be deferred without blocking the core “auth + drafts + | 10 | [CR-81](https://linear.app/community-rule/issue/CR-81/backend-public-rule-detail-page-get-apirulesid-optional) | Public rule detail (optional) | — | | 11 | [CR-82](https://linear.app/community-rule/issue/CR-82/backend-ci-postgres-migration-smoke-optional) | Local migrate smoke (**Done in repo**; optional remote CI) | — | | 12 | [CR-83](https://linear.app/community-rule/issue/CR-83/backend-stagingproduction-runbook-admin-handoff-docsops-backend) | Ops admin handoff (Cloudron) **Done** | — | -| 12.1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | Cloudron-native env vars | **Open** — `adilallo/Backend/BridgeCloudronEnv` | -| 12.2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container image registry + CI | **Next** — own branch after CR-96 | -| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | Cloudron staging install + smoke | Ops — after CR-96 + CR-97 | +| 12.1 | [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) | Cloudron-native env vars | **Done** | +| 12.2 | [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) | Container image registry + CI | **Done** — image `0.1.0` | +| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | Cloudron staging install + smoke | **Next** — [ops-backend-deploy.md §10](ops-backend-deploy.md#10-staging-install--smoke-cr-98) | | 12.4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | Production install + apex cutover | Ops — after CR-98 + CR-102 | | 12.5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | Steady-state operator runbook | Docs PR — after CR-98 | | 12.6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | Decommission legacy LAMP app | Ops — after CR-99 + sign-off | diff --git a/docs/guides/ops-backend-deploy.md b/docs/guides/ops-backend-deploy.md index de85fff..4025373 100644 --- a/docs/guides/ops-backend-deploy.md +++ b/docs/guides/ops-backend-deploy.md @@ -3,8 +3,10 @@ This doc captures everything needed to deploy the new CommunityRule (Next.js + Postgres) onto MEDLab's Cloudron and replace the legacy LAMP-packaged service at `communityrule.info`. Cloudron admin access -has been granted and the container registry is wired up (§6, §9); the -remaining gates are CR-96 (env bridging) and CR-98 (staging install). +has been granted, CR-96 (Cloudron-native env vars) and CR-97 (container +registry + first image push) are done; the remaining gate is +[CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) +(staging install + smoke — §10). > **For a plain-language summary to hand to MEDLab's Cloudron admin, > see [`../relaunch-brief.md`](../relaunch-brief.md).** This doc is the @@ -76,10 +78,12 @@ per-app in the manifest and provisioned at install time. with the legacy service; SES relay accepts it). - `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` — turns on Postgres draft persistence for signed-in users. Required in production. -- `UPLOAD_ROOT` — absolute path to a writable directory (typically on the - Cloudron **localstorage** mount) for `POST /api/uploads` (community photo + - custom-method attachments). When unset, upload routes return - `server_misconfigured`. See [CONTRIBUTING.md](../../CONTRIBUTING.md) API table. +- `UPLOAD_ROOT` — absolute path to a writable directory on the Cloudron + **localstorage** mount for `POST /api/uploads` (community photo + + custom-method attachments). Use **`/app/data/uploads`** on Cloudron + (`start.sh` chowns `/app/data` for the `node` user). When unset, upload + routes return `server_misconfigured`. See [CONTRIBUTING.md](../../CONTRIBUTING.md) + API table. ## 4. Platform settings @@ -89,9 +93,10 @@ per-app in the manifest and provisioned at install time. ([`app/api/health/route.ts`](../../app/api/health/route.ts) returns `200 {"ok":true,"database":"connected"}` when healthy, `503` otherwise). -- Memory limit: start at **512 MiB** (matches what the legacy LAMP - app has been running fine on for two years); raise if Next.js - standalone OOMs under load. +- Memory limit: **768 MiB** in + [`CloudronManifest.json`](../../CloudronManifest.json) (`memoryLimit: + 805306368`). The legacy LAMP app ran at 512 MiB; raise further only if + Next.js standalone OOMs under load. - Backups: Cloudron's automatic backups are already on for the host (legacy app shows weekly snapshots ~451 MB each). Same default applies to new apps. @@ -107,10 +112,14 @@ apex. ### Phases -1. **Staging install** — `cloudron install --image - --location staging.communityrule.info`. Set env vars from §3. Run - `prisma migrate deploy`. Smoke per - [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke). +1. **Staging install** — from a checkout whose + [`CloudronManifest.json`](../../CloudronManifest.json) matches the pushed + image tag, run `cloudron install --location staging.communityrule.info`. + Cloudron reads `dockerimage` from the manifest (no `--image` flag). Set + manual env vars from §3. `prisma migrate deploy` runs automatically in + [`scripts/start.sh`](../../scripts/start.sh) on container start. Smoke per + [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) + (§12). 2. **Soft launch / acceptance** — share the staging URL with a small group, exercise sign-in + publish + draft sync end-to-end. Hold here until confident. @@ -152,18 +161,24 @@ Product decisions (closed): Infra decision closed: 3. **Container registry — Gitea Container Registry on `git.medlab.host`.** - Same host as Cloudron (`193.46.198.90`); container package is set - **public** to sidestep the [same-host docker-login "socket hangup" + Same host as Cloudron (`193.46.198.90`). The + [`CommunityRule/community-rule`](https://git.medlab.host/CommunityRule/community-rule) + repo must be **public** so the container package inherits public visibility + (Gitea does not expose per-package visibility toggles — visibility follows + the owning repo). Public pull sidesteps the [same-host docker-login + "socket hangup" bug](https://forum.cloudron.io/topic/14572/private-docker-registry-in-cloudron), - so Cloudron pulls without credentials. Push auth from operator - laptops uses a Gitea personal access token (`read:package` + - `write:package`). Canonical image ref: - `git.medlab.host/communityrule/community-rule:`. Operator - build/push workflow lives in [§9](#9-build-and-push-image-workflow). - Tracked in [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push). - Fallback if same-host pull ever breaks: install the **Cloudron - Container Registry** app and re-tag against its hostname; no other - changes required. + so Cloudron pulls without credentials. Push auth from operator laptops uses + a Gitea personal access token (`read:package` + `write:package`). Canonical + image ref: `git.medlab.host/communityrule/community-rule:`. Images are + built **`linux/amd64` only** (Cloudron host is x86_64). Operator build/push + workflow lives in [§9](#9-build-and-push-image-workflow). First verified + image: `…:0.1.0` (digest + `sha256:e652f9f4bfa4154412cc9d8b63d55c94a128e8935579d101b5ab8977e2080e52`). + Tracked in [CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) + (**Done**). Fallback if same-host pull ever breaks: install the **Cloudron + Container Registry** app and re-tag against its hostname; no other changes + required. ## 7. Old vs new deltas @@ -199,17 +214,16 @@ All filed in Linear, titled `[Backend] …`, assigned to me, in the **Community-rule** team, **Backlog** state. 1. [**CR-96**](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names) - — `[Backend] Cloudron-native env vars` (shipped: app reads + — `[Backend] Cloudron-native env vars` (**Done** — app reads `CLOUDRON_POSTGRESQL_URL` and `CLOUDRON_MAIL_SMTP_*` only). 2. [**CR-97**](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push) - — `[Backend] Container image registry: choose, build, push`. - Registry decided (§6.3); packaging + build/push workflow shipped - (§9). Closes after the first verified `docker pull` of the pushed - image (no Cloudron-side install required to close this ticket; - that's CR-98). + — `[Backend] Container image registry: choose, build, push` (**Done**). + Registry decided (§6.3); packaging + build/push workflow shipped (§9). + First image pushed and verified via anonymous `docker pull` (§9). 3. [**CR-98**](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) — `[Backend] Cloudron staging install + smoke` at - `staging.communityrule.info`. Blocked by CR-96 + CR-97. + `staging.communityrule.info`. **Next** — checklist in §10. Requires + Cloudron CLI token (§2) only; CR-96 and CR-97 are done. 4. [**CR-99**](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) — `[Backend] Cloudron production install + apex cutover`. Side-by-side cutover at scheduled low-traffic window per §5. @@ -273,16 +287,24 @@ standalone server. Override the tag with `TAG=v0.1.1 ./scripts/docker-release.sh` for semver releases. The script prints the exact `dockerimage` line to paste back into the manifest. -3. **First push only:** in Gitea, navigate to the `CommunityRule` org - → Packages → `community-rule` → Settings → set **Visibility: Public**. +3. **First push only:** confirm the + [`CommunityRule/community-rule`](https://git.medlab.host/CommunityRule/community-rule) + repo is **Public** (Settings → General). Gitea inherits container-package + visibility from the repo — there is no per-package visibility toggle. Org + owners are not required if you have repo-admin rights on this repo. 4. **Verify the pull works without credentials** (simulates Cloudron's anonymous pull): ```bash docker logout git.medlab.host - docker pull git.medlab.host/communityrule/community-rule: + # Image is linux/amd64 only. On Apple Silicon, add --platform: + docker pull --platform linux/amd64 git.medlab.host/communityrule/community-rule: ``` + A bare `docker pull` on arm64 Macs fails with "no matching manifest for + linux/arm64" — that is expected and does **not** indicate an auth problem. + Cloudron (x86_64) pulls the amd64 manifest without `--platform`. + 5. **Commit the manifest change** alongside any code changes that shipped in this build, so the manifest and image stay in lockstep. @@ -311,13 +333,95 @@ today, and the manual workflow above is acceptable for v1 staging and production. Revisit when runners return or when release cadence justifies the runner cost. -## 10. Rate limiting (single-instance deploys) +## 10. Staging install + smoke (CR-98) + +**Goal:** Install the pushed image at `staging.communityrule.info`, configure +production env vars, and verify the vertical slice before apex cutover +([CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)). + +**Prerequisites (all satisfied unless noted):** + +- [x] **CR-96** — app reads `CLOUDRON_POSTGRESQL_URL` and + `CLOUDRON_MAIL_SMTP_*` only ([`lib/server/env.ts`](../../lib/server/env.ts), + [`prisma/schema.prisma`](../../prisma/schema.prisma)). No `DATABASE_URL` / + `SMTP_URL` shim. +- [x] **CR-97** — image pushed to + `git.medlab.host/communityrule/community-rule:0.1.0` (or current tag in + manifest); repo is **public**; anonymous amd64 pull verified (§9). +- [ ] **Cloudron CLI token** — generate at *Profile → API Tokens* on + `cloud.medlab.host`; save in 1Password (§2). +- [x] **Cloudron admin login** on `cloud.medlab.host` (§2). +- [x] **DNS** — `communityrule.info` managed in Cloudron; staging subdomain + will be provisioned at install time. + +**Install steps:** + +1. **Checkout** a commit whose [`CloudronManifest.json`](../../CloudronManifest.json) + `version`, `dockerimage`, and `memoryLimit` match the image you intend to + run (currently `0.1.0` → + `git.medlab.host/communityrule/community-rule:0.1.0`). +2. **Log in to Cloudron CLI:** + ```bash + cloudron login cloud.medlab.host + ``` +3. **Install** from the repo root (manifest is read automatically): + ```bash + cloudron install --location staging.communityrule.info + ``` + Cloudron provisions **postgresql**, **sendmail**, and **localstorage** + addons from the manifest, pulls the image (no registry credentials needed), + and starts the container. `scripts/start.sh` chowns `/app/data`, runs + `prisma migrate deploy`, then execs the Next.js server. +4. **Set manual env vars** (Cloudron does not inject these): + ```bash + cloudron configure --app --set-env \ + SESSION_SECRET="$(openssl rand -hex 32)" \ + SMTP_FROM="Community Rule " \ + NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true \ + UPLOAD_ROOT=/app/data/uploads + ``` + Rotating `SESSION_SECRET` logs everyone out. `SMTP_FROM` must be an address + SES accepts on `communityrule.info` (platform mail addon is SES-relayed with + custom-from allowed — §3). +5. **Confirm the app is running** in the Cloudron dashboard (Logs tab). Look + for a clean `prisma migrate deploy` and Next.js listening on port 3000. + +**Smoke checklist (acceptance):** + +- [ ] **Health:** `curl -sS https://staging.communityrule.info/api/health` + returns `200` with `{"ok":true,"database":"connected"}`. +- [ ] **Magic link:** request sign-in from the UI → email arrives at a real + inbox → click link → land signed in → + `GET /api/auth/session` returns a user. Confirm the link host matches + `staging.communityrule.info` (reverse proxy / `Host` alignment). +- [ ] **Publish:** complete create flow → publish a rule → public rule detail + loads. +- [ ] **Draft sync (optional):** signed-in Save & Exit persists to Postgres; + resume works after re-login. +- [ ] **Upload (optional):** with `UPLOAD_ROOT` set, attach a community photo + in create flow and confirm it renders after publish. + +**If something fails:** + +| Symptom | Likely cause | Check | +| ------- | ------------ | ----- | +| Image pull error on install | Repo still private, or wrong tag in manifest | §6.3; `docker pull --platform linux/amd64 …` from laptop | +| Health `503` / `database: disconnected` | Postgres addon not provisioned or URL missing | Cloudron app → Environment; expect `CLOUDRON_POSTGRESQL_URL` | +| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars | +| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | Set to `/app/data/uploads` (§3) | +| Container crash on start | Migration failure | App logs around `prisma migrate deploy` | + +**Done when:** all smoke checklist items pass. Then proceed to soft-launch +(§5 phase 2) and, when ready, [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) +apex cutover. + +## 11. Rate limiting (single-instance deploys) The app uses an **in-memory** rate limiter in [`lib/server/rateLimit.ts`](../../lib/server/rateLimit.ts) (magic-link requests, organizer inquiry, etc.). This is sufficient for the current **single Cloudron container** per environment. **Before horizontal scale-out** (multiple app instances behind a load balancer), replace or back the limiter with a shared store (e.g. Redis) so per-IP / per-user windows apply across instances. Until then, document expected limits in the steady-state runbook ([CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook)). -## 11. Related docs +## 12. Related docs - [`docs/guides/backend-roadmap.md`](backend-roadmap.md) §11 (environments) and §8 (Prisma migrations policy). diff --git a/docs/relaunch-brief.md b/docs/relaunch-brief.md index 5e296a9..96355ad 100644 --- a/docs/relaunch-brief.md +++ b/docs/relaunch-brief.md @@ -47,9 +47,10 @@ Rollback plan during the window: restore the legacy backup to a scratch Cloudron Roughly this order: -1. **Code prep** — small local change so the app reads Cloudron's injected `CLOUDRON_*` env vars natively. No infra impact. -2. **Build and push the app image** to a container registry. -3. **Install at staging** subdomain, smoke test, soft launch. +1. ~~**Code prep** — Cloudron-native env vars (CR-96).~~ **Done.** +2. ~~**Build and push the app image** to Gitea registry (CR-97).~~ **Done** + (`git.medlab.host/communityrule/community-rule:0.1.0`). +3. **Install at staging** subdomain, smoke test, soft launch (CR-98). 4. **Apex cutover window** — the brief downtime above. 5. **Uninstall legacy**, archive legacy repos. 6. **Write the steady-state runbook** based on what actually worked.