Files
HangmanLab.Server.T1/docs/deployment-log.md

4.2 KiB

Deployment Log

Running log of notable deployments, migrations, and incidents for HangmanLab.Server.T1. Newest entries on top. Keep entries terse and strictly non-sensitive (no secrets, tokens, passwords, or internal IPs).


2026-04-15 — Migrate vps.lab from legacy HangmanLab compose to T1

Goal: replace the old ~/HangmanLab docker-compose deployment on vps.lab with HangmanLab.Server.T1, preserving existing MySQL data, and expose the HarborForge stack under two new domains.

Steps taken

  1. Cloned HangmanLab.Server.T1 to /root/HangmanLab.Server.T1 on vps.lab.
  2. Authored site-local .env (not committed). Set COMPOSE_PROJECT_NAME=hangmanlab so the new project resolves named volumes to hangmanlab_mysql_data and hangmanlab_backend_dump, matching the volumes created by the old deployment. Added HarborForge vars (HF_*) and WIZARD_PORT=18080.
  3. Verified volume resolution with docker compose config before touching anything live.
  4. docker compose pull on the new project to pre-pull all images.
  5. docker compose down in the old project directory — without -v, so named volumes were preserved.
  6. docker compose up -d in the new project directory. All services came up: mysql healthy, hf_db_init exited successfully (created the harborforge database idempotently), hf_backend initially blocked on Config not ready as expected.
  7. Confirmed data preservation: 13 tables still present in the hangmanlab database; harborforge database created fresh with 0 tables.

Nginx

  • Added two new server blocks to the existing site file:
    • hf.hangman-lab.top127.0.0.1:${HF_FRONTEND_PORT}
    • hf-api.hangman-lab.top127.0.0.1:${HF_BACKEND_PORT}
  • nginx -t clean (pre-existing warnings about conflicting server names are unrelated).
  • Certbot --nginx --expand run over HTTP-01 to add the two new domains to the existing Let's Encrypt certificate covering hangman-lab.top and api.hangman-lab.top. Cloudflare proxy was temporarily set to DNS-only on the new subdomains during validation, then re-enabled.
  • Certbot left a backup of the old site file in sites-enabled/ which caused duplicate server_name warnings on reload; moved it out to /root/ and re-reloaded.

Frontend / setup flow

  • The setup wizard page in HarborForge.Frontend was rewritten to ask for the AbstractWizard port at step 1 (via SSH tunnel), test /health, and persist the chosen port in localStorage. No VITE_WIZARD_PORT build-time env var is used anymore.
  • Backend URL is collected at step 3 and written to localStorage as HF_BACKEND_BASE_URL. For this deployment the browser was pointed at https://hf-api.hangman-lab.top.

Incident: PUT /api/v1/config/harborforge.json → 500

  • Symptom: during the "Finish setup" step the wizard returned HTTP 500 on the config PUT.
  • Root cause: the abstract-wizard image runs as nonroot (uid/gid 65532), but Docker creates the wizard_config named volume with root:root ownership on first use, so the container process had no write permission inside /config.
  • Hot fix on vps.lab: chown -R 65532:65532 on the host-side volume path. Setup then completed successfully.
  • Permanent fix (this commit's predecessor 5e601b1): added a wizard_init one-shot service using busybox, running as root, that chowns /config to 65532:65532 every time the project comes up. The wizard service now depends_on: { wizard_init: { condition: service_completed_successfully } }. The fix is idempotent and covers fresh installs on any host, not just vps.lab.

Outcome

  • https://hangman-lab.top and https://api.hangman-lab.top unchanged and still serving the legacy stack from the same MySQL data.
  • https://hf.hangman-lab.top and https://hf-api.hangman-lab.top serving the HarborForge stack from the same compose project.
  • Setup wizard completed end-to-end; admin account created; backend reachable from the frontend.

Follow-ups

  • None blocking. If future deployments target a distroless service that writes to a named volume, use the same *_init sidecar pattern (busybox + chown) to avoid repeating this incident.