Conversation
The About tab could only detect a new release and link to it; applying the update meant SSH and scripts/update.sh. Wire that script to the UI: - panel_update_service launches update.sh via systemd-run in its own transient unit — a plain subprocess child would be killed when the updater restarts the panel's own systemd unit. Status is stateless (transient-unit state + the updater's own update-*.log) so it survives the mid-update backend restart. - Capability gates keep the button honest: root systemd installs only. Docker installs get the docker compose pull instructions; dev servers and hosts without systemd-run get a reason instead of a button. - GET/POST /api/v1/system/update (admin_required, confirm-gated, audited). API surface doc regenerated. - About tab gains Update Now with inline confirm, live log tail while the update runs, a restarting phase that tolerates the panel being briefly unreachable, and success/rollback/timeout outcomes. New aboutTab locale keys in en + es. The heavy lifting (blue/green switch, migrations, health check, automatic rollback) stays in update.sh, which is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The self-update feature commit added the 10 new app.aboutTab keys to en and es only; every other locale fell back to English in an otherwise fully translated About tab. Add ar, bn, de, fr, id, it, ko, pl, pt, ru, th, tr, vi, zh-Hans and zh-Hant, matching each file's existing register (Sie/vous/formal). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`vite preview` host-checks every request against `preview.allowedHosts`
and blocks any domain the repo's vite config doesn't list -- the deployed
container answered localhost probes fine and refused every request the
panel proxy forwarded ("Blocked request. This host ... is not allowed").
Preview is a dev convenience, not a production server, per vite's own docs.
The vite default start command is now `serve -s dist -l tcp://0.0.0.0:4173`
(SPA fallback, no host check); the generator preinstalls serve globally
when the start command needs it, since the repo never declares it. A
repo-authored start script still wins and gets no serve install.
The @docker_builds leg now sends a foreign `Host:` header to every booted
container -- the localhost-only probe was exactly the blind spot that let
this class through.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updating ServerKit used to mean SSHing into the box and running a script — which is fine, right up until you're on a phone and the "Update available" badge is just taunting you. This adds a real Update Now button to the About tab: it launches the existing
scripts/update.sh(blue/green slot switch, migrations, health check, automatic rollback — none of that is reimplemented here), streams the updater's own log into the panel, and rides out the restart it triggers. The one genuinely tricky bit is that the updater restarts the systemd unit the panel is running in, so a plain child process would get killed at the exact point of no return;systemd-runputs the script in its own transient unit that survives. Status is deliberately stateless — an update destroys in-memory state by definition, so "is it running" is a systemctl query and "how's it going" is the tail of the log file the script already writes, both readable by the new process after the restart. Riding along is a buildpack fix in the same spirit of things that only break in production: auto-detected vite apps were started withvite preview, which host-checks every request againstpreview.allowedHostsand therefore answered localhost health probes happily while blocking every real visitor arriving through the panel's proxy.Highlights
Technical changes
app/services/panel_update_service.py:get_capability()reportssystemd/docker/unsupportedwith a human reason, gating on Linux,/.dockerenv, root, the presence ofscripts/update.sh, andsystemd-run+systemctlon PATH.start_update()launches the updater viasystemd-run --unit serverkit-panel-update --collect, so the script runs in its own cgroup and survives thesystemctlrestart it performs on the panel's own unit. A fixed unit name makes concurrency a singlesystemctl is-activequery and forces two concurrent starts to collide inside systemd rather than race through the updater.systemctl reset-failedprecedes the launch — a previously failed run leaves the fixed-name unit infailedand systemd would otherwise refuse to reuse the name.latest_log()reads the newest/var/log/serverkit/update-*.log, seeks to the last 8 KiB rather than reading the whole file, strips ANSI escapes, and derives a best-effortoutcome(success/rolled_back) from the markersscripts/update.shalready prints._is_windows()is a module function rather than an inlineos.namecheck specifically so tests can fake the platform without patchingos(a leakedos.namepatch takes down the whole pytest run on Windows).GET /system/updateandPOST /system/updateinapp/api/system.py, both@admin_required. The POST requires{"confirm": true}in the body and returns 202; it writes apanel.updateaudit entry recording the transient unit and the version being upgraded from. Both routes are recorded indocs/API_SURFACE.md.ConflictErrorfor an update already in flight,ValidationErrorfor an install that can't self-update or a failed launch.AboutTab.jsxgains a phase machine (confirm→starting→running/restarting→done/failed/timeout) polling every 3s for up to 15 minutes; a request failure maps torestartingrather than an error, since the panel going unreachable is the expected middle of the operation. Success is detected by the reported version differing from the one at start, not by a completion flag.cancelledRefset on unmount stops the poll loop from writing state after the tab is gone.getPanelUpdateStatus()/startPanelUpdate()added toservices/api/system.js;.update-confirm,.update-capability-note,.update-progressand the log<pre>styled in_settings.scssusing existing design tokens.white-space: pre-wrapinside a scroll-capped box.BuildpackService.detect()now defaults vite projects toserve -s dist -l tcp://0.0.0.0:4173instead ofvite preview --host, because preview validates the incomingHostagainstpreview.allowedHostsand rejects any domain the repo's config doesn't list. A repo-authoredstartscript still wins.generate_dockerfile()emitsRUN npm install -g serveonly when the resolved start command's first token isserve— the buildpack chose it, so the repo won't have declared it, but a repo supplying its own start command shouldn't pay for the install._deploy_and_probe()in the docker-builds leg now re-requests every booted container withHost: app.example.comand fails on a non-200 or a body containing "not allowed", pinning the class of bug rather than just this instance: a container that answers127.0.0.1but refuses the proxied domain is broken for every real visitor.