# Build order — one commit per task Dependency-ordered. Each numbered item = one git commit. Operational steps (prep, acceptance test) need internet/hardware and are NOT commits — marked `[ops]`. > Commit message convention: Conventional Commits, scope = service. Footer: > `Co-Authored-By: Claude Opus 4.8 (1M context) `. ## Phase 0 — baseline 0. `chore: baseline scaffolding` — CLAUDE.md, plan/, docker-compose.yml, runtime/, .gitignore, .env.example. ## Phase 1 — config foundation 1. `feat(env): add BASE_DOMAIN + RCON_PASSWORD + CADDY_* to .env.example` 2. `feat(tooling): add render-config.sh (envsubst BASE_DOMAIN, halt on unset)` ## Phase 2 — ingress (caddy internal router) 3. `feat(caddy): add conf.d snippets (00-core auth+pack, 10-static apex+launcher)` 4. `feat(compose): add caddy service (127.0.0.1:CADDY_HTTP_PORT) + network aliases` ## Phase 3 — auth 5. `feat(drasl): add config.toml.tmpl (password login, SignPublicKeys=false)` 6. `refactor(compose): drasl internal-only behind caddy; authlib URL → auth.` ## Phase 4 — modpack 7. `feat(pack): packwiz init + initial mod curation` 8. `feat(tooling): add render-pack.sh + add-custom-mod.sh` 9. `refactor(compose): minecraft via PACKWIZ_URL` ## Phase 5 — landing 10. `feat(landing): Astro+TS onboarding site → www/` 11. `chore(landing): wire logo + .gitignore for www/` 11b. `feat(landing): /register page → Drasl API (B1) + REGISTRATION_MODE flag` (CORS/RateLimit/RegistrationNewPlayer in drasl config.toml.tmpl; see `16-landing-registration.md`). ## Phase 6 — launcher distribution 12. `feat(tooling): add fetch-launcher.sh (all platforms + stable symlinks)` ## Phase 7 — extra services 13. `feat(avatar): add nmsr service (Drasl-backed) + 40-avatar.caddy` 14. `feat(status): add uptime-kuma service + 50-status.caddy` 15. `feat(distribution): add 30-distribution.caddy (static, another repo)` ## Phase 8 — backups 16. `feat(compose): add mc-backup service (RCON, 6h)` ## Phase 9 — production ingress / TLS 17. `feat(nginx): add ulicraft-caddy.conf.tmpl + render-nginx.sh` 18. `feat(tls): add issue-letsencrypt.sh (OVH DNS-01) + LE env block` ## Phase 10 — operational (no commits) - [ops] Prep online (once): `tooling/render-config.sh`, build landing (`cd landing && set -a && . ../.env && set +a && pnpm run build` — sources `.env` so `BASE_DOMAIN` + `REGISTRATION_MODE` bake into the static output), `tooling/fetch-launcher.sh`, fetch authlib-injector into `runtime/`. - [ops] TLS: `tooling/issue-letsencrypt.sh`, then `tooling/render-nginx.sh --install`. - [ops] Bring up: `docker compose up -d --build`. - [ops] Acceptance: install Fjord, add drasl account, import pack, join. - [ops] If green: tag `v1-ready`. ## Notes - Bring-up is a single command — no run modes, no override compose files. - Each commit should leave the tree in a non-broken state (compose still parses).