Commit Graph
6 Commits
Author SHA1 Message Date
menzeljandClaude Opus 5 60a7ccff93 Add a test suite, a linter and a CI gate in front of the build (0.45.0)
CI / build-and-push (push) Successful in 1m55s
CI / check (push) Successful in 7m40s
The repo had no tests, no lint config, and a CI that went straight from push
to docker push. That is the reason F1 could ship: authorization lives in the
routers, each of 171 routes independently picks require_admin or
get_current_user, and nothing checked the choice was right.

670 tests, no Docker daemon needed. The app is driven through TestClient
without entering it as a context manager, which skips the lifespan — no
background loops, no socket — and conftest points DATA_DIR/STACKS_DIR at a
temp directory before anything is imported.

test_route_authorization.py is the load-bearing one. Rather than 171 implied
decisions it states the policy once — every route requires admin unless it is
listed in USER_READABLE or PUBLIC — and fails on any route that disagrees. A
new route defaults to admin, which is the safe direction; what it catches is a
route written with get_current_user that nobody weighed against "can this
return a credential". Writing the allowlist meant auditing all 53 user-readable
routes, which turned up one more leak: GET /api/templates/{id} returns a
template's env, and "save stack as template" snapshots the stack's real .env
into it. Now admin-only; the listing stays open.

test_agent_authorization.py pins the same invariant on the agent, where the
whole access model is one shared token declared per route and a single
forgotten Depends(verify_token) would hand over the host.

Both were checked by reintroducing the bug: re-opening /api/files/read fails
three tests with actionable messages, dropping a token guard fails two.

test_bundled_templates.py covers the 83 templates — parse, image per service,
.env.example in sync with what compose reads, every bind-mounted file actually
shipped, and no working default password. It found one on its first run:
authentik shipped PG_PASS=change-me and AUTHENTIK_SECRET_KEY=change-me against
a compose that marks both required, so the stack would have come up with a
known password instead of refusing to start. Fixed.

The rest ports the ad-hoc harnesses from 0.44.0 into permanent tests (crypto
round-trip incl. plaintext passthrough and key-loss handling, the browse
sandbox) and covers compose_service's slug/status/file handling and
secret_service's name validation.

ruff is configured as a floor, not a style bar: F, E9 and B only. Import
sorting is deliberately out — it is style, and enabling it would rewrite the
imports of nine files that have nothing else wrong. The 12 findings it did have
are fixed here (unused imports, an unused local, four raise-without-from that
were swallowing exception context).

CI now runs check (ruff, pytest, tsc) and only builds if it passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dk43rmEeRfYi5wsLDbmfyG
2026-08-31 13:16:41 +02:00
menzeljandClaude Fable 5 9119f94536 Clear the stack update pill immediately after a manual/auto update (0.38.4)
The amber image-update indicator is fed from update_service._CACHE, which
only the background loop refreshed — after a per-stack Update/Pull the
stale digests kept the pill on until the next pass. Now the local digests
are reconciled with the cached remote digests right after a successful
pull/update (local backend, agent lifecycle, auto-update pass), and the
frontend invalidates the stack-updates queries after actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:52:31 +00:00
menzeljandClaude Opus 4.8 cd15cdc75e 0.36.0: per-stack image-update indicator on the Stacks overview
Shows an amber "Update" pill next to a stack's status (and highlights the
inline Update button) when any of the stack's images has a newer digest in
the registry. Reuses the existing background image-update check — a new
update_service.stacks_update_summary() reads the cached digests in a single
container sweep (no extra registry calls), exposed as GET /api/stacks/updates
and proxied per agent at GET /api/agents/{id}/stacks/updates. The Stacks page
and each remote-host section poll it every 60s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:52:51 +00:00
menzeljandClaude Opus 4.8 255c8441c6 Phase 22: auto-update (Watchtower-style), local + agent (0.28.0)
Per-stack auto-update policy on the stack Overview tab. When the background
image-update check finds a newer registry digest for one of a stack's images,
the stack is pulled + redeployed (or just flagged, "notify only"). Only running
stacks are auto-redeployed; a stopped stack is skipped, never silently started.

- models/auto_update.py: AutoUpdate(stack_id, agent_id, enabled, redeploy,
  last_run/status/result) + schemas; registered in models/__init__.py.
- update_service: DB-free stack_images/stack_updates helpers (agent reuses
  them); agent GET /agent/stacks/{id}/updates.
- services/auto_update_service.py: run_due/run_policy (local pull+up via
  compose_service, remote via agent_service POST /agent/stacks/{id}/update,
  notify-only with per-transition dedup); lazy-called from
  update_service.background_loop. New stack_auto_updated notify event.
- routers: GET/PUT/run /api/stacks/{id}/auto-update and the
  /api/agents/{id}/stacks/{sid}/auto-update variants (policy stored centrally).
- frontend: api/autoUpdate.ts + AutoUpdatePanel (enable, redeploy|notify-only,
  Check now, last-run status) on StackDetail + RemoteStackDetail; EVENT_LABELS
  gains stack_auto_updated + backup_failed.

Live-verified all four paths (updated / update-available / up-to-date /
skipped) against real compose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:14:50 +00:00
menzeljandClaude Opus 4.8 8d19b09abd Phase 4: backups w/ volumes, notifications, settings & users, audit page (0.4.0)
- Backup/restore: per-stack tar.gz incl. named-volume snapshots (helper
  container), upload restore with rename/overwrite/conflict detection.
- Notifications: ntfy/Discord/Slack/Gotify/generic webhooks, per-event
  subscriptions; wired into the update checker and stack lifecycle.
- Settings page: update-check interval, webhook CRUD + test, user management
  (with last-admin safeguards).
- Audit log page (searchable, paginated).
- Mobile-responsive sidebar/layout.

Multi-host agents and remote backup destinations (SFTP/S3) deferred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:58:05 +00:00
menzeljandClaude Opus 4.8 22d9864436 Phase 3: env masking, image updates, port conflicts, resources, templates (0.3.0)
Backend:
- update_service: registry manifest digest check (Docker Hub/ghcr/lscr/private
  v2 token auth) vs local RepoDigests; in-memory cache + background loop
- port_service: parse compose ports, check /proc/net/tcp[6] + docker bindings
- template_service + bundled templates (jellyfin/vaultwarden/uptime-kuma/
  paperless-ngx/gitea) with {{VAR}} placeholders; custom templates in DB
- compose_edit set_resources (deploy.resources.limits/reservations)
- routers: images, ports, templates, editor/set-resources
- Template model; background update task wired into lifespan

Frontend:
- EnvEditor (table + raw, sensitive masking, quick-insert)
- Images page + UpdateBadge + dashboard 'updates available' banner
- PortConflictDialog pre-deploy check on Deploy
- ResourcePanel (CPU/RAM sliders) as editor Limits tab
- Templates page with per-variable instantiate form

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:49:38 +00:00