# ECM · Deployment guide **Scope:** make ECM reachable at a real URL your team can visit. This guide is honest about what's a one-liner and what requires DNS / hosting action outside this repo. --- ## Immediate-use URL (works TODAY · no DNS action) Everything under `docs/ecm/*` is already served by the existing GitHub Pages deploy of this repo. As soon as a commit touching `docs/ecm/*` is pushed to `main` and the Pages build finishes (typically 1–2 min), these URLs are live: ``` https://console.pattayatogether.com/login.html ← root login · single sign-in for the console https://console.pattayatogether.com/ecm/ ← ECM router (after sign-in) https://console.pattayatogether.com/ecm/portal.html https://console.pattayatogether.com/ecm/documents.html https://console.pattayatogether.com/ecm/status.html https://console.pattayatogether.com/ecm/known-gaps.html ``` > `/ecm/login.html` was hard-deleted in Batch 20.3. All auth goes through the root `/login.html` from there on; signing in at root unlocks every path on the console including `/ecm/*`. **No DNS change. No subdomain. No separate repo.** This is what Batch 15 ships — the ECM front-end is live on the same host as the main console. What is NOT live by default: - The two FastAPI backends (`document-access-service` on :8090, `document-note-service` on :8091). These run locally via `run.sh`; to expose them outside your machine you must host them somewhere your team's browsers can reach. Without them, ECM labels the session "Backend offline" and disables actions that need a backend. - Email/SMTP, OAuth/SSO, TLS on the backends, reverse-proxy gating — see `known-gaps.html`. --- ## `ecm.pattayatogether.com` subdomain (honest blocker) **Blocker — read first.** GitHub Pages allows exactly ONE custom domain (CNAME) per repository. `CNAME` is already set to `console.pattayatogether.com`. Pointing `ecm.pattayatogether.com` at the same Pages site through its own `CNAME` record alone is not supported — Pages will reject it. There are three honest paths forward. Pick the one that matches your hosting. All three result in `https://ecm.pattayatogether.com/` working; they differ only in effort and where the files actually live. ### Path A — Cloudflare Worker (fastest, no extra hosting) Cloudflare sits in front of `pattayatogether.com` and rewrites `ecm.pattayatogether.com/*` to `console.pattayatogether.com/ecm/*`. The files never move; the subdomain is a URL rewrite. - Repo artefact: `deploy/cloudflare-worker.js` - Manual steps: `deploy/cloudflare-worker.js` header comment (DNS + route binding) - Time: 5–10 min if you already have a Cloudflare zone for `pattayatogether.com` ### Path B — Hostinger subdomain (static upload, same hosting you use for `platform/`) Create `ecm.pattayatogether.com` as a Hostinger subdomain, then upload the contents of `docs/ecm/` to that subdomain's public directory. The subdomain serves ECM directly. - Repo artefact: `deploy/hostinger-subdomain.md` - Manual steps: hPanel → Subdomains → Create → FTP/SFTP upload - Time: 15–20 min ### Path C — Separate GitHub Pages repo · **same pattern as `console.pattayatogether.com`** Console is just a GitHub Pages repo with `docs/CNAME = console.pattayatogether.com`. ECM follows the identical pattern in a sibling repo (`Thailand-Together-ECM`) whose own CNAME is `ecm.pattayatogether.com`. A **GitHub Actions workflow auto-mirrors** `docs/ecm/*` from this repo to the sibling on every push, so you only ever push once. - Bootstrap: `bash scripts/ecm-init-pages-mirror.sh` (6 automated steps · `gh` CLI) - Workflow: `.github/workflows/mirror-ecm-pages.yml` - Operator runbook: `deploy/ecm-mirror-setup.md` - Manual remainder: 2 panel clicks (Pages enable + Cloudflare DNS row) · ~4 min - Time end-to-end: ~7 min one-time; subsequent updates auto-flow ### Path D — Cloudflare Worker API (real backend · Batch 18) **This is different from the other three paths:** Paths A–C rehost the static ECM UI; Path D deploys the **real backend** (ported from the two FastAPI services) as a Cloudflare Worker routed at `console.pattayatogether.com/api/*`. ECM stays on the existing GitHub Pages URL — the Worker only intercepts `/api/*`. After Path D, the production URL runs in **BACKEND mode**: real sessions, notes shared across users, disabled-user lockout enforced server-side, all bilingual error reasons come from the server. - Repo artefacts: - `deploy/ecm-worker.js` — the deployable JS Worker (13 endpoints · KV-backed) - `deploy/wrangler.toml` — copy-paste Wrangler config - `deploy/worker-deploy.md` — step-by-step 5-command guide - Manual steps: `wrangler login` · `wrangler kv:namespace create ECM_KV` · edit `wrangler.toml` · `wrangler deploy` - Time: 5–10 min - Cost: $0 on CF Free plan for internal-team use - Unlock per-session with `?backend=1`; make permanent with a one-line switch in `ecm-config.js` --- ## nginx reverse-proxy (optional HTTP gating) If you run a VPS that already fronts the console, you can gate document access at the edge using `deploy/nginx-subdomain.conf`. The gate re-routes protected HTML to the access service's `/api/access/render` endpoint so direct file access is blocked. This is the correct production posture; without it, original files are still reachable by direct URL (documented in `known-gaps.html`). --- ## Configuring backend URLs (all deployment paths) Each ECM page loads `ecm-config.js` BEFORE `assets/ecm.js`. That small file sets three globals that control where the shell talks to: ```js window.ECM_AUTH_BASE // e.g. 'https://auth.pattayatogether.example' window.ECM_NOTES_BASE // e.g. 'https://notes.pattayatogether.example' window.ECM_DOC_ROOT // e.g. 'https://console.pattayatogether.com' or '..' window.ECM_DEPLOY_LABEL // e.g. 'ecm.pattayatogether.com · prod' ``` Templates for the common scenarios live in `ecm-config.example.js` (copy the block that matches your environment into `ecm-config.js` on your deploy host). Resolution order (first non-empty wins): 1. `` / `` / `` 2. `window.ECM_AUTH_BASE` / `window.ECM_NOTES_BASE` / `window.ECM_DOC_ROOT` 3. Hardcoded defaults (`http://127.0.0.1:8090`, `http://127.0.0.1:8091`, `..`) 4. Literal `'none'` disables the corresponding backend. --- ## CORS / origin policy on the backends Whichever subdomain you land on, add it to the services' allow-lists before the front-end can talk to them: ```bash export DAS_ALLOW_ORIGINS="https://ecm.pattayatogether.com,https://console.pattayatogether.com" export DNS_ALLOW_ORIGINS="https://ecm.pattayatogether.com,https://console.pattayatogether.com" export DAS_REJECT_QUERY_TOKEN=true # never place tokens in URLs export DAS_SIGNING_KEY="<64-char hex>" # persistent, from secret manager ``` The services ship with `127.0.0.1` defaults in `run.sh`; re-export before running them in a LAN / VPS context. --- ## Verify the deployment After any deployment path: 1. Open the target URL (e.g. `https://ecm.pattayatogether.com/` or `https://console.pattayatogether.com/ecm/`). 2. The router either routes to `login.html` or, if the cookie is valid, to `portal.html`. 3. Hit `/status.html` — it probes both backends live and must report either *backend online* (if you wired them) or *offline* (honest, expected when you didn't). 4. Sign in with a test email. On success the cookie is set and the session badge turns green. 5. Open a document; the "Full content" pane must either render via `/api/access/render` (backend up) or fall back to the source-file iframe (backend down). The mode is labeled on screen. If anything is labeled "(claimed, not verified)" it's a bug — every badge is sourced from a live probe. --- ## What this guide does NOT do - Provision TLS on the FastAPI backends — bring your own reverse proxy (caddy, nginx, traefik, Cloudflare Tunnel). - Provision email / SMTP / OAuth — tracked on `known-gaps.html` as `blocked-by-infra`. - Automate the second push to a separate gh-pages repo — if you pick Path C, wire your own workflow (a simple `rsync` job on `main` works). For the full list of what is and isn't ready, read `known-gaps.html` on the live site.