Compare commits
2
Commits
3251f27edb
...
e3cd2df372
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cd2df372 | ||
|
|
067d017ea6 |
@@ -20,11 +20,72 @@ Each stack is independently deployable with its own `compose.yml` and `.env`. Al
|
||||
|
||||
## Tools
|
||||
|
||||
| Directory | Description |
|
||||
| File/Directory | Description |
|
||||
|---|---|
|
||||
| `stacks.sh` | CLI to manage stacks, services, and scaffolding |
|
||||
| `_backup` | Daily restic backups to HiDrive (host script + systemd timer) |
|
||||
| `_update` | Nightly image update check + prune (host script + systemd timer) |
|
||||
|
||||
## stacks.sh
|
||||
|
||||
`stacks.sh` is the primary management CLI. It wraps `docker compose` with glob-based multi-stack targeting, manages the update and backup systemd services, scaffolds new stacks, and generates shell completions.
|
||||
|
||||
```bash
|
||||
./stacks.sh help
|
||||
```
|
||||
|
||||
**Stack commands** — all accept one or more stack names or glob patterns (omit for all stacks):
|
||||
|
||||
```bash
|
||||
./stacks.sh ls # list all stacks with live container status
|
||||
./stacks.sh ps gitea # container status table
|
||||
./stacks.sh up # start all stacks
|
||||
./stacks.sh up gitea traefik # start specific stacks
|
||||
./stacks.sh down 'g*' # stop stacks matching glob
|
||||
./stacks.sh restart 'g*,traefik' # glob + exact name, comma-separated
|
||||
./stacks.sh pull --parallel # pull all images in parallel
|
||||
./stacks.sh logs -f gitea # follow logs
|
||||
./stacks.sh logs -n 100 gitea n8n # tail multiple stacks
|
||||
./stacks.sh exec gitea gitea gitea admin user list # exec in container
|
||||
./stacks.sh run passbolt passbolt bin/cake passbolt healthcheck
|
||||
```
|
||||
|
||||
**Service management:**
|
||||
|
||||
```bash
|
||||
./stacks.sh update install # link & enable systemd update timer
|
||||
./stacks.sh update run # run update now
|
||||
./stacks.sh update status # show timer/service status
|
||||
./stacks.sh update logs # show journal logs
|
||||
|
||||
./stacks.sh backup install # link & enable systemd backup timer
|
||||
./stacks.sh backup run # run backup now
|
||||
./stacks.sh backup snapshots # list restic snapshots
|
||||
```
|
||||
|
||||
**Scaffold a new stack:**
|
||||
|
||||
```bash
|
||||
./stacks.sh new myapp # basic stack with Traefik labels
|
||||
./stacks.sh new myapp --db postgres # with Postgres service
|
||||
./stacks.sh new myapp --db postgres --redis # with Postgres + Redis
|
||||
./stacks.sh new myapp --no-traefik # expose port instead of Traefik
|
||||
```
|
||||
|
||||
Generates `compose.yml` (with healthchecks, `../.data/` volumes, Traefik labels) and `.env.example`.
|
||||
|
||||
**Shell completion:**
|
||||
|
||||
```bash
|
||||
# Dynamic — discovers stacks at tab-complete time (default)
|
||||
./stacks.sh completion zsh --install
|
||||
|
||||
# Static — bakes current stack list in; useful on the VPS
|
||||
./stacks.sh completion zsh --static --install
|
||||
```
|
||||
|
||||
**Global flags:** `--dry-run`, `--parallel`, `--verbose`, `--quiet`
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
@@ -53,19 +114,13 @@ rsync -avz _backup/ vps:~/stacks/_backup/
|
||||
# Initialize restic repo (first time only)
|
||||
ssh vps 'source ~/stacks/_backup/.env && restic init -r /mnt/hidrive/users/valknar/Backup/stacks'
|
||||
|
||||
# Install systemd units
|
||||
ssh vps 'ln -sf ~/stacks/_backup/stacks-backup.service /etc/systemd/system/ && \
|
||||
ln -sf ~/stacks/_backup/stacks-backup.timer /etc/systemd/system/ && \
|
||||
systemctl daemon-reload && systemctl enable --now stacks-backup.timer'
|
||||
# Install systemd units (or use stacks.sh on the VPS)
|
||||
ssh vps '~/stacks/stacks.sh backup install'
|
||||
|
||||
# Manual test run
|
||||
ssh vps '~/stacks/_backup/backup.sh'
|
||||
|
||||
# Check timer status
|
||||
ssh vps 'systemctl status stacks-backup.timer'
|
||||
|
||||
# View snapshots
|
||||
ssh vps 'source ~/stacks/_backup/.env && restic -r /mnt/hidrive/users/valknar/Backup/stacks snapshots'
|
||||
# Manual run / status
|
||||
ssh vps '~/stacks/stacks.sh backup run'
|
||||
ssh vps '~/stacks/stacks.sh backup status'
|
||||
ssh vps '~/stacks/stacks.sh backup snapshots'
|
||||
```
|
||||
|
||||
## Updates
|
||||
@@ -75,18 +130,13 @@ The `_update` script runs nightly at 2:00 AM. It pulls the latest image for ever
|
||||
```bash
|
||||
# Deploy update stack
|
||||
rsync -avz _update/ vps:~/stacks/_update/
|
||||
ssh vps 'chmod +x ~/stacks/_update/update.sh'
|
||||
|
||||
# Install systemd units
|
||||
ssh vps 'ln -sf ~/stacks/_update/stacks-update.service /etc/systemd/system/ && \
|
||||
ln -sf ~/stacks/_update/stacks-update.timer /etc/systemd/system/ && \
|
||||
systemctl daemon-reload && systemctl enable --now stacks-update.timer'
|
||||
# Install systemd units (or use stacks.sh on the VPS)
|
||||
ssh vps '~/stacks/stacks.sh update install'
|
||||
|
||||
# Manual test run
|
||||
ssh vps '~/stacks/_update/update.sh'
|
||||
|
||||
# Check timer status
|
||||
ssh vps 'systemctl status stacks-update.timer'
|
||||
# Manual run / status
|
||||
ssh vps '~/stacks/stacks.sh update run'
|
||||
ssh vps '~/stacks/stacks.sh update status'
|
||||
```
|
||||
|
||||
## Notifications
|
||||
|
||||
@@ -726,6 +726,7 @@ REDIS
|
||||
# ─── Shell Completion ─────────────────────────────────────────────────────────
|
||||
|
||||
_completion_bash() {
|
||||
local stacks_init="$1" # pre-rendered stacks array initialisation block
|
||||
cat << 'EOF'
|
||||
# stacks.sh bash completion — source this file or place in /etc/bash_completion.d/
|
||||
_stacks_complete() {
|
||||
@@ -736,19 +737,10 @@ _stacks_complete() {
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
}
|
||||
|
||||
# Locate the stacks dir from the invoked script path
|
||||
local script="${COMP_WORDS[0]}"
|
||||
local stacks_dir
|
||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
||||
|
||||
local -a stacks=()
|
||||
if [[ -d "$stacks_dir" ]]; then
|
||||
for d in "$stacks_dir"/*/; do
|
||||
local n; n="$(basename "$d")"
|
||||
[[ "$n" == _* ]] && continue
|
||||
[[ -f "$d/compose.yml" ]] && stacks+=("$n")
|
||||
done
|
||||
fi
|
||||
EOF
|
||||
echo "$stacks_init"
|
||||
cat << 'EOF'
|
||||
|
||||
local commands="ls ps up down restart pull logs exec run config images top stats compose update backup new completion version help"
|
||||
local update_subs="install uninstall run status logs next"
|
||||
@@ -758,7 +750,7 @@ _stacks_complete() {
|
||||
case "${COMP_WORDS[1]}" in
|
||||
update) COMPREPLY=( $(compgen -W "$update_subs" -- "$cur") ); return ;;
|
||||
backup) COMPREPLY=( $(compgen -W "$backup_subs" -- "$cur") ); return ;;
|
||||
completion) COMPREPLY=( $(compgen -W "bash zsh --install" -- "$cur") ); return ;;
|
||||
completion) COMPREPLY=( $(compgen -W "bash zsh --install --static" -- "$cur") ); return ;;
|
||||
new)
|
||||
case "$prev" in
|
||||
--db) COMPREPLY=( $(compgen -W "postgres mysql mariadb" -- "$cur") ); return ;;
|
||||
@@ -803,22 +795,15 @@ EOF
|
||||
}
|
||||
|
||||
_completion_zsh() {
|
||||
local stacks_init="$1" # pre-rendered stacks array initialisation block
|
||||
cat << 'EOF'
|
||||
#compdef stacks.sh stacks
|
||||
|
||||
_stacks() {
|
||||
local script="${words[1]}"
|
||||
local stacks_dir
|
||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
||||
|
||||
local -a stacks=()
|
||||
if [[ -d "$stacks_dir" ]]; then
|
||||
for d in "$stacks_dir"/*/; do
|
||||
local n="${d:t}"
|
||||
[[ "$n" == _* ]] && continue
|
||||
[[ -f "${d}compose.yml" ]] && stacks+=("$n")
|
||||
done
|
||||
fi
|
||||
EOF
|
||||
echo "$stacks_init"
|
||||
cat << 'EOF'
|
||||
|
||||
local -a commands=(
|
||||
'ls:List all stacks with live status'
|
||||
@@ -916,6 +901,7 @@ _stacks() {
|
||||
completion)
|
||||
_arguments \
|
||||
'--install[Install the completion file]' \
|
||||
'--static[Bake current stack list into completion script]' \
|
||||
'1:shell:(bash zsh)'
|
||||
;;
|
||||
esac
|
||||
@@ -928,13 +914,14 @@ EOF
|
||||
}
|
||||
|
||||
cmd_completion() {
|
||||
local shell="" install=false
|
||||
local shell="" install=false static=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
bash|zsh) shell="$arg" ;;
|
||||
--install) install=true ;;
|
||||
*) die "Unknown argument: $arg (bash|zsh [--install])" ;;
|
||||
--static) static=true ;;
|
||||
*) die "Unknown argument: $arg (bash|zsh [--install] [--static])" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -944,10 +931,52 @@ cmd_completion() {
|
||||
warn "No shell specified, defaulting to: $shell"
|
||||
fi
|
||||
|
||||
# Build the stacks array initialisation block — either dynamic (runtime discovery)
|
||||
# or static (current list baked in at generation time).
|
||||
local bash_stacks_init zsh_stacks_init
|
||||
|
||||
if $static; then
|
||||
local -a cur_stacks=()
|
||||
mapfile -t cur_stacks < <(list_all_stacks)
|
||||
[[ ${#cur_stacks[@]} -eq 0 ]] && die "No stacks found to bake into completion"
|
||||
local generated_at
|
||||
generated_at="$(date '+%Y-%m-%d')"
|
||||
bash_stacks_init=" # Stacks baked in at generation time (${generated_at}) — rerun to update
|
||||
local -a stacks=(${cur_stacks[*]})"
|
||||
zsh_stacks_init=" # Stacks baked in at generation time (${generated_at}) — rerun to update
|
||||
local -a stacks=(${cur_stacks[*]})"
|
||||
info "Baking ${#cur_stacks[@]} stacks: ${cur_stacks[*]}"
|
||||
else
|
||||
bash_stacks_init=' # Dynamically discover stacks from the script location at completion time
|
||||
local stacks_dir
|
||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
||||
|
||||
local -a stacks=()
|
||||
if [[ -d "$stacks_dir" ]]; then
|
||||
for d in "$stacks_dir"/*/; do
|
||||
local n; n="$(basename "$d")"
|
||||
[[ "$n" == _* ]] && continue
|
||||
[[ -f "$d/compose.yml" ]] && stacks+=("$n")
|
||||
done
|
||||
fi'
|
||||
zsh_stacks_init=' # Dynamically discover stacks from the script location at completion time
|
||||
local stacks_dir
|
||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
||||
|
||||
local -a stacks=()
|
||||
if [[ -d "$stacks_dir" ]]; then
|
||||
for d in "$stacks_dir"/*/; do
|
||||
local n="${d:t}"
|
||||
[[ "$n" == _* ]] && continue
|
||||
[[ -f "${d}compose.yml" ]] && stacks+=("$n")
|
||||
done
|
||||
fi'
|
||||
fi
|
||||
|
||||
local content
|
||||
case "$shell" in
|
||||
bash) content="$(_completion_bash)" ;;
|
||||
zsh) content="$(_completion_zsh)" ;;
|
||||
bash) content="$(_completion_bash "$bash_stacks_init")" ;;
|
||||
zsh) content="$(_completion_zsh "$zsh_stacks_init")" ;;
|
||||
*) die "Unsupported shell: $shell (bash|zsh)" ;;
|
||||
esac
|
||||
|
||||
@@ -1042,7 +1071,8 @@ ${BOLD}UTILITIES${RESET}
|
||||
--db postgres|mysql|mariadb Add a database service
|
||||
--redis Add a Redis service
|
||||
--no-traefik Expose port instead of Traefik labels
|
||||
${CYAN}completion${RESET} bash|zsh [--install] Generate/install shell completion
|
||||
${CYAN}completion${RESET} bash|zsh [--install] [--static] Generate/install shell completion
|
||||
--static Bake current stack list in (no runtime discovery)
|
||||
${CYAN}version${RESET} Print version
|
||||
${CYAN}help${RESET} Show this help
|
||||
|
||||
|
||||
Reference in New Issue
Block a user