d6cebb337109616dab2d42955f43633c02479073
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d6cebb3371 |
Fix what a review of the whole app found, end to end (0.67.1)
A pass over every feature against a real Docker daemon, then clicking through the UI as admin and read-only user, on a desktop and a phone. Security. /ws/deploy ran compose up for the read-only role; it is admin-only now. A container's inspect view, a stack's compose file (POSTGRES_PASSWORD: hunter2) and the viewer's .env tab no longer show credentials to that role. Git branch and URL values that git would read as options (-x, ext::) are refused. SFTP host keys are checked: pinned on first use or entered by hand (key line or SHA256 fingerprint), a different key is refused before the password is sent. A backup name from the API can no longer reach outside the destination folder. The file browser refuses to move or rename a browse root, as delete did. Docker lifecycle. Closing the deploy console released the stack lock while compose was still running and logged rc=None as success; deploys and updates are now background runs that hold the lock to the end, report the real exit code and can be re-attached to. Every closed log view left a `compose logs -f` behind. A dozen open tabs exhausted the default thread pool and hung the API; event and exec streams have their own threads. Auto-update and the update endpoint ignored failed pulls. A deleted git stack was recreated and started by the next webhook or poll; deleting a stack now drops its git source, auto-update policy and schedule entries. Deleting no longer walks into mounted shares, is refused while a backup runs, and removes the containers even when the compose file is broken. Stack folders compose has to normalise (Nextcloud -> nextcloud) work everywhere and group correctly on the volumes, networks and images pages. Detection. Host port conflicts read the container's own network namespace; /proc/1/net is read instead. Images referenced as docker.io/… were never update-checked; local builds no longer report "needs credentials". NVIDIA GPUs are read from the driver's procfs. The backup dialog's "path mismatch" warning came from mountinfo's filesystem-relative root and fired for any stacks folder on its own disk, ZFS dataset or btrfs subvolume; the host path now comes from Docker's mounts. Backups and destinations. The download ignored the folder/volume selection (axios sent binds[]). Restoring under a new name kept the old one. A report header past nginx's buffer turned a good backup into a 502. SFTP/S3 errors were bare 500s and "Test" did not try to write. A destination used by a schedule could be deleted. Backup file names are in the schedule's or the browser's time zone instead of UTC. Editor and converter. `docker run` conversion produced invalid compose for --network host, named volumes and -dit; --gpus and --mount were dropped; a multi-line command pasted into the one-line field became `image: ' '` (now a textarea, and flattened continuations are read). Validation failed for env_file stacks. The editor overwrote unsaved edits on every container event, lost them on a stray click (now asks), threw when closing the diff, and its helper panel listed the starter service "app" for every existing stack. Monaco is bundled instead of loaded from jsdelivr, so the editor works offline (and monaco-editor 0.57 drops the dompurify advisory; axios and react-router updated). UI. All times were two hours off: offset-less UTC from SQLite was read as local time, and "next run" always said "just now". A token refresh mid-session duplicated deploys, reset terminals and doubled logs. A backend restart (update, self-update) signed everyone out on reload; the page now waits for it. The editor was unusable on a phone, tables and tabs overflowed, dialog text was black on the dark theme, and the file browser opened on an error with the default roots. Read-only users get "Admin only" pages instead of 403s; in-use networks offer no delete; "Scan all" covers StackPilot's own stacks only and runs in the background instead of timing out at 600s. Deployment. nginx passes the port in Host (the OIDC redirect URI lost :5009) and keeps an outer proxy's X-Forwarded-Proto. The frontend waits for a healthy backend (healthcheck probes every 2s while starting). The frontend image builds with npm ci. Secrets are 0644 in a 0700 folder so non-root containers can read them; existing files are fixed at startup. The test suite no longer starts real containers on a host with Docker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fx7gcuy8d6nPd95g43rGHu |
||
|
|
270ddda51d |
Let a stuck backup be stopped instead of stranding the stack (0.67.0)
A backup holds the stack lock for up to twelve hours — a large volume
legitimately takes that long — and stops the stack's containers for a
consistent snapshot. One that died without unwinding therefore left the
stack stopped and refusing every start, stop, restart and deploy with
"Stack 'npmplus' is busy: backup in progress", with nothing to click
until the TTL ran out.
Backups and restores now register themselves in stack_job_service while
they run, with their phase and the helper containers they stream
through. Cancelling sets a flag and removes those containers, which is
what actually ends the long read: the backup then unwinds through its
own finally — the half-written archive goes, the containers it stopped
are started again, the lock is released. Checks sit between two files
and two volumes, never inside one. A restore is not cancellable at all;
it is halfway through writing a stack's data back.
POST /api/stacks/{id}/unlock does that and waits for it. A backup lock
with no job behind it is a leftover, so it is released on one click; a
deploy lock takes an explicit force, because compose keeps running past
its request. GET /api/stacks/{id}/lock and the stack detail response say
what holds the stack and since when, and the stack page turns that into
"Stop backup" / "Release lock".
Startup now clears every lock rather than only expired ones: after a
restart nothing of ours is in flight, the same reasoning the leftover
helper containers already follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8f0a01c4f5 |
Make backup and restore reliable, and keep NFS out of them (0.64.0)
A review of the backup path found several ways a backup was not one, and a restore could destroy data it did not contain. Data loss: - Restore with overwrite rmtree'd the whole stack directory, including folders the backup had left out (over 2 GiB, deselected, an NFS share mounted inside). It now clears only what the backup holds, keeps the excluded paths (recorded in the manifest) and never crosses into another filesystem. - The stack was only stopped when named volumes were selected, so data folders inside the stack directory were archived while in use. Any selected data now stops it. - Retention matched by prefix: pruning "app" deleted "app-db"'s backups. It now matches backup-<id>-(full|config)-<date>.tar.gz exactly. - A restore under a new name wrote into the original's volumes when they had an explicit name, and into its folders outside the stack directory. Both are skipped now; a volume owned by another stack or mounted by a running container is never overwritten. - A truncated archive was only noticed after the old stack was gone. The whole archive is verified first, and the stack directory is unpacked next to the old one and swapped in. Remote storage: - Bind folders on a mounted share (fstab/autofs), bind-type volumes pointing at one and shares mounted inside the stack folder are detected by filesystem type and not selected; a folder du cannot measure in 20s is no longer selected by default either. Reliability: - After a backup exactly the containers that ran are started again with compose start (falling back to the Docker API), instead of up -d, which started services stopped on purpose and applied undeployed edits. A stack that cannot be stopped is not backed up. - Backup and restore hold the stack lock; a scheduled backup that finds the stack busy retries after 10 minutes. Restore stops the stack while replacing its data and brings it back up. - external: true volumes are backed up; anonymous ones are listed, not selected. Restored volumes get their original driver and options. - A backup with uncaptured data or a stack that did not restart is reported as incomplete: in the dialog, and for schedules as a warning with a notification and without rotating older backups out. - Archive writing and restore run off the event loop; downloads delete their temp file; helper containers use the long-timeout client; S3 listing is paginated; a file changing mid-read no longer corrupts the archive. Manifest format 3 records compose_binds/excluded and volume driver/options/external; format 1/2 backups still restore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5347a36eaf |
Back up bind-mount data, not just the compose file (0.40.0)
A stack's real state lives in its bind-mounted config directories, and those
were never captured: the backup only tarred the stack folder as this container
sees it. When STACKS_HOST_DIR differs from the container's STACKS_DIR, compose
resolves ./config against the container path and the daemon creates it at that
path on the *host* — invisible here, so the archive held little more than
compose.yaml and .env.
New services/stack_assets_service.py inventories a stack's data (bind sources
merged from container mounts + the compose file, named volumes) and does all
data I/O through a throwaway helper container, i.e. by host path, so unseen
directories are captured anyway. It also detects the host/container stacks-path
mismatch and reports it.
- manifest v2: full inventory, per-asset capture result, skip reasons (v1 still
restores)
- NFS/CIFS-backed volumes are skipped by default and never wiped on restore
- deselected data inside the stack folder no longer sneaks in via compose/
- volume/bind archives stream through temp files instead of RAM
- restore preserves mode, ownership, mtime and symlinks, and writes bind folders
back to their host paths (rewritten when the stack is renamed)
- backup dialog shows the inventory with sizes and per-item checkboxes; restore
gained a "restore bind folders" toggle
- new GET /api/stacks/{id}/backup/inventory (+ agent + proxy), backup endpoints
take include_binds/binds/volumes, restore takes restore_binds
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
79d82361d8 |
0.32.1: backup/restore fixes (audit findings, all paths live-verified)
- backup_filename() crashed with NameError (bare now()) since 0.8.0 — broke every scheduled backup at the upload step, agent backup download and the central remote-backup/push endpoints. The local manual path worked only because the router had its own copy (now an alias). - restore: the manifest stack_id from an uploaded backup is now slugified too — a crafted '../../...' id could previously escape STACKS_DIR. - create_backup no longer starts a previously-stopped stack (stop/restart only when the stack was actually running). - overwrite-restore wipes the existing volume contents before extracting, so files created since the backup no longer survive underneath it. Verified end-to-end: full/config backup contents (compose, .env, .secrets, bind dirs, extras, volume tars), delete→restore round-trip incl. volume data, rename restore with volume re-prefixing, 409 conflict + overwrite, traversal guard, scheduled run + retention prune + restore-from against real MinIO, and the complete remote-agent cycle (download/push/restore). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5cd55382ed |
Phase 8: back up & restore remote (agent) stacks (0.8.0)
- Agent: GET /agent/stacks/{id}/backup + POST /agent/stacks/restore (reuse
backup_service). backup_service gains backup_basename/backup_filename helpers.
- Main proxy streams agent <-> main <-> destination (creds stay central):
agent_service download_to_file/upload_file; routers/agents.py backup download,
backup/push, restore upload, restore-from.
- Schedules: BackupSchedule.agent_id; schedule_service downloads from the agent
when set; per-host filename prefix isolates retention across hosts.
- Frontend: agents api backup/restore; BackupButton/RestoreButton agent-aware
(Backup on remote stack detail, Restore per host section); schedule form host
selector (local or an online agent) + host shown on schedule rows.
Rough-verified (per request): py_compile, frontend tsc build, image imports
(main 99 / agent 16 routes). Full live agent round-trip to be tested post-deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
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> |