Nightly update pulls for coolify/immich kept hitting transient ghcr.io
toomanyrequests errors and getting skipped, because the timer fired at
exactly 02:00:00 Europe/Amsterdam (the same instant countless other
hosts' cron/timer jobs also hit registries) and the retry loop only
spanned ~15s (3 attempts, 5/10/15s backoff) — not enough to ride out
the burst.
- Widen pull retries to 5 attempts with 20/40/60/80s backoff (~3+ min).
- Add RandomizedDelaySec=1800 to the update timer (and its install
template) so this host's pulls no longer land on the exact
top-of-hour thundering herd.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmtksEEYSBZqUacw3qF1A9
Two opposite failure modes in `update run`:
- pulsenode was reported "updated" every night though its image never
changed: the pre-pull image-ID snapshot came back empty (the awk
fallback used \s, which mawk silently ignores), and empty != populated
was read as "changed".
- coolify was never recreated despite a new image: its nightly pull kept
hitting a transient `toomanyrequests`, and a failed pull only warned
before falling through to the comparison, which then saw no change.
Changes:
- _stack_image_ids: POSIX [[:space:]] instead of \s; return non-zero when
images can't be resolved so callers can tell "unknown" from "unchanged".
- Retry `docker compose pull` 3x with backoff; if it still fails, add the
stack to a new `skipped` list and skip the recreate instead of treating
it as up to date.
- Detect changes by comparing running containers' image IDs against the
post-pull resolved config, so a transient empty read can't be mistaken
for "everything changed".
- Notify with an amber "finished with warnings" summary listing skipped
stacks.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
update run silently swallowed pull errors and never logged what actually
differed, making a recurring false "updates found" for pulsenode (no
registry-side change since Aug 19) impossible to diagnose.
glow v2.1.2 validates the style path with ~ expanded but passes the
original unexpanded string to glamour's file reader, causing ENOENT.
Fix: pass the style via --style flag so bash expands $HOME before glow
sees it. Revert glow.yml style to "auto" (unused when arty drives it).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New generate_notes_md(): builds title, description blockquote,
dependencies table, per-script fenced blocks, and details section
from the entire arty.yml using existing yq helpers
- show_notes() now accepts --raw to print raw Markdown; formatted
mode pipes through the same glow/mdcat/python3 render pipeline
- Removes the decorative ━━━ border (clashes with full-document output)
- main() passes args[] through to show_notes for flag forwarding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--no-pager causes journalctl to disable ANSI colors. Setting SYSTEMD_PAGER=cat
achieves the same effect (no interactive pager) while preserving color output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
systemctl and journalctl use their own color detection (SYSTEMD_COLORS env
var) independently of the terminal — child processes don't always inherit
it. Force colors for status and logs subcommands of update and backup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The update and backup services ran without STACKS_DIR since systemd
doesn't source ~/.env or ~/.zshrc. Capture the value at install time
and write it as Environment= in the generated unit files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Makes stacks work like arty — discovers stacks in the current directory.
Explicit STACKS_DIR env var still takes precedence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bin/setup:
- Fix Step 1 of 6 → Step 1 of 7
- exec zsh -l </dev/tty to avoid inheriting stale curl pipe on stdin
(without /dev/tty the curl|sh pipe bytes re-run the script as zsh)
- Fall back to printing exec zsh hint when /dev/tty is unavailable
- Check completion file existence instead of trusting arty exit code
bin/arty:
- Fix cmd_completion returning exit 1 for zsh: last statement was
[[ "$shell" == "bash" ]] which is false, changed to if/fi
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The script already sets zsh as default shell via chsh. Now it also
hands off to a zsh login shell immediately at the end so the user
doesn't need to run exec zsh manually.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ensure_zsh step (step 3 of 7): installs zsh via apt/brew if
missing, sets it as default shell via chsh
- Replace manual completion write with arty completion zsh --install
- Extend PATH with ~/.local/bin and ~/go/bin before completion step so
yq installed by arty deps (in a subshell) is found
- Renumber all steps 1-7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop .sh extension — scripts are now invocable directly as arty and stacks.
Remove redundant aliases from .zshrc since ~/bin is already in PATH.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>