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>
scripts
Personal shell scripts, available system-wide via ~/scripts in PATH.
stacks
Docker Compose stack manager for ~/stacks. Set STACKS_DIR to override the default location.
stacks ls # list all stacks with live container status
stacks ps gitea # container status table
stacks up # start all stacks
stacks up gitea traefik # start specific stacks
stacks down 'g*' # stop stacks matching glob
stacks restart 'g*,traefik' # glob + exact name, comma-separated
stacks pull --parallel # pull all images in parallel
stacks logs -f gitea # follow logs
stacks logs -n 100 gitea n8n # tail multiple stacks
stacks exec gitea gitea gitea admin user list # exec in container
stacks run passbolt passbolt bin/cake passbolt healthcheck
stacks top # resource usage per stack
stacks stats # live docker stats
Update service — nightly at 2:00 AM, pulls new images and recreates changed containers:
stacks update install # write & enable systemd unit + timer
stacks update run # run now
stacks update status # timer/service status
stacks update logs # journald logs
stacks update next # next scheduled run
Backup service — daily at 3:00 AM, dumps Postgres DBs and runs restic backup:
stacks backup install # write & enable systemd unit + timer
stacks backup run # run now
stacks backup status # timer/service status
stacks backup logs # journald logs
stacks backup snapshots # list restic snapshots
Scaffold a new stack:
stacks new myapp # basic stack with Traefik labels
stacks new myapp --db postgres # with Postgres service
stacks new myapp --db postgres --redis # with Postgres + Redis
stacks new myapp --no-traefik # expose port instead of Traefik
Shell completion:
stacks completion zsh --install # dynamic (discovers stacks at tab time)
stacks completion zsh --static --install # static (bakes current list in)
Global flags: --dry-run, --parallel, --verbose, --quiet
arty
Bash library repository and release management system. Run inside a directory with an arty.yml.
arty install <repo-url> [name] # install a library from git
arty deps [--dry-run] # install all dependencies from arty.yml
arty list # list installed libraries with dependency tree
arty notes # display project notes from arty.yml
arty scripts # list all scripts defined in arty.yml
arty remove <name> # remove an installed library
arty init [name] # initialise a new arty.yml project
arty completion bash|zsh # print shell completion script
arty <script-name> [args] # run a script defined in arty.yml scripts section
Shell completion:
arty completion zsh --install # install zsh completion
arty completion bash --install # install bash completion
Global Flags: --dry-run, --verbose
Example arty.yml:
name: "my-project"
version: "0.1.0"
description: "A bash library project"
references:
- https://github.com/user/bash-utils.git # simple URL
- url: git@github.com:user/logging-lib.git
into: lib/logging # clone into this path (relative to arty.yml)
ref: v2.1.0 # branch, tag, or commit hash
- url: git@github.com:user/dev-tools.git
env: [dev, ci] # only install in these environments
scripts:
build: bash scripts/build.sh
test: bash scripts/test.sh
deploy: bash scripts/deploy.sh $@
envs:
default:
APP_ENV: development
LOG_LEVEL: debug
production:
APP_ENV: production
LOG_LEVEL: info