Skip to content

Scripts: add a stack update script - #404

Draft
jackspiering wants to merge 4 commits into
mainfrom
scripts/update-stack
Draft

jackspiering wants to merge 4 commits into
mainfrom
scripts/update-stack

Conversation

@jackspiering

@jackspiering jackspiering commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Adds scripts/update-stack.sh. It compares a stack folder that you copied out of the clone ("Run the stack from a copy") with the latest version of the service, and updates compose.yaml and .env without losing your changes. It targets bash and the GNU tools on Linux.

  • Downloads the GitHub archive of main (about 350 KB) with curl or wget, once per run. It needs no clone, and an existing clone is not read or changed.
  • Only compose.yaml and .env are compared and updated. ts/, the data folders, the README, and other files stay as they are.
  • --apply saves the upstream compose.yaml and .env in .scaletail/. The next run merges three versions with git merge-file: your file, that saved copy, and the new upstream file. Each file that changes is first copied to .scaletail/backup-<time>/.
  • A conflict writes markers into the file (after the backup), like git. The next run refuses while markers remain, and docker compose refuses the file too, so nothing runs half-merged.
  • First run, without a saved copy: .env gets the variables it lacks, with their comments. compose.yaml is replaced by the upstream version: the diff is shown first, and your old file stays in the backup.
  • Never prints .env values, only variable names. It prints diffs of compose.yaml.
  • Skips a stack when SERVICE changes upstream, unless --force. Prints the ## Upgrading section of the README when there are updates.
  • Never starts, stops, or restarts containers. After --apply it runs docker compose config --quiet when Docker is available, and prints only whether it passed.
  • Exit status: 0 up to date or updated, 1 updates available, a conflict, or a skipped stack, 2 error.

Also adds scripts/test-update-stack.sh (7 cases on a fake archive served from a temporary folder, no network), the scripts.yml workflow (ShellCheck and the test), and docs in the root README.md and AGENTS.md.

Example output, from an old Gotify copy with an edited image tag and .env (paths shortened):

Upstream: commit adb87c1 from https://codeload.github.com/tailscale-dev/ScaleTail/tar.gz/refs/heads/main

== ~/stacks/gotify (service: gotify)
  compose.yaml  merge    (your changes stay)
  .env          merge    (your changes stay)
                added: COMPOSE_PROFILES, GOTIFY_CLIENT_TOKENS, GOTIFY_URLS, IGOTIFY_IMAGE_URL, SECNTFY_TOKENS
  status: updated
  Upgrading section of the README:
     | Earlier versions of this stack set `GOTIFY_DEFAULTUSER_PASS=admin` in `compose.yaml`. ...
  saved:  the upstream files in .scaletail/ (commit adb87c1)
  backup: .scaletail/backup-20261030-080952/
  compose: docker compose config is valid
  Next:   docker compose pull && docker compose up -d   (in ~/stacks/gotify)

Related Issues

  • None.

Verification

  • docker compose config --quiet from each changed service directory, with dummy values for required variables. N/A: no service changed.
  • The stack started, Tailscale is healthy, and the service is reachable through the Tailnet. N/A: no service changed.
  • The main function of the service works, and its data survives a restart. N/A: no service changed.
  • rumdl check --config .markdownlint.yml . on the whole repository (rumdl 0.2.78, the pinned version), and git diff --check origin/main
  • shellcheck scripts/*.sh (0.10.0): no findings.
  • bash scripts/test-update-stack.sh: all checks pass. Two deliberate breakages (no .env append, a .env value in the output) make it fail.
  • Smoke tests against the real GitHub archive, on copies of Gotify from before Gotify: add optional iGotify container #388 in a scratch folder outside the clone (no containers started):
    • First run: the compose.yaml diff shows the new iGotify service, and .env gets the 5 new variables. --apply replaces compose.yaml with a backup. docker compose config then fails on required variable GOTIFY_DEFAULTUSER_PASS is missing, as the Upgrading section says, and the script reports the failure without quoting .env. A second run is up to date.
    • With a saved copy from the old version (served from a local archive), an edited image tag, and edited .env values: both files merge cleanly (output above), and the values do not appear in the output.
    • An edit to the restart: line next to the new iGotify block conflicts: the markers land in compose.yaml, the next run refuses, and after resolving it is up to date.
    • Two folders named gotify in one run, the first with pending changes and the second up to date: the second reports up to date. --service works for a folder with another name. A folder with compose.yml gets a hint to rename it.
  • CI (Compose files, Scripts, Workflow lint, lint) passes.
  • Not run: macOS (out of scope), and any live stack (no service changed).

Impact on existing users

  • None. The script is new and optional. No service changed.

Checklist

  • I have performed a self-review of my code and followed the templates structure. N/A for the template: no service changed.
  • I did not commit a working auth key, password, token, or other credential.
  • The service README uses the headings of the template, in the same order, and is up to date. N/A: no service changed.
  • I added, removed, or renamed the service in the root README.md, in alphabetical order, or this PR does not change the list of services. This PR does not change the list of services.

Additional Context

  • The second commit replaces the first version (679 lines, base detection from the git history of a full clone, marker commit, --base, --ref, --no-fetch) with a download and a saved copy (about 260 lines). Squash-merge flattens both.
  • git merge-file is used instead of diff3 -m from diffutils, because diff3 -m reports a conflict when both sides made the same change.
  • Adds the title prefix Scripts: to the lists in AGENTS.md and CONTRIBUTING.md.

scripts/update-stack.sh checks stack folders that were copied out of a clone
against the latest version in the repository and updates them with a
three-way merge, so edits to .env and compose.yaml survive. It reads the
upstream files with git only, never prints .env values, makes a backup
before it writes, and never restarts containers.

scripts/test-update-stack.sh tests it against a throwaway repository, and a
new workflow runs ShellCheck and the test on changes to scripts/.
Download the service from GitHub instead of reading a full clone, and
update compose.yaml and .env with a three-way merge against the upstream
files that the last run saved in .scaletail/. This drops the base
detection, the marker commit, --base, --ref and --no-fetch.

- The first run replaces compose.yaml with a backup, and adds missing
  .env variables.
- A conflict writes markers into the file, after a backup, and the next
  run refuses until they are resolved.
- Target bash and GNU tools on Linux, not bash 3.2 and BSD.
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.

1 participant