Skip to content

Latest commit

 

History

History
161 lines (117 loc) · 10.2 KB

File metadata and controls

161 lines (117 loc) · 10.2 KB

Deploy

UnionOps — stewarded by Ryan Morris.

If you host an instance, you are the data controller for data that instance stores (sessions, Officer Hub records, future database). Prefer Canadian hosting for confidential modules. Comms graphics tools remain on-device in the browser; the Officer Hub does not.

Production checklist

  1. Set a unique AUTH_SECRET (openssl rand -base64 32) — never use the repo placeholders. Production refuses to start without it.
  2. Set AUTH_URL to your public HTTPS origin (no trailing slash). Behind CapRover or custom nginx, this must be the browser-facing host (e.g. https://unionops.org), not the internal app FQDN — otherwise locale redirects and Auth cookies advertise the proxy hostname to crawlers.
  3. MFA is opt-in (AUTH_MFA_ENABLED=true). Leave it off for demos/usability. For real casework: enable MFA and set AUTH_MFA_MODE=totp. Workshop hosts may use shared_code_insecure only with AUTH_ALLOW_SHARED_MFA_IN_PROD=true plus a unique AUTH_MFA_CODE. When MFA is enabled in production, unset mode or shared-code without break-glass fails closed.
  4. Set your union’s default brand (optional but recommended for a white-label host):
    • Edit config/host-brand.json before build, or
    • npm run brand:set -- --primary=#… --secondary=#… --local=… --sub="…", or
    • Pass NEXT_PUBLIC_BRAND_PRIMARY / SECONDARY / ACCENT and NEXT_PUBLIC_DEFAULT_LOCAL_NUMBER / SUB_TEXT as container env (see .env.example).
  5. Do not rely on demo accounts (demo123) for real grievances or member files. On workshop/demo hosts, set NEXT_PUBLIC_DEMO_SITE=true so the authenticated hub shows a Demo banner; turn that off for real tenant instances.
  6. Confirm health: GET /api/health{"status":"ok",...} (includes version, commit, backends, emailEnabled, cronConfigured) or npm run health:check.
  7. CSP and related security headers are set in next.config.ts (apply on CapRover/Docker/Vercel alike).
  8. Read the two-tier privacy model in the site Privacy page and docs/COMPLIANCE.md.

GHCR images

Containers publish to GitHub Container Registry from docker/Dockerfile.

Main tip (after successful CI on main — see .github/workflows/ci.yml):

ghcr.io/hackmods/union-communications:main
ghcr.io/hackmods/union-communications:sha-<short>

Tagged releases (v* — see .github/workflows/release.yml):

ghcr.io/hackmods/union-communications:v0.1.0
ghcr.io/hackmods/union-communications:latest

:latest is only updated on version tags, not on every main push.

Pull and run:

docker pull ghcr.io/hackmods/union-communications:v0.1.0
docker run --rm -p 3000:3000 \
  -e AUTH_SECRET="$(openssl rand -base64 32)" \
  -e AUTH_URL="https://your.domain.example" \
  ghcr.io/hackmods/union-communications:v0.1.0

Docker Compose (image)

See commented image: example in docker/docker-compose.yml.

Docker Compose (build from source)

cd docker
docker compose up --build

Compose requires AUTH_SECRET in the environment (or docker/.env). MFA stays off by default (AUTH_MFA_ENABLED=false); set it true plus mode/code when testing second-factor flows.

CapRover

This repo includes captain-definition pointing at ./docker/Dockerfile.

Durable Postgres on CapRover: step-by-step walkthrough in CAPROVER_POSTGRES.md (two-app setup, env template, bootstrap seed, verify). Paste-ready env: docker/.env.production.example.

Prefer GHCR image pull on small droplets (ghcr.io/hackmods/union-communications:main) over CapRover git rebuilds — large COPY --from / node_modules layers can fail with BuildKit unknown parent image ID. Lessons: session-knowledge-2026-08-25-caprover-buildkit.md, .cursor/rules/caprover-docker.mdc. CI publishes images in the docker-image job (independent of E2E). Optional secrets: CAPROVER_SERVER, CAPROVER_PASSWORD, CAPROVER_APP.

  1. Create an app; set Container HTTP Port to 3000 (not 80). A wrong port yields CapRover NGINX 502 even when logs say Ready.
  2. App Configs (minimum):
Variable Example
AUTH_SECRET output of openssl rand -base64 32
AUTH_URL Public HTTPS origin only (no trailing slash) — e.g. https://unionops.org. Never the CapRover/internal FQDN (*.behind7proxies.com); wrong value leaks internal hosts into locale redirects, Auth.js callback-url cookies, and GSC “Page with redirect” noise.
AUTH_ALLOW_DEMO_USERS true on the public demo host so president.243@unionops.test / demo123 work. The image defaults this to match NEXT_PUBLIC_DEMO_SITE. Omit/false for live casework.
AUTH_MFA_ENABLED true for casework; omit/false for demos (default)
AUTH_MFA_MODE totp when MFA enabled; workshops: shared_code + break-glass
AUTH_ALLOW_SHARED_MFA_IN_PROD true only for workshop/demo hosts using shared code
AUTH_MFA_CODE 6-digit code when using shared_code mode

Optional transactional email (invites / officer reminders / RSVP confirm — ADR-016):

Variable Example
EMAIL_ENABLED true (required to send; otherwise APIs return not_configured)
NEXT_PUBLIC_EMAIL_ENABLED true (Hub Invites “Send email” button; bake at build time)
EMAIL_FROM UnionOps <noreply@your-verified-domain> (must match a Mailgun-verified domain)
MAILGUN_API_KEY Mailgun Private API key (Sending → API Security) — required on DigitalOcean
MAILGUN_DOMAIN Verified sending domain (e.g. unionops.org or mg.unionops.org)
MAILGUN_API_REGION us (default) or eu

DigitalOcean CapRover: outbound SMTP on 25 / 465 / 587 is blocked (CONN ETIMEDOUT). Do not rely on SMTP_* there — set MAILGUN_API_KEY + MAILGUN_DOMAIN so UnionOps sends over HTTPS (api.mailgun.net). Confirm with GET /api/auth/email-status/smtp.preferredTransport: "mailgun_api".

Optional SMTP (non-DO hosts, or Mailgun port 2525 if your network allows it):

Variable Example
SMTP_HOST smtp.mailgun.org (EU: smtp.eu.mailgun.org)
SMTP_PORT 2525 preferred when 465/587 are blocked; else 587
SMTP_USER Mailgun SMTP login (often postmaster@mg.your-domain)
SMTP_PASS Mailgun SMTP password from Domain → SMTP credentials

When MAILGUN_API_KEY is set it takes priority over SMTP. On SMTP CONN timeout to 465/587 the app retries port 2525 once, then surfaces a DigitalOcean hint.

Optional error sinks (ADR-006 — ops only, not product analytics; defaults off). Full matrix: HOSTED_SECURITY.md.

Variable Example
SENTRY_ENABLED true (server/edge; needs DSN)
NEXT_PUBLIC_SENTRY_DSN Sentry client key — bake at build for browser errors
SENTRY_DSN Optional runtime server DSN (CapRover App Configs OK)
ERROR_LOG_FILE_ENABLED true
ERROR_LOG_FILE_PATH /data/logs/unionops-errors.jsonl (mount a Persistent Directory)
ERROR_LOG_FILE_MAX_BYTES optional rotate threshold (default 10 MiB)
ERROR_LOG_FILE_KEEP optional rotated file count (default 3)

After deploy, curl -sL https://<host>/api/health/ → check observability (and watch for *Misconfigured / sentryClientServerMismatch).

Optional brand defaults — bake into the image at build time (NEXT_PUBLIC_* is inlined by Next.js). Prefer editing config/host-brand.json (or npm run brand:set) before docker build when you want a white-label host without env sprawl:

Variable Example
NEXT_PUBLIC_BRAND_PRIMARY #CE1126
NEXT_PUBLIC_BRAND_SECONDARY #FFFFFF
NEXT_PUBLIC_BRAND_ACCENT #9B0D1C
NEXT_PUBLIC_DEFAULT_LOCAL_NUMBER 79
NEXT_PUBLIC_DEFAULT_SUB_TEXT Hospital Workers
NEXT_PUBLIC_OFFICER_HUB_PUBLIC true (Docker soft-launch default)
NEXT_PUBLIC_DEMO_SITE true on demo hosts; false for live tenants. Bake at build time (login hint). The runner image also sets AUTH_ALLOW_DEMO_USERS to the same value so production authorize() matches the hint.
  1. Deploy via CapRover git push / webhook, or pull the GHCR tag if your CapRover setup uses a registry image.
  2. Health check: GET /api/health.
  3. Public unionops.org host: point the installable origin at apex https://unionops.org. Set AUTH_URL=https://unionops.org (same origin). Until www serves this app (or 301s to apex) with a trusted certificate, leave www off the PWA service-worker allowlist (src/lib/pwa/hosts.ts). After deploy, spot-check curl -sI https://unionops.org/examples/Location must stay on unionops.org, not the CapRover hostname.

CI on main can POST CAPROVER_WEBHOOK_URL (GitHub Actions secret) after tests pass.

Hybrid backups

/app/hybrid encrypts export/import in the browser. Passphrases never leave the client. Hybrid is a backup preference today — it does not replace configuring a secure hosted store for live multi-user grievance data.

Related

Proxmox sandbox (internal)

For the CT 115 lab host (192.168.0.115:3000):

  1. On your workstation: npm run package:sandboxunionops-src.tar.gz (git archive, no node_modules).
  2. Copy the tarball to the container (e.g. /tmp/unionops-src.tar.gz).
  3. Overlay into /root/unionops-src (tar -xzf — do not wipe the tree with rm -rf).
  4. Rebuild: docker build -f docker/Dockerfile -t unionops:local . with BUILD_COMMIT_SHA set to the deployed commit.
  5. Restart the unionops container preserving env from docker inspect (never log AUTH_SECRET).
  6. Verify: npm run health:check and npm run test:smoke:sandbox from your workstation.