Files
deepdrft/deploy/PROD-INSTALL.md
T
daniel-c-harvey 1e063d95f4
Deploy DeepDrftAPI / Build, Publish & Bundle (push) Successful in 2m21s
Deploy DeepDrftManager / Build & Publish (push) Successful in 1m33s
Deploy DeepDrftPublic / Build & Publish (push) Successful in 4m17s
Package install tarball / package (push) Successful in 7s
Deploy DeepDrftAPI / Deploy (push) Successful in 1m33s
Deploy DeepDrftManager / Deploy (push) Successful in 1m28s
Deploy DeepDrftPublic / Deploy (push) Successful in 1m30s
chore: Trigger CI
2026-06-23 08:50:22 -04:00

8.5 KiB
Raw Permalink Blame History

DeepDrftHome — Production Installation Checklist

Fresh-box checklist for deploying the DeepDrftHome solution (DeepDrftPublic, DeepDrftManager, DeepDrftAPI) to a new production host. Every package, directory, and service is treated as absent. Gated steps — those requiring a decision, a secret, or a network action from the operator — are marked [GATE].

This document is a reference you run from. It can drift from the actual deploy/ scripts (bootstrap.sh, install.sh, setup-step10-creds.sh, the systemd units, and the nginx templates) — when those change, update this checklist to match.

Phase 0 — Prerequisites (build/admin machine)

  • Confirm DNS A/AAAA records for deepdrft.com and app.deepdrft.com point at the new host's IP — certbot's HTTP-01 challenge fails if DNS hasn't propagated. [GATE]
  • Generate a CI deploy ed25519 key on your local machine (not the host): ssh-keygen -t ed25519 -C "gitea-ci-deepdrft-prod" -f ~/.ssh/gitea_deepdrft_prod. Public key → installer prompt; private key → Gitea secret DEEPDRFT_PROD_SSH_DEPLOY. [GATE]
  • Download the latest deepdrft-install.tar.gz release asset (built by package-install.yml on a deploy/ push to master). If none exists, push a no-op change to deploy/ on master and wait for the artifact. [GATE]
  • scp deepdrft-install.tar.gz root@<host>:/tmp/

Phase 1 — Bootstrap / installer (run as root)

  • Run: INSTALL_PKG_PATH=/tmp/deepdrft-install.tar.gz bash bootstrap.sh (installs OS prereqs, hands off to install.sh).
  • The installer is interactive — have ready: app user (deepdrft / /deepdrft), PG role (deepdrft), DB names (deepdrft-meta, deepdrft-auth), public domain, app subdomain (default app.<public>), ports (5000/5001/5002), certbot email, PG password (twice), CI deploy public key. [GATE]

Automated Steps 010: apt preflight (postgresql, nginx, rsync, openssl, jq, wget) → create user → enable-linger → directory layout → deploy scripts to /opt/deepdrft/bin/ → systemd units enabled (not started) → credentials (Step 6, prompts) → PostgreSQL role + DBs → authorized_keys forced-command → nginx vhosts → summary.

Phase 2 — Credentials (Step 6, interactive) [GATE]

Writes 6 files to /deepdrft/.config/credentials/ (mode 600): filedatabase.json (no prompt; vault path hardcoded), apikey.json (auto-generate or paste), connections.json (PG password), authblocks.json (JWT secret, issuer/audience, SMTP host+token+From, admin user/email/password, support email), api-public.json + api-manager.json (auto-built). JWT issuer/audience must match appsettings.json AuthBlocks:Jwt.

Verify: sudo -u deepdrft ls -la /deepdrft/.config/credentials/ → 6 × mode 600, owned deepdrft:deepdrft.

Phase 3 — CorsSettings [GATE]

DeepDrftAPI/appsettings.json CorsSettings.AllowedOrigins must include the Manager origin https://app.deepdrft.com. The API throws on startup if origins are empty; a missing Manager origin causes silent 401s on CMS auth. (Confirm this is present before building.)

Phase 4 — Gitea secrets [GATE]

Add DEEPDRFT_PROD_SSH_DEPLOY = full private key contents. (The dev/beta host uses DEEPDRFT_DCH7_SSH_DEPLOY.)

Phase 5 — TLS (after DNS propagates) [GATE]

host deepdrft.com
host app.deepdrft.com
snap install --classic certbot
ln -sf /snap/bin/certbot /usr/bin/certbot
certbot --nginx --email <email> --agree-tos --no-eff-email -d deepdrft.com -d app.deepdrft.com
nginx -t && systemctl reload nginx
certbot renew --dry-run

The installer's vhosts are HTTP-only (listen 80); certbot rewrites them in place to add the 443 blocks.

Phase 6 — Verify SSH forced-command chain (before first deploy) [GATE]

  • Layer 1: ssh -i key deepdrft@host deploy-public → prints the [deploy-public] prefix then a missing-archive error (non-zero exit expected).
  • Layer 2: ssh -i key deepdrft@host idssh-wrapper: unknown command: id (no shell).
  • Layer 3: rsync a smoke file → lands in /deepdrft/staging/.
  • Layer 4: deploy-public, deploy-manager, deploy-api each print their prefix.

