#!/usr/bin/env bash # update-all.sh — main post-pull orchestrator for the Ulicraft stack on the host. # # Run AFTER `git pull` (it does NOT pull or push). Single source of truth for the # build + restart steps; the `deploy` skill is just `git pull --ff-only` followed # by `./update-all.sh --hard`. # # Steps (always, in order): render configs → sync server mods → regen landing mod # list → rebuild landing (www/) → apply via docker compose. # # Modes: # (default, rolling) `docker compose up -d --build`, then restart ONLY the # services whose mounted inputs actually changed — drasl/nmsr on a config # re-render, minecraft on a mod change. Minimal downtime. NOTE: this path # canNOT detect changes to caddy's static conf (caddy/conf.d/*.caddy) — it # has no pre-pull baseline for them; use --hard for those. # --hard `docker compose down --remove-orphans && up -d --build`. # Full restart (Minecraft world downtime); picks up everything, caddy # static conf included. # # Cautious by design: strict halt on ANY error (set -euo pipefail) — never a # partial deploy. Fix the cause and re-run. Uses pnpm/node only (never npm). set -euo pipefail cd "$(dirname "$0")" # repo root (this script lives at the root) # ── args ──────────────────────────────────────────────────────────── HARD=0 case "${1:-}" in "") HARD=0 ;; --hard) HARD=1 ;; -h|--help) echo "usage: $0 [--hard]"; exit 0 ;; *) echo "unknown arg: $1" >&2; echo "usage: $0 [--hard]" >&2; exit 2 ;; esac step() { printf '\n\033[1;36m→ %s\033[0m\n' "$*"; } cfg_hash() { [ -f "$1" ] && md5sum "$1" | cut -d' ' -f1 || echo absent; } # Cheap content proxy: jar filenames + sizes (content changes ⇒ size changes). mods_hash() { { find server-mods -maxdepth 1 -name '*.jar' -printf '%f %s\n' 2>/dev/null || true; } \ | sort | md5sum | cut -d' ' -f1 } # ── snapshot inputs (rolling mode only) ───────────────────────────── if [ "$HARD" -eq 0 ]; then before_drasl=$(cfg_hash drasl/config/config.toml) before_nmsr=$(cfg_hash nmsr/config.toml) before_mods=$(mods_hash) fi # ── 1. render drasl/nmsr configs from .env (halts on bad input) ───── step "render-config.sh" tooling/render-config.sh # ── 2. mirror the server mod subset into ./server-mods ────────────── step "sync-server-mods.sh" tooling/sync-server-mods.sh # ── 3. regenerate the landing mod list (mods.json + logos) ────────── # build-modlist.py needs stdlib tomllib (Python >=3.11). The host's default # `python3` may be older (e.g. Ubuntu 22.04 ships 3.10), so pick the first # interpreter that satisfies it rather than relying on the shebang. step "build-modlist.py" PY="" for p in python3.13 python3.12 python3.11 python3; do command -v "$p" >/dev/null 2>&1 || continue if "$p" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then PY="$p"; break fi done [ -n "$PY" ] || { echo "no python >=3.11 found (build-modlist.py needs stdlib tomllib)" >&2; exit 1; } "$PY" tooling/build-modlist.py # ── 4. rebuild the landing site into www/ (gitignored — pull never # updates it, so it must be rebuilt every run) ───────────────── step "landing build" ( # Non-interactive shells lack node/pnpm on PATH — source nvm + pnpm. export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" export PNPM_HOME="$HOME/.local/share/pnpm" export PATH="$PNPM_HOME/bin:$PATH" command -v pnpm >/dev/null 2>&1 || { echo "pnpm not on PATH (looked in nvm=$NVM_DIR, pnpm=$PNPM_HOME/bin)." >&2 echo "Install pnpm + node (never npm) and re-run." >&2 exit 1 } cd landing set -a; . ../.env; set +a pnpm run build ) # ── 5. apply via docker compose ───────────────────────────────────── if [ "$HARD" -eq 1 ]; then step "docker compose down + up (--hard, full restart)" docker compose down --remove-orphans docker compose up -d --build else step "docker compose up -d --build" docker compose up -d --build # Rolling `up -d` does NOT reload changed bind-mounted configs/mods, so restart # only the services whose inputs actually changed since the snapshot above. restart=() [ "$(cfg_hash drasl/config/config.toml)" != "$before_drasl" ] && restart+=(drasl) [ "$(cfg_hash nmsr/config.toml)" != "$before_nmsr" ] && restart+=(nmsr) [ "$(mods_hash)" != "$before_mods" ] && restart+=(minecraft) # mods ⇒ MC restart (downtime, required) if [ "${#restart[@]}" -gt 0 ]; then step "restart changed services: ${restart[*]}" docker compose restart "${restart[@]}" else echo " (no config/mod changes — nothing to restart)" fi fi # ── 6. status summary ─────────────────────────────────────────────── step "docker compose ps" docker compose ps printf '\n\033[1;32m✓ update-all complete\033[0m\n'