Put apps back the way they were found - #11
Merged
Conversation
update and backup stopped and started every app unconditionally, so an
app you had deliberately shut down came back up as a side effect of
updating - and there was no way to see, before committing to a run,
what was actually running.
Both commands now scan the current state first (one docker ps plus a
compose ps per app), print the split, and put each app back the way
they found it. A stopped app still gets its new image pulled - compose
recreates a stopped container on the new image at its next start - but
nothing starts it. backup archives a stopped app where it lies instead
of starting it to do so.
On a terminal one prompt settles the rest, doubling as update's
existing confirmation:
● 5 running: authelia, gitea, immich, jellyfin, nextcloud
○ 3 stopped: audiobookshelf, larapaper, vaultwarden
Update 5 running app(s). The other 3 are stopped - what should
happen to them?
1) leave them stopped, but pull so they're current next start
2) start them too, so everything ends up running
3) skip them entirely - no pull, no changes
n) cancel
--stopped=keep|start|skip (or STOPPED_POLICY in manage.conf) answers it
for cron, as does -y, which takes the default. --stopped=start is the
old sweep-everything-up behaviour. With skip, stopped apps aren't even
pulled. The closing tally notes how many were left stopped.
stop, start and restart are untouched: those are explicit instructions
rather than a sweep, so restart on a stopped app still starts it.
backup gains no confirmation prompt it didn't have before - the
stopped-apps question is the only thing it asks, and only when some
app is actually stopped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing automated the changelog or the release, so main had drifted: manage.sh was bumped to VERSION="0.4.0" while the CHANGELOG's top heading was still 0.3.0, and everything merged since the July 0.3.0 release sat under that heading. With no v0.4.0 tag, update-self also still reports "already up to date" for every user. Split the changelog at the v0.3.0 tag. 0.3.0 keeps only what it shipped - including its original "up to PARALLEL_PULLS at a time" pull entry, which had been edited in place to describe work that came later - and a 0.4.0 section collects what landed after it: the live pull progress, the sliding-window pulls, carrying on past a failed app, the failure hints and the state-preserving update. Add a release workflow so tagging is the only manual step: on a v* tag it checks the tag matches VERSION, shellchecks the script that update-self will hand people, extracts that version's CHANGELOG section and publishes the release with it as the notes. The lint workflow now also fails a PR when VERSION has no matching CHANGELOG heading, which is the drift that started this. 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.
Follow-up to #10, rebased on
main(0.4.0).The problem
updateandbackupstopped and started every app unconditionally. An app you had deliberately shut down came back up as a side effect of updating — and there was no way to see, before committing to a run, what was actually running in the first place.What changed
Both commands now scan the current state first, print the split, and put each app back the way they found it:
A stopped app still gets its new image pulled — compose recreates a stopped container on the new image at its next start — but nothing starts it.
backuparchives a stopped app where it lies instead of starting it to do so.On a terminal, one prompt settles the rest. It doubles as
update's existing confirmation, so it isn't an extra question:For cron and scripts,
--stopped=keep|start|skip(orSTOPPED_POLICYinmanage.conf) answers it without prompting, as does-y, which takes the default.--stopped=startis the old behaviour if you want everything swept up. Withskip, stopped apps aren't even pulled.The closing tally notes what was left alone:
Scope
stop,startandrestartare untouched — those are explicit instructions rather than a sweep, sorestarton a stopped app still starts it.backupgains no confirmation prompt it didn't have before. The stopped-apps question is the only thing it asks, and only when something is actually stopped.restoreis unchanged; it's a deliberate single-app operation whose whole point is getting the app running on restored data.Cost of the scan
One
docker ps -q --no-truncfor the whole run, plus acompose ps -qper app, with container liveness resolved by set membership rather than aninspectper container. I used the authoritative compose lookup rather than matching compose project labels, because a wrong reading here decides whether an app gets started.Verification
Against a stub
dockermodelling 8 apps — 5 running, 3 stopped — asserting on the actualstop/up/pullcalls made, not just the output:keep)--stopped=start--stopped=skip1/Enter23nInteractive cases were driven through a real pty. Also checked:
backuparchives a stopped app without starting it (STOP:authelia UP:autheliaand nothing else) and reportsbacked up, left stopped;--stopped=bananais rejected;--dry-runreports true state and per-app intent;restarton a stopped app still cycles it; and the #10 failure-mix regression still reports6 of 10 apps - 2 failed, 2 skippedand exits 1.Run under Debian
dashand Alpine busyboxash, with and without a pty.shellcheckclean,sh -nanddash -npass.Second commit: changelog split + release automation
Nothing automated the changelog or the release, so
mainhad drifted:manage.shwas bumped toVERSION="0.4.0"while the CHANGELOG's top heading was still## [0.3.0] - 2026-07-13, and everything merged since the July 0.3.0 release sat under that heading. With nov0.4.0tag,update-selfalso still reports "already up to date" for everyone, however much has landed on main.Changelog
Split at the
v0.3.0tag, usinggit log v0.3.0..mainrather than guesswork. 0.3.0 keeps only what it shipped — including its original "up toPARALLEL_PULLSat a time" pull entry, which I'd edited in place in #9 to describe work that came later. That was wrong of me: 0.3.0's release notes are published and don't include it.A new 0.4.0 section collects what actually landed after the release: live pull progress, sliding-window pulls, carrying on past a failed app, the failure hints, and the state-preserving update. Dated today — adjust if you tag later; the release workflow matches on version, not date.
Release workflow
.github/workflows/release.yml, on av*tag push. Tagging stays the only manual step:git tag v0.4.0 && git push origin v0.4.0It then: checks the tag matches
VERSIONinmanage.sh; shellchecks the tagged script (update-selfdownloads and runs this exact file, so a broken one must never reach a release); extracts that version's CHANGELOG section; and publishes the release with it as the notes.The Lint workflow also gains a step failing any PR where
VERSIONhas no matching CHANGELOG heading — the drift that started this. It ran green on this PR.Checked
Both workflows parse as YAML and every
run:block passessh -n. The extraction was run against the real file: 49 lines for 0.4.0, correctly bounded at the## [0.3.0]heading; 0.3.0 still bounded at## [0.1.0]; a nonexistent version yields 0 bytes so the workflow fails rather than publishing empty notes; and the new intro paragraph doesn't leak into the extracted notes. The tag guard acceptsv0.4.0and rejectsv0.5.0againstVERSION="0.4.0".I couldn't exercise the publish step itself without pushing a tag to your repo, which is yours to do.
🤖 Generated with Claude Code