Do not proceed until all four pass.

Phase 7 — First deploy (push master)

Three workflows trigger by path filter:

  • deploy-api.yml: DeepDrftAPI/, DeepDrftData/, DeepDrftContent/, DeepDrftModels/
  • deploy-public.yml: DeepDrftPublic/, DeepDrftPublic.Client/, DeepDrftShared.Client/, DeepDrftModels/
  • deploy-manager.yml: DeepDrftManager/, DeepDrftShared.Client/, DeepDrftModels/

Root-file-only changes trigger none. deploy-api builds + publishes self-contained linux-x64, runs ef migrations bundle, rsyncs, then deploy-api.sh applies the EF bundle to deepdrft-meta, swaps bin/, restarts the unit. deploy-public also installs the wasm-tools workload. Watch the three parallel Gitea jobs. [GATE]

Phase 8 — EF migration verification [GATE]

The EF bundle runs before the binary swap (metadata DB). The AuthBlocks deepdrft-auth schema self-migrates on first boot and seeds the admin. Verify \dt on both DBs plus __EFMigrationsHistory. On failure: journalctl --user -u deepdrftapi.

Phase 9 — Service health

Check deepdrftapi / deepdrftpublic / deepdrftmanager via systemctl --user status and journalctl. Common failures: missing/wrong credential key; unreadable creds (must be 600 deepdrft:deepdrft); PostgreSQL not on peer auth; wrong vault path; OOM on droplets < 2 GB (add swap).

Phase 10 — Smoke-test per host

API (port 5002, internal):

  • curl localhost:5002/api/track/page → empty list on fresh install.
  • curl localhost:5002/api/stats/home → zeros.
  • POST /api/auth/login with admin creds → a JWT.

Public site:

  • curl https://deepdrft.com/ renders.
  • curl https://deepdrft.com/robots.txtAllow: / (NOT Disallow: / — that means the env isn't Production).
  • curl https://deepdrft.com/sitemap.xml → XML (static roots present even with 0 releases).
  • Confirm the unit carries Environment=ASPNETCORE_ENVIRONMENT=Production.

Manager (CMS):

  • curl https://app.deepdrft.com/ renders.
  • curl https://app.deepdrft.com/robots.txtDisallow: / (always uncrawlable).
  • curl -o /dev/null -w "%{http_code}" https://app.deepdrft.com/ → 200.

Blazor WebSocket:

  • curl -H "Upgrade: websocket" -H "Connection: Upgrade" https://deepdrft.com/_blazor → 101 (not 502/504 from nginx).

Phase 11 — Hardening

  • Change the admin password immediately — the seed credentials are stored plaintext on disk. [GATE]
  • Firewall (UFW): allow 22/80/443, deny the rest. Port 5002 (API) is internal-only.
  • apt-get install -y unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades
  • Review pg_hba.conf — no host all all 0.0.0.0/0 md5 line; the deepdrft role connects via Unix socket (peer auth).
  • Back up ~/api/deepdrft/vaults — it is not in any deploy artifact or EF bundle; a server wipe loses all audio permanently. [GATE]

Phase 12 — Iteration notes

  • EF migrations auto-apply before the binary swap on every deploy — no manual dotnet ef database update.
  • AuthBlocks self-migrates on each API start.
  • The FileDatabase vault is never touched by deploys; new vault types are created by the app on first access.
  • Credential rotation: rerun setup-step10-creds.sh --force on the host as deepdrft, then restart the affected services.
  • Each deploy script moves the current bin/ to bin.prev/ — manual rollback: mv ~/public/bin.prev ~/public/bin && systemctl --user restart deepdrftpublic.service (substitute manager / api/deepdrft).
  • Two distinct staging dirs: ~/staging/ (CI rsync jail) vs ~/api/deepdrft/vaults/staging/ (large-audio upload staging). Do not conflate them.

Host path reference

Path What
/deepdrft/.config/credentials/ 6 × JSON credential files (600)
/deepdrft/.config/systemd/user/ 3 × .service unit files
/deepdrft/public/bin/ DeepDrftPublic publish output
/deepdrft/manager/bin/ DeepDrftManager publish output
/deepdrft/api/deepdrft/bin/ DeepDrftAPI publish output
/deepdrft/api/deepdrft/vaults/ FileDatabase vault — never delete, never in deploy
/deepdrft/staging/ rsync jail root (CI artifact drop zone)
/opt/deepdrft/bin/ssh-wrapper Forced-command dispatcher
/opt/deepdrft/bin/deploy-*.sh Per-service deploy scripts
/etc/nginx/sites-available/deepdrft.com.conf Public nginx vhost
/etc/nginx/sites-available/app.deepdrft.com.conf Manager nginx vhost