Skip to content

Put apps back the way they were found - #11

Merged
AdamXweb merged 2 commits into
AdamXweb:mainfrom
adamXbot:feat/preserve-app-state
Aug 4, 2026
Merged

Put apps back the way they were found#11
AdamXweb merged 2 commits into
AdamXweb:mainfrom
adamXbot:feat/preserve-app-state

Conversation

@adamXbot

@adamXbot adamXbot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #10, rebased on main (0.4.0).

The problem

update and backup stopped 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:

[action] ⇒ Updating apps:
  ● 5 running: authelia, gitea, immich, jellyfin, nextcloud
  ○ 3 stopped: audiobookshelf, larapaper, vaultwarden

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. It doubles as update's existing confirmation, so it isn't an extra question:

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 (recommended)
  2) start them too, so everything ends up running
  3) skip them entirely - no pull, no changes
  n) cancel
Choose [1]:

For cron and scripts, --stopped=keep|start|skip (or STOPPED_POLICY in manage.conf) answers it without prompting, as does -y, which takes the default. --stopped=start is the old behaviour if you want everything swept up. With skip, stopped apps aren't even pulled.

The closing tally notes what was left alone:

[success] - Updated all 8 apps in 4s (3 left stopped).

Scope

  • 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 something is actually stopped.
  • restore is 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-trunc for the whole run, plus a compose ps -q per app, with container liveness resolved by set membership rather than an inspect per 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 docker modelling 8 apps — 5 running, 3 stopped — asserting on the actual stop/up/pull calls made, not just the output:

case containers cycled images pulled
default (keep) the 5 running only all 8
--stopped=start all 8 all 8
--stopped=skip the 5 running only 5
interactive 1/Enter the 5 running only all 8
interactive 2 all 8 all 8
interactive 3 the 5 running only 5
interactive n none none

Interactive cases were driven through a real pty. Also checked: backup archives a stopped app without starting it (STOP:authelia UP:authelia and nothing else) and reports backed up, left stopped; --stopped=banana is rejected; --dry-run reports true state and per-app intent; restart on a stopped app still cycles it; and the #10 failure-mix regression still reports 6 of 10 apps - 2 failed, 2 skipped and exits 1.

Run under Debian dash and Alpine busybox ash, with and without a pty. shellcheck clean, sh -n and dash -n pass.


Second commit: changelog split + release automation

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] - 2026-07-13, 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 everyone, however much has landed on main.

Changelog

Split at the v0.3.0 tag, using git log v0.3.0..main rather than guesswork. 0.3.0 keeps only what it shipped — including its original "up to PARALLEL_PULLS at 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 a v* tag push. Tagging stays the only manual step:

git tag v0.4.0 && git push origin v0.4.0

It then: checks the tag matches VERSION in manage.sh; shellchecks the tagged script (update-self downloads 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 VERSION has 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 passes sh -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 accepts v0.4.0 and rejects v0.5.0 against VERSION="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

adamXbot and others added 2 commits August 4, 2026 21:56
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>
@AdamXweb
AdamXweb merged commit 0134780 into AdamXweb:main Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants