Commit Graph
9 Commits
Author SHA1 Message Date
menzeljandClaude Opus 5 41a21b5a25 Make tokens revocable and move the refresh token out of localStorage (0.46.0)
CI / check (push) Successful in 7m7s
CI / build-and-push (push) Successful in 1m44s
F5 — A token was valid until it expired, full stop. Resetting a compromised
account's password changed nothing for whoever held its tokens (up to 30 days
for a refresh token), demoting or disabling an account only took effect once
the same clock ran out, and logout was purely client-side.

Every account now has a token_version, every token is minted carrying it, and
every request compares the two. Bumping it is the revoke switch, pulled on the
three changes that alter what an account may do: password, role, active flag.
"Sign out everywhere" in the user menu bumps your own. Plain "Sign out" only
drops the cookie, because signing out on your phone should not kill your
desktop session.

The refresh token left localStorage for an httpOnly cookie (SameSite=Lax,
scoped to /api/auth), and the access token is now held in memory only. A
successful XSS can still act inside the open page but can no longer walk off
with 30 days of access. The cookie is marked Secure only when the request
arrived over HTTPS — request.url.scheme is trustworthy since the F4 fix — so a
plain-HTTP homelab keeps working. Any refresh token an older build left in
localStorage is deleted on first load. Scripted clients that cannot hold a
cookie can still ask for it in the body with ?in_body=true.

F9 comes with it, as predicted: the WebSocket helpers read the role off the
live user instead of the token's claim. /ws/exec is root-equivalent on the
host, and a token minted while the account was an admin stayed syntactically
valid after a demotion.

The sharp edge was the migration, not the feature. _ensure_model_columns emits
ADD COLUMN without a DEFAULT, so SQLite would have filled token_version with
NULL on every existing install, every version check would have failed against
it, and the upgrade would have locked out every user everywhere. The helper now
renders NOT NULL DEFAULT <literal> for scalar defaults; test_schema_migration
builds a genuinely old-shaped user table and asserts the backfill. The version
comparison also tolerates NULL as 1, so a database migrated by some other route
still works.

Writing that test surfaced an undocumented precondition: _ensure_model_columns
does nothing unless `models` has been imported, since SQLModel.metadata is
empty until then. It holds in production because init_db imports first; now it
says so.

The authorization matrix did its job — adding two auth routes failed the suite
until both were classified, which is exactly the review moment it exists for.

30 new tests (698 total). Upgrading signs everyone out once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dk43rmEeRfYi5wsLDbmfyG
2026-08-31 13:31:00 +02:00
menzeljandClaude Opus 5 1e8d4248fd Stream update progress into a bar on the stack's row (0.42.0)
CI / build-and-push (push) Successful in 1m55s
Update ran as a blocking POST with nothing to show but a spinner, so the
status added in 86c67df could only sit above the table as a banner.

Adds /ws/update/{stack_id}, streaming `compose pull` then `up -d` with
--progress json, and feeds it through the existing DeployTracker — the
same weighting the deploy console uses. The result renders as a progress
bar inside the stack's own row: percentage, phase label, and layer/byte
detail. Non-streaming actions (start/stop/restart/pull/down) reuse the
bar in its indeterminate form, so every row action looks consistent.

compose_service gains _stream_phase, shared by stream_up and the new
stream_update; a failed pull short-circuits before `up`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016pMmFFkdfxkoYjcEcpZTa5
2026-08-31 00:33:34 +02:00
menzeljandClaude Opus 4.8 be3568274f Phase 21: container terminal (web exec), local + agent (0.27.0)
Interactive shell into a compose-managed container over WebSocket + xterm.js,
opened from the container card on the stack Overview tab. Admin-only (non-admin
handshake rejected with 4403); only containers with the compose project label
are reachable.

- backend services/exec_service.py: create/start/resize exec + a shared
  bidirectional pump_exec (recv/sendall on sock._sock, executor thread,
  resize control frames, exit-code frame).
- routers/ws.py: _authorize_admin + /ws/exec/{container_id} and the
  /ws/agent-exec/{agent_id}/{container_id} proxy (forwards BOTH directions).
