v0.3.0 — community readiness: operator surface, data-root, parity, fixes
Full community-readiness release. All Phases 2–6 of the readiness plan land together per the v0.3.0 ADR (atomic Phase 2 + 2.5 constraint; Phases 3–6 within the same release).
Upgrading from v0.2.x? See docs/migration.md. Existing operators on systemd must
mvrepos to/var/lib/ctfd-warboard/repos/(or accept the auto-WARN withmvinstructions) and re-runmake service-install.
Operator surface (Phase 2)
- Makefile at repo root with 17 targets — single entrypoint for install / check / bot / service-* / migrate-data-root.
.env.example+scripts/ctfd-bot.env.exampledocumenting all bot env vars (no real secrets).docs/deployment.md— full VPS setup: dedicated user, FHS layout, env file, systemd install, journald, SSH key, backups, log rotation, secret rotation, mixed bot+CLI access.
Data root + hardened systemd (Phase 2.5, atomic)
packages/core/src/paths.ts— shared resolver:dataRoot(),repoPathFor(),lockPathFor(),assertDataRootWritable(). HonorsCTFD_WARBOARD_HOME; absolute-path validation;mkdir -pmode0700; path-traversal rejection via allowlist regex; boot-time writability check (FATAL + exit 1 before Discord connect).- Bot + CLI share data root via
CTFD_WARBOARD_HOME. BotCONFIG_DIRderived fromdataRoot(). Botctf-start.tsandctf-join.tsuserepoPathFor(ctfName)instead of cwd-basedresolve(ctfName). ConfigManagerlazy-oncemeta.repoPathmigration — rewrites persisted CTFMeta entries whoserepoPathfalls outsidedataRoot(); WARN log with old→new +mvinstruction (emitted only after rename succeeds); idempotent.make migrate-data-root— triggers migration without starting the bot.- Hardened systemd unit (
scripts/ctfd-bot.service) —User=ctfd-warboard,EnvironmentFile=,Environment=CTFD_WARBOARD_HOME=/var/lib/ctfd-warboard,ProtectSystem=strict,ReadWritePaths=/var/lib/ctfd-warboard,NoNewPrivileges=true,PrivateTmp=true,ProtectHome=true. No inline secrets. make service-installcode-level gate — refuses to install the strict unit ifpackages/core/src/paths.tsis absent (filesystem stat).- Cross-process lock unification — bot and CLI both resolve lock paths through
lockPathFor(); concurrent ops on sharedCTFD_WARBOARD_HOMEserialize correctly.
Bot ↔ CLI parity (Phase 3)
downloadChallengeFilesForCTFin@ctfd-warboard/core— shared download orchestration helper.- Bot
/ctf startnow downloads CTFd challenge files (identical layout toctfd init), with Discord interactioneditReplyprogress edits every 10 challenges. Downloads run inside the per-ctfNameFileLock, before the git transaction. - CLI
init.tsnow also uses the shared helper — single source of truth.
Functional fixes (Phase 4)
- CLI
ctfd claimusesresolveChallengeforcategory/challengeresolution (web/sql-injectionnow resolves correctly to categoryweb). - CLI
--versionreads dynamically frompackages/cli/package.json. resolveAuthAndClient(meta, opts?)in@ctfd-warboard/core— shared auth helper. Priority:meta.token→meta.sessionCookie(verified; 401 falls through) → username + password. Bot/ctf start, bothelpers.ts, CLIsolve, CLIinitall migrated.loginWithCredentialsdirect callsites inpackages/botandpackages/cliremoved.- CLI
ctfd solvenow supports all auth types (was token-only). helpers.tserror rewrap narrowed — only emits "No credentials found" when no auth method exists; otherwise re-throws original error withcause(DNS/TLS/SSRF surface correctly).
CI + release docs (Phase 5)
- CI Bun version pinned to
1.3.13in.github/workflows/ci.yml;.tool-versionsadded for local reproducibility. - CI runs
make check(single entrypoint matches local). docs/release.md— release checklist with concrete shell commands.
README (Phase 6)
- New sections: "5-minute local bot smoke test", "Production install on Ubuntu with systemd", "How secrets are stored", "Known limitations", "Troubleshooting". Cross-links to
docs/deployment.md,docs/migration.md,docs/release.md.
Verification
- 239 tests pass / 0 fail across 28 files.
bun run lint,bun run typecheck,bun audit --audit-level=high→ exit 0.systemd-analyze verify scripts/ctfd-bot.service→ exit 0.- Acceptance greps:
resolve(ctfName)\|resolve(repoName)inpackages/bot/src→ empty;loginWithCredentialsinpackages/bot packages/cli→ empty.
Known limitations
- VM smoke test under the strict systemd unit is deferred — operators are encouraged to exercise the hardened unit before mass adoption and append any required additional
ReadWritePaths=entries. - Several NIT/MEDIUM follow-ups from the v0.3.0 code review (memoize
mkdirSync, narrowauth-resolverre-login to also cover 403, ctf-start scaffold-before-lock, etc.) are filed as future work.
Full Changelog: v0.2.1...v0.3.0