Update docs
This commit is contained in:
+2
-1
@@ -20,10 +20,11 @@ User-facing docs. Implementation conventions live in `.cursor/rules/`.
|
|||||||
|
|
||||||
These will be deleted once the backend services are stood up:
|
These will be deleted once the backend services are stood up:
|
||||||
|
|
||||||
|
- [relaunch-brief.md](./relaunch-brief.md) — short executive summary for MEDLab Cloudron admin: what the relaunch is, what's being replaced, how cutover works.
|
||||||
- [guides/backend-roadmap.md](./guides/backend-roadmap.md)
|
- [guides/backend-roadmap.md](./guides/backend-roadmap.md)
|
||||||
- [guides/backend-linear-tickets.md](./guides/backend-linear-tickets.md)
|
- [guides/backend-linear-tickets.md](./guides/backend-linear-tickets.md)
|
||||||
- [guides/template-recommendation-matrix.md](./guides/template-recommendation-matrix.md)
|
- [guides/template-recommendation-matrix.md](./guides/template-recommendation-matrix.md)
|
||||||
- [guides/ops-backend-deploy.md](./guides/ops-backend-deploy.md) — admin handoff for deploying to MEDLab's Cloudron.
|
- [guides/ops-backend-deploy.md](./guides/ops-backend-deploy.md) — technical deploy handoff + cutover plan (Cloudron, env vars, health checks, follow-up tickets).
|
||||||
|
|
||||||
## Cursor rules
|
## Cursor rules
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ A backend review was merged into **[docs/backend-roadmap.md](backend-roadmap.md)
|
|||||||
|
|
||||||
- **Done in Linear and shipped:** **CR-72–CR-76**, **CR-77** (publish from create flow), **CR-78** (template seed), **CR-79**, **CR-88**, **CR-89**. The **CR-72 → CR-83** numbering is the original **sequential plan**, not current blocking order; the **core product vertical** through publish + templates is effectively complete in-repo.
|
- **Done in Linear and shipped:** **CR-72–CR-76**, **CR-77** (publish from create flow), **CR-78** (template seed), **CR-79**, **CR-88**, **CR-89**. The **CR-72 → CR-83** numbering is the original **sequential plan**, not current blocking order; the **core product vertical** through publish + templates is effectively complete in-repo.
|
||||||
- **Backlog (still open):** **CR-80** (web vitals — file-based route remains), **CR-81** (public rule detail — no `GET /api/rules/[id]` or marketing detail page yet), **CR-82** (CI migrate smoke), **CR-86** (profile + account + draft resume — UI mostly placeholder), **CR-90** / **CR-91**, **CR-93** (template grid facets on marketing). **CR-84 Done** — canonical error contract `{ error: { code, message }, details? }` and `x-request-id` propagation shipped via `lib/server/{responses,requestId,apiRoute}.ts`; auth + drafts + rules routes migrated, remaining `app/api/*` are a follow-up pass. **CR-85 Done** — multi-device session policy + lazy expired-row cleanup (per-user prune on every sign-in plus ~5% global sweep, no cron); ADR comment block in [`lib/server/session.ts`](../../lib/server/session.ts).
|
- **Backlog (still open):** **CR-80** (web vitals — file-based route remains), **CR-81** (public rule detail — no `GET /api/rules/[id]` or marketing detail page yet), **CR-82** (CI migrate smoke), **CR-86** (profile + account + draft resume — UI mostly placeholder), **CR-90** / **CR-91**, **CR-93** (template grid facets on marketing). **CR-84 Done** — canonical error contract `{ error: { code, message }, details? }` and `x-request-id` propagation shipped via `lib/server/{responses,requestId,apiRoute}.ts`; auth + drafts + rules routes migrated, remaining `app/api/*` are a follow-up pass. **CR-85 Done** — multi-device session policy + lazy expired-row cleanup (per-user prune on every sign-in plus ~5% global sweep, no cron); ADR comment block in [`lib/server/session.ts`](../../lib/server/session.ts).
|
||||||
- **CR-83 Done (admin handoff scope):** [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md) shipped as the **admin handoff sheet** (access, env vars, platform settings, open decisions). The full deploy runbook is intentionally split out — see the new follow-up tickets in [Ticket 12 / CR-83 follow-ups](#follow-up-tickets-filed-under-cr-83) below.
|
- **CR-83 Done (admin handoff + cutover plan):** [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md) shipped. Cloudron admin access on `cloud.medlab.host` granted; doc now covers (a) what's in place, (b) the side-by-side → apex cutover plan, and (c) the two open product questions + registry decision still outstanding. Steady-state operator runbook is split out into a follow-up — see [Ticket 12 / CR-83 follow-ups](#follow-up-tickets-filed-under-cr-83) below. Key new finding: legacy `communityrule.info` is a single Cloudron **LAMP** app (`lamp.cloudronapp.php74@5.1.2`) hosting marketing site + Express/MySQL backend + a broken Flask chatbot all in one container; all three retire together via CR-99 + CR-101.
|
||||||
- **CR-86** is **no longer blocked** by publish — **CR-77** is **Done**; profile work is gated by **implementation**, not waiting on publish wiring.
|
- **CR-86** is **no longer blocked** by publish — **CR-77** is **Done**; profile work is gated by **implementation**, not waiting on publish wiring.
|
||||||
- **Not in this ticket list** but called out in **[docs/backend-roadmap.md](backend-roadmap.md):** shared **rate-limit store** (e.g. Redis) before multi-instance; **`GET /api/create-flow/methods`** exists for facet scoring (Ticket 16 / CR-88) but is not duplicated as a separate doc ticket.
|
- **Not in this ticket list** but called out in **[docs/backend-roadmap.md](backend-roadmap.md):** shared **rate-limit store** (e.g. Redis) before multi-instance; **`GET /api/create-flow/methods`** exists for facet scoring (Ticket 16 / CR-88) but is not duplicated as a separate doc ticket.
|
||||||
|
|
||||||
@@ -540,32 +540,35 @@ _Section B — Final Review screen `+` button per category:_
|
|||||||
|
|
||||||
**Depends on:** Tickets 1–8 complete enough to deploy a vertical slice.
|
**Depends on:** Tickets 1–8 complete enough to deploy a vertical slice.
|
||||||
|
|
||||||
**Server / admin:** **This is the handoff ticket.** Scope is **narrowed** vs. the original "full operator runbook" framing: the deliverable is the **admin-handoff sheet** — exactly what access, env vars, and platform decisions to ask MEDLab's Cloudron admin for. The full deploy runbook (build / push / install / migrate / smoke / rollback) is **split out into a follow-up ticket** so CR-83 isn't blocked on access we don't have yet.
|
**Server / admin:** Cloudron admin access on `cloud.medlab.host` granted. Scope of this ticket is the **handoff doc + cutover plan** — exactly what's in place, what the side-by-side cutover looks like, and what open product/infra questions remain. The steady-state operator runbook is split out into [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) (we write it after we've done the work).
|
||||||
|
|
||||||
**Goal:** Single short doc the admin can read end-to-end and respond to in one round-trip: required access, Cloudron-injected vs. manually-set env vars, platform settings to confirm (`httpPort`, `healthCheckPath`, addons, memory, backups), and the open product decisions only admin can answer (subdomains, sender address, registry choice, cutover overlap, retention).
|
**Goal:** Short doc that captures (a) granted access + auto-injected vs. manually-set env vars + platform settings, (b) the side-by-side → apex cutover plan with the legacy `communityrule.info` service, and (c) the remaining open questions (apex vs. permanent-subdomain final URL, legacy `rules` data communication, container registry choice).
|
||||||
|
|
||||||
**Platform context:** Target is **Cloudron at MEDLab** (same host as the legacy [`CommunityRule/CommunityRuleBackend`](https://git.medlab.host/CommunityRule/CommunityRuleBackend), which is Express + MySQL with a 30-min `run.sh` watchdog). New app is a properly packaged Cloudron app (Docker image + `CloudronManifest.json`), uses the **postgresql + sendmail + localstorage** addons, and replaces the legacy service entirely — **no data migration**. Cloudron's container supervisor replaces the old watchdog.
|
**Platform context:** Target is **Cloudron at MEDLab** (`cloud.medlab.host`). The legacy `communityrule.info` is a single Cloudron **LAMP** app (`lamp.cloudronapp.php74@5.1.2`, 512 MiB at apex) hosting **three things stuffed into one container** under `/app/data/public/`: the static marketing site, the Express/MySQL backend at [`CommunityRule/CommunityRuleBackend`](https://git.medlab.host/CommunityRule/CommunityRuleBackend) (kept alive by a 30-min `run.sh` watchdog on port 3000; MySQL is the LAMP package's bundled MySQL, not a Cloudron addon), and the Flask chatbot at [`CommunityRule/CommunityRuleChatBot`](https://git.medlab.host/CommunityRule/CommunityRuleChatBot) (currently crash-looping with `ModuleNotFoundError`, last touched May 2024). New app is a properly packaged Cloudron app (Docker image + `CloudronManifest.json`, **postgresql + sendmail + localstorage** addons) and replaces all three — **no data migration**. Cloudron's container supervisor replaces the watchdog.
|
||||||
|
|
||||||
**Implementation (shipped):**
|
**Implementation (shipped):**
|
||||||
|
|
||||||
1. [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md) — admin handoff sheet (~1 page):
|
1. [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md):
|
||||||
- **§2 Access checklist** (Cloudron admin login, registry creds, DNS, `cloudron` CLI, log access, read of legacy app config).
|
- **§1 Context** — what the legacy LAMP slot actually contains and why side-by-side cutover is the safe path.
|
||||||
- **§3 Env vars** split into Cloudron auto-injected (`CLOUDRON_POSTGRESQL_URL`, `CLOUDRON_MAIL_SMTP_*`) vs. manually-set (`SESSION_SECRET`, `SMTP_FROM`, `NEXT_PUBLIC_ENABLE_BACKEND_SYNC`).
|
- **§2 Access** — what Cloudron admin already grants self-serve; only outstanding admin-side step is generating a CLI token.
|
||||||
- **§4 Platform settings** (`httpPort: 3000`, `healthCheckPath: /api/health`, memory, backups, TLS).
|
- **§3 Env vars** split into Cloudron auto-injected (`CLOUDRON_POSTGRESQL_URL`, `CLOUDRON_MAIL_SMTP_*`) vs. manually-set (`SESSION_SECRET`, `SMTP_FROM`, `NEXT_PUBLIC_ENABLE_BACKEND_SYNC`). Notes that addons are manifest-declared, not platform-enabled, and that platform mail is SES-relayed on `communityrule.info` with custom-from allowed.
|
||||||
- **§5 Decisions** (subdomains, sender, registry, cutover, retention).
|
- **§4 Platform settings** (`httpPort: 3000`, `healthCheckPath: /api/health`, 512 MiB to start, automatic backups already on).
|
||||||
- **§7 Old vs new deltas** (addons, watchdog, OTP→magic link, sender, API surface — all reasons not to reuse legacy infra).
|
- **§5 Cutover plan** — staging at `staging.communityrule.info`, soft-launch, apex cutover at scheduled low-traffic window (~5–15 min downtime).
|
||||||
|
- **§6 Open questions** — apex vs. permanent subdomain final URL; legacy `rules` data communication; container registry choice.
|
||||||
|
- **§7 Old vs new deltas** (LAMP-package detail, watchdog, OTP→magic link, sender, API surface, chatbot).
|
||||||
- **§8 Follow-up tickets** (the six tickets below).
|
- **§8 Follow-up tickets** (the six tickets below).
|
||||||
2. Cross-links: [`docs/guides/backend-roadmap.md`](backend-roadmap.md) §11 (environments — names Cloudron at MEDLab) and §8 (migrations policy — never rewrite applied migrations).
|
2. Cross-links: [`docs/guides/backend-roadmap.md`](backend-roadmap.md) §11 (environments — names Cloudron at MEDLab) and §8 (migrations policy — never rewrite applied migrations).
|
||||||
|
|
||||||
**Acceptance criteria:**
|
**Acceptance criteria:**
|
||||||
|
|
||||||
- [x] Admin can grant the right access + answer the open decisions in one pass without further back-and-forth.
|
- [x] Admin handoff covers exactly the access that was needed (most self-serve via Cloudron admin login).
|
||||||
- [x] Doc is ~1 page and explicitly lists what is **not** in scope so admin doesn't expect a full deploy walkthrough.
|
- [x] Cutover plan is side-by-side and explicitly avoids in-place apex replacement.
|
||||||
- [x] Six follow-up tickets enumerated and linked (see below).
|
- [x] Six follow-up tickets enumerated and linked, with CR-99 + CR-101 scope corrected to reflect that legacy is one LAMP slot containing marketing + backend + chatbot (all retire together).
|
||||||
|
- [x] Open product/infra questions surfaced rather than assumed.
|
||||||
|
|
||||||
**Files:** [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md), [`docs/guides/backend-roadmap.md`](backend-roadmap.md), [`docs/README.md`](../README.md), [`CONTRIBUTING.md`](../../CONTRIBUTING.md).
|
**Files:** [`docs/guides/ops-backend-deploy.md`](ops-backend-deploy.md), [`docs/guides/backend-roadmap.md`](backend-roadmap.md), [`docs/README.md`](../README.md), [`CONTRIBUTING.md`](../../CONTRIBUTING.md).
|
||||||
|
|
||||||
**Status:** [CR-83](https://linear.app/community-rule/issue/CR-83/backend-stagingproduction-runbook-admin-handoff-docsops-backend) **Done** (admin handoff scope). Deployment-pipeline implementation tracked in the follow-up tickets below.
|
**Status:** [CR-83](https://linear.app/community-rule/issue/CR-83/backend-stagingproduction-runbook-admin-handoff-docsops-backend) **Done**. Deployment-pipeline implementation tracked in the follow-up tickets below.
|
||||||
|
|
||||||
### Follow-up tickets filed under CR-83
|
### Follow-up tickets filed under CR-83
|
||||||
|
|
||||||
@@ -576,9 +579,10 @@ All six are titled `[Backend] …`, assigned to Vinod, in the **community-rule**
|
|||||||
| 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 |
|
| 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) |
|
| 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 |
|
| 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 |
|
||||||
| 4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-dns-cutover) | `[Backend] Cloudron production install + DNS cutover` | CR-98 green for the agreed overlap window |
|
| 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) |
|
| 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-expressmysql-backend) | `[Backend] Decommission legacy Express/MySQL backend` | CR-99 + sign-off window |
|
| 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 |
|
||||||
|
| 7 | [CR-102](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export) | `[Backend] Decide fate of legacy rules table (read-only export?)` | must resolve before CR-99 maintenance window |
|
||||||
|
|
||||||
**Per-ticket detail:**
|
**Per-ticket detail:**
|
||||||
|
|
||||||
@@ -727,9 +731,10 @@ Tickets **10–11** can be deferred without blocking the core “auth + drafts +
|
|||||||
| 12.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` |
|
| 12.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` |
|
||||||
| 12.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` |
|
| 12.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` |
|
||||||
| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` |
|
| 12.3 | [CR-98](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke) | `[Backend] Cloudron staging install + smoke` |
|
||||||
| 12.4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-dns-cutover) | `[Backend] Cloudron production install + DNS cutover` |
|
| 12.4 | [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover) | `[Backend] Cloudron production install + apex cutover` |
|
||||||
| 12.5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | `[Backend] Steady-state operator runbook` |
|
| 12.5 | [CR-100](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook) | `[Backend] Steady-state operator runbook` |
|
||||||
| 12.6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-expressmysql-backend) | `[Backend] Decommission legacy Express/MySQL backend` |
|
| 12.6 | [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app) | `[Backend] Decommission legacy CommunityRule LAMP app` |
|
||||||
|
| 12.7 | [CR-102](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export) | `[Backend] Decide fate of legacy rules table (read-only export?)` |
|
||||||
| 13 | [CR-84](https://linear.app/community-rule/issue/CR-84/backend-api-error-contract-request-id-logging) | API errors + request-id logging |
|
| 13 | [CR-84](https://linear.app/community-rule/issue/CR-84/backend-api-error-contract-request-id-logging) | API errors + request-id logging |
|
||||||
| 14 | [CR-85](https://linear.app/community-rule/issue/CR-85/backend-custom-session-lifecycle-cleanup-invalidation-policy) | Session lifecycle + cleanup **Done** |
|
| 14 | [CR-85](https://linear.app/community-rule/issue/CR-85/backend-custom-session-lifecycle-cleanup-invalidation-policy) | Session lifecycle + cleanup **Done** |
|
||||||
| 15 | [CR-86](https://linear.app/community-rule/issue/CR-86/backend-profile-dashboard-account-figma-profile) | Profile + account (Figma 22143:900069) |
|
| 15 | [CR-86](https://linear.app/community-rule/issue/CR-86/backend-profile-dashboard-account-figma-profile) | Profile + account (Figma 22143:900069) |
|
||||||
|
|||||||
@@ -1,64 +1,82 @@
|
|||||||
# Backend deploy — admin handoff
|
# Backend deploy — admin handoff + cutover plan
|
||||||
|
|
||||||
This is the list of access, environment variables, and platform decisions
|
This doc captures everything needed to deploy the new CommunityRule
|
||||||
needed to deploy CommunityRule (the new Next.js + Postgres app in this
|
(Next.js + Postgres) onto MEDLab's Cloudron and replace the legacy
|
||||||
repo) onto MEDLab's Cloudron. Hand it to the Cloudron admin; once they
|
LAMP-packaged service at `communityrule.info`. Cloudron admin access
|
||||||
confirm what is checked below, the actual deploy happens in the
|
has been granted; remaining open item is the registry decision in §6.
|
||||||
follow-up tickets listed in §8.
|
|
||||||
|
> **For a plain-language summary to hand to MEDLab's Cloudron admin,
|
||||||
|
> see [`../relaunch-brief.md`](../relaunch-brief.md).** This doc is the
|
||||||
|
> technical version.
|
||||||
|
|
||||||
## 1. Context
|
## 1. Context
|
||||||
|
|
||||||
- This app **replaces** the old Express + MySQL backend at
|
- This app **fully replaces** the existing `communityrule.info`
|
||||||
[`CommunityRule/CommunityRuleBackend`](https://git.medlab.host/CommunityRule/CommunityRuleBackend).
|
service — both the marketing site and the backend API.
|
||||||
- It is packaged as a real Cloudron app (Docker image +
|
- The existing service is a single Cloudron **LAMP** app
|
||||||
`CloudronManifest.json`). No `/app/data` drop-in. No `run.sh`
|
(`lamp.cloudronapp.php74@5.1.2`, installed at the
|
||||||
watchdog — Cloudron's container supervisor handles restarts.
|
`communityrule.info` apex, 512 MiB) that hosts three things stuffed
|
||||||
- **Greenfield Postgres.** No data migration from the old MySQL addon.
|
into one container under `/app/data/public/`:
|
||||||
Old auth (4-digit OTP in `email_otp`) is replaced by hashed
|
1. The static **marketing site** (HTML / CSS / images).
|
||||||
magic-link tokens; old API and `rules` / `version_history` tables do
|
2. The **Express/MySQL backend** at
|
||||||
not map to anything in the new app.
|
[`CommunityRule/CommunityRuleBackend`](https://git.medlab.host/CommunityRule/CommunityRuleBackend),
|
||||||
|
kept alive by a 30-min `lsof`-based `run.sh` watchdog on port
|
||||||
|
3000. MySQL is the LAMP package's bundled MySQL, persisted
|
||||||
|
inside `/app/data` (not a Cloudron addon).
|
||||||
|
3. A **Flask chatbot** at
|
||||||
|
[`CommunityRule/CommunityRuleChatBot`](https://git.medlab.host/CommunityRule/CommunityRuleChatBot)
|
||||||
|
on port 5000, also watchdog-supervised; currently crash-looping
|
||||||
|
with `ModuleNotFoundError: No module named 'flask'` and last
|
||||||
|
touched in May 2024. **Not migrated.** Dies with the LAMP
|
||||||
|
container at decommission.
|
||||||
|
- The new app is a **properly packaged Cloudron app** (Docker image +
|
||||||
|
`CloudronManifest.json`, postgresql + sendmail + localstorage
|
||||||
|
addons). Cloudron's container supervisor replaces the watchdog.
|
||||||
|
- **Greenfield Postgres.** No data migration from the LAMP container's
|
||||||
|
internal MySQL. Old auth (4-digit OTP in `email_otp`) is replaced
|
||||||
|
by hashed magic-link tokens. Old API and `rules` /
|
||||||
|
`version_history` tables do not map to anything in the new app.
|
||||||
|
|
||||||
## 2. Access I need
|
## 2. Access — granted
|
||||||
|
|
||||||
Check off as granted:
|
Cloudron admin login on `cloud.medlab.host` granted. From the
|
||||||
|
dashboard the deployer can self-serve:
|
||||||
|
|
||||||
- [ ] **Cloudron admin login** for the MEDLab instance, or "deploy
|
- [x] **Cloudron admin login** (full admin on the MEDLab instance).
|
||||||
app" capability scoped to one app slot.
|
- [x] **DNS for `communityrule.info`** — domain is managed inside
|
||||||
- [ ] **Container registry credentials** — read/write to wherever
|
Cloudron, so new subdomains and TLS certs are one-click.
|
||||||
images get pushed (Docker Hub, GHCR, MEDLab self-hosted registry —
|
- [x] **App log access** — Cloudron web log viewer.
|
||||||
admin's choice; see §6).
|
- [x] **Read of legacy app config** — visible in admin UI.
|
||||||
- [ ] **DNS** — ability to add/edit a subdomain record pointing at the
|
- [ ] **`cloudron` CLI token** — generate at *Profile → API Tokens*
|
||||||
Cloudron host, or confirmation that admin will add the records I
|
before first install. Save in 1Password.
|
||||||
specify.
|
|
||||||
- [ ] **`cloudron` CLI access** from my workstation (token-based; no
|
|
||||||
SSH needed for normal ops).
|
|
||||||
- [ ] **Cloudron app log access** — web UI is fine; SSH only if web
|
|
||||||
logs aren't enough.
|
|
||||||
- [ ] **Read access to the existing legacy app's Cloudron config** so
|
|
||||||
I can confirm domain / cert / SMTP setup before cutover.
|
|
||||||
|
|
||||||
## 3. Environment variables
|
## 3. Environment variables
|
||||||
|
|
||||||
### Cloudron auto-injects (admin: confirm the addons are enabled)
|
### Cloudron auto-injects (provisioned by addons declared in `CloudronManifest.json`)
|
||||||
|
|
||||||
|
Cloudron addons are not "enabled" platform-wide; they are requested
|
||||||
|
per-app in the manifest and provisioned at install time.
|
||||||
|
|
||||||
- `CLOUDRON_POSTGRESQL_URL` — from the **postgresql** addon. The app
|
- `CLOUDRON_POSTGRESQL_URL` — from the **postgresql** addon. The app
|
||||||
reads `DATABASE_URL`; bridging is a small in-app code change (see
|
reads `DATABASE_URL`; bridging is a small in-app code change (see
|
||||||
§8 ticket 1).
|
§8 [CR-96](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names)).
|
||||||
- `CLOUDRON_MAIL_SMTP_SERVER` / `_PORT` / `_USERNAME` / `_PASSWORD` —
|
- `CLOUDRON_MAIL_SMTP_SERVER` / `_PORT` / `_USERNAME` / `_PASSWORD` —
|
||||||
from the **sendmail** addon. The app reads `SMTP_URL`; bridged the
|
from the **sendmail** addon. The platform Mail server is configured
|
||||||
same way.
|
for `communityrule.info` with **Amazon SES relay** + "allow custom
|
||||||
|
from address" on, so `SMTP_FROM` of our choice will deliver. The
|
||||||
|
app reads `SMTP_URL`; bridged the same way.
|
||||||
|
|
||||||
### I set manually via `cloudron configure --app <id> --set-env`
|
### I set manually via `cloudron configure --app <id> --set-env`
|
||||||
|
|
||||||
- `SESSION_SECRET` — long random (`openssl rand -hex 32`). Required,
|
- `SESSION_SECRET` — long random (`openssl rand -hex 32`). Required,
|
||||||
≥ 16 chars. Rotating it logs everyone out.
|
≥ 16 chars. Rotating it logs everyone out.
|
||||||
- `SMTP_FROM` — visible "From:" address on sign-in emails. Cloudron
|
- `SMTP_FROM` — visible "From:" address on sign-in emails. Cloudron
|
||||||
does **not** inject this. Recommend `hello@communityrule.info` (same
|
does not inject this. Use `hello@communityrule.info` (continuity
|
||||||
as the old service) unless admin wants a new address.
|
with the legacy service; SES relay accepts it).
|
||||||
- `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` — turns on Postgres draft
|
- `NEXT_PUBLIC_ENABLE_BACKEND_SYNC=true` — turns on Postgres draft
|
||||||
persistence for signed-in users. Recommended for production.
|
persistence for signed-in users. Required in production.
|
||||||
|
|
||||||
## 4. Platform settings to confirm
|
## 4. Platform settings
|
||||||
|
|
||||||
- Container `httpPort`: **3000** (matches [`Dockerfile`](../../Dockerfile)
|
- Container `httpPort`: **3000** (matches [`Dockerfile`](../../Dockerfile)
|
||||||
`ENV PORT=3000`).
|
`ENV PORT=3000`).
|
||||||
@@ -66,59 +84,99 @@ Check off as granted:
|
|||||||
([`app/api/health/route.ts`](../../app/api/health/route.ts) returns
|
([`app/api/health/route.ts`](../../app/api/health/route.ts) returns
|
||||||
`200 {"ok":true,"database":"connected"}` when healthy, `503`
|
`200 {"ok":true,"database":"connected"}` when healthy, `503`
|
||||||
otherwise).
|
otherwise).
|
||||||
- Memory limit: start at **512 MB**; raise if Next.js standalone OOMs
|
- Memory limit: start at **512 MiB** (matches what the legacy LAMP
|
||||||
under load.
|
app has been running fine on for two years); raise if Next.js
|
||||||
- Backups: confirm Cloudron's daily snapshot is on for both this app
|
standalone OOMs under load.
|
||||||
and its postgresql addon.
|
- Backups: Cloudron's automatic backups are already on for the host
|
||||||
- TLS, DNS, SPF/DKIM: handled by Cloudron for the chosen subdomain —
|
(legacy app shows weekly snapshots ~451 MB each). Same default
|
||||||
confirm.
|
applies to new apps.
|
||||||
|
- TLS / DNS / SPF / DKIM: handled by Cloudron for any subdomain of
|
||||||
|
`communityrule.info`.
|
||||||
|
|
||||||
## 5. Decisions I need from admin
|
## 5. Cutover plan (side-by-side, never in-place)
|
||||||
|
|
||||||
1. **Subdomains** for staging and production. Default proposal:
|
The legacy app is at the apex `communityrule.info` and is still
|
||||||
`staging-app.communityrule.info` + `app.communityrule.info`.
|
serving real traffic. Best practice is **side-by-side cutover** — new
|
||||||
2. **Sender address** — reuse legacy `hello@communityrule.info`, or
|
app gets validated at a fresh subdomain before any swap touches the
|
||||||
pick a new one?
|
apex.
|
||||||
3. **Container registry** — MEDLab self-hosted, Docker Hub (under
|
|
||||||
what org), or GHCR?
|
|
||||||
4. **Cutover overlap** — how many days should the old service keep
|
|
||||||
running in parallel before we uninstall it?
|
|
||||||
5. **Backup retention** beyond Cloudron defaults? (If "defaults are
|
|
||||||
fine," say so.)
|
|
||||||
|
|
||||||
## 6. What is intentionally NOT in this doc
|
### Phases
|
||||||
|
|
||||||
So admin doesn't expect more than this scope:
|
1. **Staging install** — `cloudron install --image <our-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).
|
||||||
|
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.
|
||||||
|
3. **Apex cutover at a scheduled low-traffic window** — this is the
|
||||||
|
only step with brief downtime (~5–15 min). Sequence:
|
||||||
|
1. Take one final manual backup of the legacy LAMP app (Cloudron
|
||||||
|
*Backups* tab → *Backup now*).
|
||||||
|
2. `cloudron uninstall` the legacy app at `communityrule.info`.
|
||||||
|
3. `cloudron configure --location communityrule.info` to move the
|
||||||
|
validated staging install to the apex (or `cloudron install`
|
||||||
|
fresh at apex if cleaner).
|
||||||
|
4. Re-run `prisma migrate deploy`, re-set production env vars if
|
||||||
|
not preserved by the move, smoke again.
|
||||||
|
4. **Decommission** — see [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-expressmysql-backend).
|
||||||
|
Hold the final LAMP backup ≥ 90 days for safety.
|
||||||
|
|
||||||
- The deploy runbook itself (build / push / install / migrate / smoke
|
### Why not in-place?
|
||||||
/ rollback) — written **after** access in §2 is granted; see §8
|
|
||||||
ticket 5.
|
|
||||||
- Code changes to bridge `CLOUDRON_*` env vars to `DATABASE_URL` /
|
|
||||||
`SMTP_URL` — I do those locally and ship the image with bridging
|
|
||||||
built in; see §8 ticket 1.
|
|
||||||
- Decommissioning the legacy Express/MySQL service — does not happen
|
|
||||||
until the new app is green in production; see §8 ticket 6.
|
|
||||||
|
|
||||||
## 7. Old vs new backend deltas
|
Uninstalling the legacy app and installing the new one at apex
|
||||||
|
without a staging step means the live site is down for the entire
|
||||||
|
duration of the first install — and the first install is exactly when
|
||||||
|
all the env-var / addon / port surprises happen. Side-by-side keeps
|
||||||
|
those surprises out of view.
|
||||||
|
|
||||||
So nothing surprises admin or users at cutover:
|
## 6. Decisions — status
|
||||||
|
|
||||||
- Old addons: **MySQL + sendmail**. New addons: **postgresql +
|
Product decisions (closed):
|
||||||
sendmail + localstorage**. Different DB addon entirely; do not
|
|
||||||
reuse the old one.
|
1. **Final URL — `communityrule.info` apex.** New app fully replaces
|
||||||
- Old service ran from `/app/data/public/communityRuleBackend` with a
|
the legacy site, including the marketing surface. Brief cutover
|
||||||
30-min `lsof`-based `run.sh` watchdog — not a packaged Cloudron
|
downtime (~5–15 min) is accepted.
|
||||||
app. New app is a proper Cloudron app; Cloudron supervises it.
|
2. **Legacy `rules` data — not migrated.** No data moves into the new
|
||||||
- Old auth = plaintext 4-digit OTP. New auth = magic **link** in
|
app's Postgres. A pre-cutover **read-only export** of the
|
||||||
email. If users report "I'm not getting a code," remind them to
|
`rules` + `version_history` MySQL tables is under consideration;
|
||||||
look for a link.
|
approach depends on the actual row count, which we'll pull as
|
||||||
|
part of the CR-99 pre-cutover backup. Tracked in
|
||||||
|
[CR-102](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export).
|
||||||
|
|
||||||
|
Infra decision still open:
|
||||||
|
|
||||||
|
3. **Container registry** — GHCR (under your personal / org account,
|
||||||
|
lowest friction), Docker Hub, or MEDLab self-hosted. Tracked in
|
||||||
|
[CR-97](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push).
|
||||||
|
|
||||||
|
## 7. Old vs new deltas
|
||||||
|
|
||||||
|
So nothing surprises anyone at cutover:
|
||||||
|
|
||||||
|
- Legacy is a **LAMP package** with bundled MySQL inside the
|
||||||
|
container. New app uses the Cloudron **postgresql + sendmail +
|
||||||
|
localstorage** addons — entirely different storage, no shared
|
||||||
|
state.
|
||||||
|
- Legacy stuffs three apps (marketing + Node backend + Python
|
||||||
|
chatbot) into one container with a `run.sh` watchdog. New app is
|
||||||
|
one Next.js process, supervised by Cloudron natively.
|
||||||
|
- Old auth = plaintext 4-digit OTP. New auth = hashed magic **link**
|
||||||
|
in email. If users report "I'm not getting a code," remind them to
|
||||||
|
look for a link instead.
|
||||||
- Old code hardcoded `from: 'hello@communityrule.info'` in
|
- Old code hardcoded `from: 'hello@communityrule.info'` in
|
||||||
[`controllers/emailController.js`](https://git.medlab.host/CommunityRule/CommunityRuleBackend/raw/branch/master/controllers/emailController.js)
|
[`controllers/emailController.js`](https://git.medlab.host/CommunityRule/CommunityRuleBackend/raw/branch/master/controllers/emailController.js)
|
||||||
because Cloudron does not inject a `MAIL_FROM`. New app reads
|
because Cloudron does not inject a `MAIL_FROM`. New app reads
|
||||||
`SMTP_FROM` — see §3.
|
`SMTP_FROM` — see §3.
|
||||||
- Old API surface (`/api/send_otp`, `/api/publish_rule`, etc.) and
|
- Old API surface (`/api/send_otp`, `/api/publish_rule`, etc.) and
|
||||||
schema (`rules` + `version_history` tables, soft-delete via
|
schema (`rules` + `version_history` tables, soft-delete via
|
||||||
`deleted` column) **do not overlap**. No data migration.
|
`deleted` column) **do not overlap** with the new app. No data
|
||||||
|
migration.
|
||||||
|
- The Flask chatbot at
|
||||||
|
[`CommunityRule/CommunityRuleChatBot`](https://git.medlab.host/CommunityRule/CommunityRuleChatBot)
|
||||||
|
is currently crash-looping inside the LAMP container and is **not
|
||||||
|
being migrated** — confirmed with admin. It dies when the LAMP
|
||||||
|
container is uninstalled in [CR-101](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-expressmysql-backend).
|
||||||
|
|
||||||
## 8. Follow-up tickets
|
## 8. Follow-up tickets
|
||||||
|
|
||||||
@@ -127,22 +185,29 @@ All filed in Linear, titled `[Backend] …`, assigned to me, in the
|
|||||||
|
|
||||||
1. [**CR-96**](https://linear.app/community-rule/issue/CR-96/backend-bridge-cloudron-env-vars-to-canonical-names)
|
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`. No
|
— `[Backend] Bridge CLOUDRON_* env vars to canonical names`. No
|
||||||
admin dependency; can land now.
|
blockers; can land now.
|
||||||
2. [**CR-97**](https://linear.app/community-rule/issue/CR-97/backend-container-image-registry-choose-build-push)
|
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`.
|
— `[Backend] Container image registry: choose, build, push`.
|
||||||
Depends on registry decision (§5).
|
Blocked by registry decision (§6.3).
|
||||||
3. [**CR-98**](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke)
|
3. [**CR-98**](https://linear.app/community-rule/issue/CR-98/backend-cloudron-staging-install-smoke)
|
||||||
— `[Backend] Cloudron staging install + smoke`. Blocked by CR-96
|
— `[Backend] Cloudron staging install + smoke` at
|
||||||
+ CR-97; needs Cloudron CLI access + staging DNS.
|
`staging.communityrule.info`. Blocked by CR-96 + CR-97.
|
||||||
4. [**CR-99**](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-dns-cutover)
|
4. [**CR-99**](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)
|
||||||
— `[Backend] Cloudron production install + DNS cutover`. Blocked
|
— `[Backend] Cloudron production install + apex cutover`.
|
||||||
by CR-98 green for the agreed overlap window.
|
Side-by-side cutover at scheduled low-traffic window per §5.
|
||||||
|
Blocked by CR-98 green + CR-102 resolved.
|
||||||
5. [**CR-100**](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook)
|
5. [**CR-100**](https://linear.app/community-rule/issue/CR-100/backend-steady-state-operator-runbook)
|
||||||
— `[Backend] Steady-state operator runbook`. Blocked by CR-98
|
— `[Backend] Steady-state operator runbook`. Blocked by CR-98
|
||||||
(we write it after we've actually done it).
|
(write what we actually did).
|
||||||
6. [**CR-101**](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-expressmysql-backend)
|
6. [**CR-101**](https://linear.app/community-rule/issue/CR-101/backend-decommission-legacy-communityrule-lamp-app)
|
||||||
— `[Backend] Decommission legacy Express/MySQL backend`. Blocked
|
— `[Backend] Decommission legacy CommunityRule LAMP app`.
|
||||||
by CR-99 + sign-off window. Priority: Low.
|
Uninstall the entire LAMP slot (marketing + Express backend +
|
||||||
|
chatbot in one go); preserve final backup ≥ 90 days. Blocked by
|
||||||
|
CR-99 + sign-off window. Priority: Low.
|
||||||
|
7. [**CR-102**](https://linear.app/community-rule/issue/CR-102/backend-decide-fate-of-legacy-rules-table-read-only-export)
|
||||||
|
— `[Backend] Decide fate of legacy rules table (read-only export?)`.
|
||||||
|
Count rows + decide whether to publish a static archive before
|
||||||
|
CR-99 uninstalls the legacy MySQL. Priority: Low.
|
||||||
|
|
||||||
## 9. Related docs
|
## 9. Related docs
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# CommunityRule relaunch
|
||||||
|
|
||||||
|
A short high-level summary of what's being built, what it replaces, and
|
||||||
|
how the cutover will work.
|
||||||
|
|
||||||
|
## What gets replaced
|
||||||
|
|
||||||
|
The existing `CommunityRule` Cloudron app currently hosts three things in
|
||||||
|
one container:
|
||||||
|
|
||||||
|
- The static marketing site.
|
||||||
|
- The Express + MySQL backend (rule drafting, publishing, OTP sign-in).
|
||||||
|
- A Flask chatbot.
|
||||||
|
|
||||||
|
All three retire together when the new app goes live. The chatbot is
|
||||||
|
**not** being migrated or replaced in this stage.
|
||||||
|
|
||||||
|
## What the new app is
|
||||||
|
|
||||||
|
- A Next.js application with a Postgres database, packaged as a
|
||||||
|
Cloudron app (Docker image + `CloudronManifest.json`).
|
||||||
|
- Uses Cloudron's **postgresql + sendmail + localstorage** addons.
|
||||||
|
Cloudron's built-in container supervisor keeps it running.
|
||||||
|
- Sign-in changes from 4-digit email **codes** to email **links**
|
||||||
|
("magic link" authentication). Users click a link in their inbox
|
||||||
|
instead of typing a code.
|
||||||
|
- One visible process, one port (3000), one health check
|
||||||
|
(`/api/health`), ~512 MiB memory — the same footprint as the
|
||||||
|
existing app.
|
||||||
|
|
||||||
|
## What does NOT carry over
|
||||||
|
|
||||||
|
- **No user accounts.** New sign-ins start fresh.
|
||||||
|
- **No published rules from the old database.** We'll count the
|
||||||
|
existing `rules` table before cutover and decide whether to publish
|
||||||
|
a read-only archive (CSV/JSON) somewhere for anyone looking for
|
||||||
|
their old work.
|
||||||
|
- **No chatbot.**
|
||||||
|
|
||||||
|
## How the cutover will work
|
||||||
|
|
||||||
|
Side-by-side, the legacy app keeps running untouched
|
||||||
|
until the new one is verified.
|
||||||
|
|
||||||
|
1. **Staging phase.** New app installed at
|
||||||
|
`staging.communityrule.info` (auto-provisioned by Cloudron). Legacy
|
||||||
|
app at the apex is not touched. Quiet testing within MEDLab/stakeholders.
|
||||||
|
2. **Cutover phase.** When staging is green and we're ready, schedule
|
||||||
|
a low-traffic window. During the window (roughly 5–15 minutes of
|
||||||
|
apex downtime):
|
||||||
|
- Take a final backup of the legacy app (Cloudron one-click).
|
||||||
|
- Pull a copy of the legacy `rules` table if we decided to publish
|
||||||
|
an archive.
|
||||||
|
- Uninstall the legacy app at the apex `communityrule.info`.
|
||||||
|
- Move the new app to the apex.
|
||||||
|
- Smoke-test, confirm backups are on, done.
|
||||||
|
3. **Post-cutover.** Legacy backup retained ≥ 90 days as a safety net.
|
||||||
|
Legacy source repos get README pointers to the new app and are archived.
|
||||||
|
|
||||||
|
Rollback plan during the window: restore the legacy backup to a scratch
|
||||||
|
Cloudron slot and point DNS back. Realistic only if we discover
|
||||||
|
something genuinely broken in the first few minutes.
|
||||||
|
|
||||||
|
## Rough timeline
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
|
||||||
|
Staging should be ready to deploy in 1-2 weeks, and we can go from there.
|
||||||
Reference in New Issue
Block a user