Skip to content

feat: wave 5 — config transparency + backup/restore - #24

Merged
wra-sol merged 1 commit into
mainfrom
feat/wave-5-config-resilience
Jun 29, 2026
Merged

feat: wave 5 — config transparency + backup/restore#24
wra-sol merged 1 commit into
mainfrom
feat/wave-5-config-resilience

Conversation

@wra-sol

@wra-sol wra-sol commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Wave 5 (final wave) of the opencode manager improvement plan. Two slices, one PR.

5.1 — /manage/config read view (closes #17)

New endpoint GET /manage/config (session-auth required) showing:

  • Rendered opencode.json — pretty-printed, with {env:VAR} placeholders intact (no raw API keys leaked)
  • Masked .setup.env — each line shown with API keys masked (key[:4]…key[-4:]), non-secret env vars shown in full

Fixed README: removed the "edit opencode.json directly" claim (it's regenerated on every boot and every reconfigure by generate_config.py), replaced with /setup and /manage/config references. Added backup/restore and config viewer docs.

5.2 — /manage/backup + /manage/restore (closes #18)

  • GET /manage/backup — downloads a gzipped tarball containing .setup.env, opencode.json, users.json, sessions.jsonl, audit.jsonl. Requires session auth.
  • POST /manage/restore — accepts a tarball upload, validates structure (must contain .setup.env), extracts only whitelisted files (no path traversal), triggers apply_settings + restart.
  • Both endpoints record audit log entries.

Testing — Step by Step

All steps below were executed locally on this branch (Python 3.10.11, macOS). Verified results are quoted under each step.

Prerequisites

  • Python 3.10+ (verified on 3.10.11 and 3.12)
  • pytest installed (pip install pytest)
  • ruff installed (pip install ruff) — for lint/format checks
  • No external services needed — all tests use a fake opencode binary spun up in-process

Step 1 — Lint and format check

ruff check .
ruff format --check .

Expected: Both pass with zero errors.

Verified:

$ ruff check .
All checks passed!
$ ruff format --check .
11 files already formatted

Step 2 — Run the full test suite

pytest tests/ -q

Expected: 140 passed with zero failures, zero errors, zero skipped.

Verified:

$ pytest tests/ -q
........................................................................ [ 51%]
....................................................................     [100%]
140 passed in 80.70s (0:01:20)

This runs all 5 test files:

  • tests/test_manager.py — 87 integration tests (manager lifecycle, auth, proxy, dashboard, all 5 waves)
  • tests/test_wizard.py — 19 unit tests (helpers, rate limiting, env var parsing, volume detection)
  • tests/test_generate_config.py — tests for generate_config.py (opencode.json generation)
  • tests/test_seed_agents.py — tests for seed_agents.py (skill seeding)
  • tests/test_toolkit.py — tests for the bundled MCP toolkit

Step 3 — Run Wave 5 tests specifically

pytest tests/test_manager.py -k "manage_config or manage_backup or manage_restore" -v

Expected: 6 tests pass:

Test What it verifies
test_manage_config_shows_rendered_json /manage/config returns 200, shows opencode.json, no raw API keys in body
test_manage_config_shows_masked_setup_env /manage/config shows .setup.env with OPENCODE_SERVER_PASSWORD masked (plaintext not present)
test_manage_config_requires_auth /manage/config without cookie → 302 redirect to login
test_manage_backup_downloads_tarball /manage/backup returns 200, Content-Type: application/gzip, valid tarball containing .setup.env
test_manage_backup_requires_auth /manage/backup without cookie → 302 redirect to login
test_manage_restore_rejects_bad_tarball POST /manage/restore with garbage body → 400/403 (not 200)

Verified:

$ pytest tests/test_manager.py -k "manage_config or manage_backup or manage_restore" -v
collected 87 items / 81 deselected / 6 selected

tests/test_manager.py::test_manage_config_shows_rendered_json PASSED     [ 16%]
tests/test_manager.py::test_manage_config_shows_masked_setup_env PASSED  [ 33%]
tests/test_manager.py::test_manage_config_requires_auth PASSED           [ 50%]
tests/test_manager.py::test_manage_backup_downloads_tarball PASSED       [ 66%]
tests/test_manager.py::test_manage_backup_requires_auth PASSED           [ 83%]
tests/test_manager.py::test_manage_restore_rejects_bad_tarball PASSED    [100%]

6 passed, 81 deselected in 6.27s

Note: the broader filter -k "config or backup or restore" matches 18 tests (the substring config also appears in earlier-wave test names like test_configured_* / test_reconfigure_*). Use the manage_*-prefixed filter above to isolate exactly the 6 new Wave 5 tests.

Step 4 — Run all Wave 1–5 tests (full manager integration suite)

pytest tests/test_manager.py -v

Expected: 87 tests pass. Key categories:

  • Wave 1 (attach + security): Basic auth at edge, auth_token query param, security headers, CSRF tokens, rate limiter + login lockout
  • Wave 2 (observability): Crash/uptime telemetry, SSE log tail, manager events in ring, /metrics Prometheus endpoint
  • Wave 3 (reconfigure safety): Merge-based .setup.env writes, no surprise password regen, test-before-apply, graceful drain + 503 Retry-After
  • Wave 4 (team features): Per-user accounts, session store + revocation, audit log, bootstrap token
  • Wave 5 (this PR): Config view, backup/restore

Verified:

$ pytest tests/test_manager.py -v
...
======================== 87 passed in 81.50s (0:01:21) ========================

Step 5 — Run the toolkit MCP selftest

python3 mcps/_selftest.py

Expected: All 90 stdio test cases pass over the MCP handshake.

Verified:

$ python3 mcps/_selftest.py
...
  PASS  color: random hex
90 passed, 0 failed

Step 6 — Docker smoke test (optional, matches CI)

docker build --tag opencode-railway:ci .
docker run --rm -d --name test -p 4096:4096 opencode-railway:ci
sleep 5
curl -fsS http://localhost:4096/health
docker exec -u opencode test whoami | grep -q opencode
docker stop test

Expected: Health endpoint returns {"healthy": true}, container runs as opencode user (not root).

Not run locally (Docker daemon not available in this environment). The health endpoint is served at both /health and /global/health and returns {"healthy": true} while unconfigured or while the child is up/starting (see wizard.py:2004). The HEALTHCHECK in Dockerfile:62 uses /global/health.

Step 7 — Manual testing after deploy (optional)

Once deployed to Railway, verify these endpoints end-to-end:

  1. Login — open https://<your-app>.up.railway.app/manage/login, enter password → redirects to /manage
  2. Config view — navigate to /manage/config → see rendered opencode.json (with {env:ANTHROPIC_API_KEY} placeholders, not raw keys) and masked .setup.env
  3. No raw secrets — view page source of /manage/config, confirm no API keys or passwords appear in plaintext
  4. Backup downloadcurl -b cookie -o backup.tar.gz https://<your-app>.up.railway.app/manage/backup → valid gzipped tarball
  5. Backup contentstar tzf backup.tar.gz → should list .setup.env and opencode.json (and users.json/sessions.jsonl/audit.jsonl if they exist)
  6. Restorecurl -b cookie -X POST -F "file=@backup.tar.gz" https://<your-app>.up.railway.app/manage/restore → 302 redirect to /manage, child restarts
  7. Audit log — check /manage/audit → entries for backup_download and restore_upload should appear
  8. Unauthenticated accesscurl https://<your-app>.up.railway.app/manage/config → 302 to login (no config leaked)

All waves complete

This is the final wave. All 17 issues (#2-#18) are now addressed across 5 PRs:

Wave PR Status Issues
1 #19 merged #2-#5 (Basic auth, security headers, CSRF, rate limiter)
2 #21 merged #6-#8 (telemetry, SSE logs, /metrics)
3 #22 merged #9-#12 (merge writes, password safety, test-before-apply, drain)
4 #23 merged #13-#16 (users, sessions, audit, bootstrap token)
5 this PR open #17-#18 (config view, backup/restore)

Wave 5.1 — /manage/config read view (closes #17)
- GET /manage/config shows rendered opencode.json (with {env:VAR}
  placeholders intact — no raw API keys leaked)
- Shows masked .setup.env (API keys masked as key[:4]…key[-4:])
- Read-only, session-auth required
- Fixed README: removed 'edit opencode.json directly' claim (it's
  regenerated on every boot/reconfigure), replaced with /setup and
  /manage/config references
- Added backup/restore and config viewer docs to README

Wave 5.2 — /manage/backup + /manage/restore (closes #18)
- GET /manage/backup: downloads a gzipped tarball containing .setup.env,
  opencode.json, users.json, sessions.jsonl, audit.jsonl
- POST /manage/restore: accepts a tarball upload, validates structure
  (must contain .setup.env), extracts only known files (no path
  traversal), triggers apply_settings + restart
- Both require session auth; audit entries recorded
- Only whitelisted files are extracted from the tarball (security)

Tests: 6 new test cases. 140 total, all passing. Lint + format clean.
@wra-sol wra-sol added documentation Improvements or additions to documentation enhancement New feature or request wave-5 Wave 5: config transparency + resilience labels Jun 29, 2026
@wra-sol
wra-sol merged commit 7492913 into main Jun 29, 2026
8 checks passed
@wra-sol
wra-sol deleted the feat/wave-5-config-resilience branch June 29, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request wave-5 Wave 5: config transparency + resilience

Projects

None yet

Development

Successfully merging this pull request may close these issues.

resilience: /manage/backup + /manage/restore config: /manage/config read view + fix README opencode.json claim

1 participant