scripts/new-stack.sh

#!/usr/bin/env bash
# scripts/new-stack.sh
# Scaffold a new stack under stacks/<name>/ with a basic compose.yml,
# .env, README, and a Caddy site include. Refuses to overwrite.
#
# Usage: new-stack.sh <name>

set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
. "${HERE}/lib/log.sh"

REPO="${REPO:-/srv/homelab}"
name="${1:-}"

if [[ -z "${name}" ]]; then
    printf 'usage: %s <name>\n' "$0" >&2
    exit 2
fi
if [[ ! "${name}" =~ ^[a-z][a-z0-9-]{1,30}$ ]]; then
    log_err "name must be kebab-case [a-z0-9-], 2-31 chars"
    exit 2
fi

dest="${REPO}/stacks/${name}"
if [[ -e "${dest}" ]]; then
    log_err "${dest} already exists"
    exit 1
fi

install -d "${dest}"

cat >"${dest}/.env.example" <<EOF
# stacks/${name}/.env.example
TZ=Europe/Zurich
PUID=1000
PGID=1000
EOF

cat >"${dest}/docker-compose.yml" <<EOF
# stacks/${name}/docker-compose.yml
# Scaffolded by scripts/new-stack.sh on $(date -u +%F). Replace.
services:
  ${name}:
    image: ghcr.io/homelab/placeholder:latest
    container_name: ${name}
    restart: unless-stopped
    env_file: .env
    networks: [default]

networks:
  default:
    external: true
    name: homelab-default
EOF

cat >"${dest}/README.md" <<EOF
# ${name}

Scaffolded stack. Fill in compose image + any config files.
See mercemay.top/src/homelab-compose/ for conventions.
EOF

cat >"${REPO}/caddy/sites-enabled/${name}.caddy" <<EOF
${name}.home.arpa {
    tls internal
    reverse_proxy ${name}:80
    import snippets/security/hsts.snippet
    import snippets/auth/authelia-forward.snippet
}
EOF

log_info "created stack at ${dest}"
log_info "next: edit compose.yml and run 'docker compose -f ${dest}/docker-compose.yml up -d'"