- agent_app.py: /agent/ws/exec/{container_id}.
- frontend: @xterm/xterm + @xterm/addon-fit; ContainerTerminal modal (shell
  picker, fit/resize, exit/error handling) + a Terminal button on ContainerCard.

Live-verified (TestClient): local happy/exit/guard/4403/4401, agent happy/4401,
proxy bidirectional round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 12:53:21 +00:00
menzeljandClaude Opus 4.8 d46a6c3576 Remote deploy console: stream agent compose up to the browser (0.23.0)
Extends the live deploy console to remote/agent stacks. New agent WS endpoint
`/agent/ws/deploy/{stack_id}` runs `compose up -d` and streams its output; the
central app proxies it through `/ws/agent-deploy/{agent_id}/{stack_id}` (same
pattern + token URL-encoding as the agent-logs proxy) and records an
`agent.stack.start` audit entry. The editor's remote Deploy path now opens the
DeployConsole (agentId) instead of the blocking `agentsApi.action(start)`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:44:31 +00:00
menzeljandClaude Opus 4.8 7592085ce9 Live deploy console: stream compose up output to the browser (0.22.0)
Deploying a local stack from the editor now opens a console modal that streams
the `docker compose up -d` output (image pulls, container creation) live over a
new `/ws/deploy/{stack_id}` WebSocket, replacing the blind "Deploying…" spinner.
The compose subprocess keeps running server-side if the modal is closed early;
the same audit entry + start/error notification as the REST start path is recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:35:33 +00:00
menzeljandClaude Opus 4.8 4bc0fb8901 Fix remote-stack log streaming: URL-encode agent token in WS proxy (0.13.2)
The agent-logs WebSocket proxy injected the agent token raw into the upstream
query string (?token=<token>). Tokens containing base64/url-special characters
(+, /, =) were then mangled by the query parser on the agent side (e.g. "+"
decoded to a space), so the agent rejected the stream with close code 4401 even
though the same token works for the HTTP API (where it travels in the
Authorization header). URL-encode the token with urllib.parse.quote so it
round-trips intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:21:28 +00:00
menzeljandClaude Opus 4.8 5ebd615651 Remote stack logs: surface proxy/agent errors instead of silent disconnect (0.13.1)
Remote-stack log streaming showed only "disconnected, 0 lines" whenever the
agent log proxy failed, because the LogViewer ignored type:"error" messages and
the proxy swallowed connection errors.

- ws.py: the agent-logs proxy now reports a clear, logged reason on failure —
  distinguishes "cannot reach agent <url>" from a handshake rejection (HTTP 404
  hints the agent is outdated and lacks live-log support) and forwards abnormal
  upstream close codes (e.g. 4401 bad agent token).
- LogViewer: renders type:"error" messages (red) and surfaces a 4401 close as an
  authorization error, instead of silently showing "Waiting for log output…".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:14:58 +00:00
menzeljandClaude Opus 4.8 1931500c24 Phase 11: remote UX & network attach (0.11.0)
- Live remote-stack logs over a WebSocket proxied through the central app to
  the agent (/ws/agent-logs/{agent}/{stack}); agent gains a WS log endpoint.
- Deploy to a remote host from the UI: host selector in the New Stack editor
  and template dialog; templates instantiate onto an agent via the proxy.
- Network attach/detach: expandable inspect view per network with
  connect/disconnect + container picker; GET /{id}/containers, POST connect/disconnect.
- Remove dead pages/Placeholder.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 07:49:20 +00:00
menzeljandClaude Opus 4.8 f732cb080b Initial commit: StackPilot Phase 1 (Core)
Self-hosted Docker Compose manager.
- Backend: FastAPI + docker-py + SQLite (JWT auth, file-first stacks,
  lifecycle, live status, WebSocket logs, docker-run converter, audit log)
- Frontend: React + Vite + Tailwind (login/setup, dashboard, stacks,
  stack detail, Monaco editor, dark/light theme)
- Deployment: docker-compose.yml, Dockerfiles, nginx reverse proxy

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