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
|
## 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) |
|
| `_backup` | Daily restic backups to HiDrive (host script + systemd timer) |
|
||||||
| `_update` | Nightly image update check + prune (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
|
## Deployment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -53,19 +114,13 @@ rsync -avz _backup/ vps:~/stacks/_backup/
|
|||||||
# Initialize restic repo (first time only)
|
# Initialize restic repo (first time only)
|
||||||
ssh vps 'source ~/stacks/_backup/.env && restic init -r /mnt/hidrive/users/valknar/Backup/stacks'
|
ssh vps 'source ~/stacks/_backup/.env && restic init -r /mnt/hidrive/users/valknar/Backup/stacks'
|
||||||
|
|
||||||
# Install systemd units
|
# Install systemd units (or use stacks.sh on the VPS)
|
||||||
ssh vps 'ln -sf ~/stacks/_backup/stacks-backup.service /etc/systemd/system/ && \
|
ssh vps '~/stacks/stacks.sh backup install'
|
||||||
ln -sf ~/stacks/_backup/stacks-backup.timer /etc/systemd/system/ && \
|
|
||||||
systemctl daemon-reload && systemctl enable --now stacks-backup.timer'
|
|
||||||
|
|
||||||
# Manual test run
|
# Manual run / status
|
||||||
ssh vps '~/stacks/_backup/backup.sh'
|
ssh vps '~/stacks/stacks.sh backup run'
|
||||||
|
ssh vps '~/stacks/stacks.sh backup status'
|
||||||
# Check timer status
|
ssh vps '~/stacks/stacks.sh backup snapshots'
|
||||||
ssh vps 'systemctl status stacks-backup.timer'
|
|
||||||
|
|
||||||
# View snapshots
|
|
||||||
ssh vps 'source ~/stacks/_backup/.env && restic -r /mnt/hidrive/users/valknar/Backup/stacks snapshots'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
@@ -75,18 +130,13 @@ The `_update` script runs nightly at 2:00 AM. It pulls the latest image for ever
|
|||||||
```bash
|
```bash
|
||||||
# Deploy update stack
|
# Deploy update stack
|
||||||
rsync -avz _update/ vps:~/stacks/_update/
|
rsync -avz _update/ vps:~/stacks/_update/
|
||||||
ssh vps 'chmod +x ~/stacks/_update/update.sh'
|
|
||||||
|
|
||||||
# Install systemd units
|
# Install systemd units (or use stacks.sh on the VPS)
|
||||||
ssh vps 'ln -sf ~/stacks/_update/stacks-update.service /etc/systemd/system/ && \
|
ssh vps '~/stacks/stacks.sh update install'
|
||||||
ln -sf ~/stacks/_update/stacks-update.timer /etc/systemd/system/ && \
|
|
||||||
systemctl daemon-reload && systemctl enable --now stacks-update.timer'
|
|
||||||
|
|
||||||
# Manual test run
|
# Manual run / status
|
||||||
ssh vps '~/stacks/_update/update.sh'
|
ssh vps '~/stacks/stacks.sh update run'
|
||||||
|
ssh vps '~/stacks/stacks.sh update status'
|
||||||
# Check timer status
|
|
||||||
ssh vps 'systemctl status stacks-update.timer'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notifications
|
## Notifications
|
||||||
|
|||||||
@@ -726,6 +726,7 @@ REDIS
|
|||||||
# ─── Shell Completion ─────────────────────────────────────────────────────────
|
# ─── Shell Completion ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_completion_bash() {
|
_completion_bash() {
|
||||||
|
local stacks_init="$1" # pre-rendered stacks array initialisation block
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
# stacks.sh bash completion — source this file or place in /etc/bash_completion.d/
|
# stacks.sh bash completion — source this file or place in /etc/bash_completion.d/
|
||||||
_stacks_complete() {
|
_stacks_complete() {
|
||||||
@@ -736,19 +737,10 @@ _stacks_complete() {
|
|||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Locate the stacks dir from the invoked script path
|
|
||||||
local script="${COMP_WORDS[0]}"
|
local script="${COMP_WORDS[0]}"
|
||||||
local stacks_dir
|
EOF
|
||||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
echo "$stacks_init"
|
||||||
|
cat << 'EOF'
|
||||||
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
|
|
||||||
|
|
||||||
local commands="ls ps up down restart pull logs exec run config images top stats compose update backup new completion version help"
|
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"
|
local update_subs="install uninstall run status logs next"
|
||||||
@@ -758,7 +750,7 @@ _stacks_complete() {
|
|||||||
case "${COMP_WORDS[1]}" in
|
case "${COMP_WORDS[1]}" in
|
||||||
update) COMPREPLY=( $(compgen -W "$update_subs" -- "$cur") ); return ;;
|
update) COMPREPLY=( $(compgen -W "$update_subs" -- "$cur") ); return ;;
|
||||||
backup) COMPREPLY=( $(compgen -W "$backup_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)
|
new)
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--db) COMPREPLY=( $(compgen -W "postgres mysql mariadb" -- "$cur") ); return ;;
|
--db) COMPREPLY=( $(compgen -W "postgres mysql mariadb" -- "$cur") ); return ;;
|
||||||
@@ -803,22 +795,15 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
_completion_zsh() {
|
_completion_zsh() {
|
||||||
|
local stacks_init="$1" # pre-rendered stacks array initialisation block
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
#compdef stacks.sh stacks
|
#compdef stacks.sh stacks
|
||||||
|
|
||||||
_stacks() {
|
_stacks() {
|
||||||
local script="${words[1]}"
|
local script="${words[1]}"
|
||||||
local stacks_dir
|
EOF
|
||||||
stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)"
|
echo "$stacks_init"
|
||||||
|
cat << 'EOF'
|
||||||
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
|
|
||||||
|
|
||||||
local -a commands=(
|
local -a commands=(
|
||||||
'ls:List all stacks with live status'
|
'ls:List all stacks with live status'
|
||||||
@@ -916,6 +901,7 @@ _stacks() {
|
|||||||
completion)
|
completion)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--install[Install the completion file]' \
|
'--install[Install the completion file]' \
|
||||||
|
'--static[Bake current stack list into completion script]' \
|
||||||
'1:shell:(bash zsh)'
|
'1:shell:(bash zsh)'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -928,13 +914,14 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_completion() {
|
cmd_completion() {
|
||||||
local shell="" install=false
|
local shell="" install=false static=false
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
bash|zsh) shell="$arg" ;;
|
bash|zsh) shell="$arg" ;;
|
||||||
--install) install=true ;;
|
--install) install=true ;;
|
||||||
*) die "Unknown argument: $arg (bash|zsh [--install])" ;;
|
--static) static=true ;;
|
||||||
|
*) die "Unknown argument: $arg (bash|zsh [--install] [--static])" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -944,10 +931,52 @@ cmd_completion() {
|
|||||||
warn "No shell specified, defaulting to: $shell"
|
warn "No shell specified, defaulting to: $shell"
|
||||||
fi
|
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
|
local content
|
||||||
case "$shell" in
|
case "$shell" in
|
||||||
bash) content="$(_completion_bash)" ;;
|
bash) content="$(_completion_bash "$bash_stacks_init")" ;;
|
||||||
zsh) content="$(_completion_zsh)" ;;
|
zsh) content="$(_completion_zsh "$zsh_stacks_init")" ;;
|
||||||
*) die "Unsupported shell: $shell (bash|zsh)" ;;
|
*) die "Unsupported shell: $shell (bash|zsh)" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -1042,7 +1071,8 @@ ${BOLD}UTILITIES${RESET}
|
|||||||
--db postgres|mysql|mariadb Add a database service
|
--db postgres|mysql|mariadb Add a database service
|
||||||
--redis Add a Redis service
|
--redis Add a Redis service
|
||||||
--no-traefik Expose port instead of Traefik labels
|
--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}version${RESET} Print version
|
||||||
${CYAN}help${RESET} Show this help
|
${CYAN}help${RESET} Show this help
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user