#!/usr/bin/env bash # # Reaper's Sky - server mod updater. # # Brings the server's mods/ folder in line with a mods.json manifest: # 1. gracefully stops the running server (tmux session "mc") # 2. prunes jars that are not in the manifest, downloads + sha1-verifies # the ones that are missing # 3. boots the server again and waits until it reports "Done" # # Usage (on the server): # bash update.sh [MANIFEST] [--no-start] # # MANIFEST path to mods.json (default: /tmp/reaper-sky-mods.json) # --no-start sync only, leave the server stopped # # You normally do NOT run this by hand. From your PC run: # python3 server/build_mods.py --push # which regenerates mods.json from the client instance, uploads it to # /tmp/reaper-sky-mods.json on the server and pipes this script over SSH. set -euo pipefail SERVER_DIR="${DIR:-$HOME/reaper-sky-server}" SESSION="mc" MANIFEST="/tmp/reaper-sky-mods.json" START=true for arg in "$@"; do case "$arg" in --no-start) START=false ;; -h|--help) sed -n '2,20p' "$0"; exit 0 ;; *) MANIFEST="$arg" ;; esac done [ -f "$MANIFEST" ] || { echo "[update] ERROR: manifest not found: $MANIFEST" >&2; exit 1; } [ -d "$SERVER_DIR" ] || { echo "[update] ERROR: server dir not found: $SERVER_DIR" >&2; exit 1; } log() { printf '\033[1;34m[update]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[update] ERROR:\033[0m %s\n' "$*" >&2; exit 1; } # "running" = the tmux session's pane is executing java right now. running() { tmux has-session -t "$SESSION" 2>/dev/null || return 1 [ "$(tmux list-panes -t "$SESSION" -F '#{pane_current_command}' 2>/dev/null)" = "java" ] } # --- 1. stop ------------------------------------------------------------------- if running; then log "Stopping server (session '$SESSION')..." tmux send-keys -t "$SESSION" "stop" C-m for _ in $(seq 1 90); do running || break sleep 1 done running && die "Server still running after 90s -- refusing to continue." log "Server stopped." else log "Server not running -- skipping stop." fi # --- 2. sync mods ---------------------------------------------------------------- log "Syncing mods against $(basename "$MANIFEST")..." mkdir -p "$SERVER_DIR/mods" cd "$SERVER_DIR/mods" python3 - "$MANIFEST" <<'PY' import hashlib, json, os, subprocess, sys manifest = json.load(open(sys.argv[1])) keep = set() for m in manifest["mods"]: fname, url, want = m["file"], m["url"], m["sha1"] keep.add(fname) if os.path.exists(fname): h = hashlib.sha1() with open(fname, "rb") as fh: for b in iter(lambda: fh.read(65536), b""): h.update(b) if h.hexdigest() == want: continue # already present & correct print(f" ~ {fname} (hash mismatch -- re-downloading)") else: print(f" + {fname}") if not url: raise SystemExit(f"no download URL for {fname}") tmp = fname + ".part" subprocess.run(["curl", "-fsSL", "--retry", "3", "-o", tmp, url], check=True) h = hashlib.sha1() with open(tmp, "rb") as fh: for b in iter(lambda: fh.read(65536), b""): h.update(b) if h.hexdigest() != want: os.unlink(tmp) raise SystemExit(f"sha1 mismatch for {fname}") os.rename(tmp, fname) for f in sorted(os.listdir(".")): if f.endswith(".jar") and f not in keep: os.unlink(f) print(f" - {f} (stale)") on_disk = len([f for f in os.listdir(".") if f.endswith(".jar")]) print(f"[update] {len(keep)} mods in manifest, {on_disk} jars on disk") PY log "Mods synced." # --- 3. start + wait for boot ------------------------------------------------------ start_server() { if ! tmux has-session -t "$SESSION" 2>/dev/null; then tmux new-session -d -s "$SESSION" -c "$SERVER_DIR" fi tmux send-keys -t "$SESSION" "cd '$SERVER_DIR' && ./run.sh" C-m } if [ "$START" = false ]; then log "--no-start given: leaving the server stopped." exit 0 fi LOG="$SERVER_DIR/logs/latest.log" OFFSET=$(wc -l < "$LOG" 2>/dev/null || echo 0) # ignore pre-boot log content log "Starting server (tmux session '$SESSION')..." start_server log "Waiting for boot (up to 4 min)..." for i in $(seq 1 240); do cur=$(wc -l < "$LOG" 2>/dev/null || echo 0) [ "$cur" -lt "$OFFSET" ] && OFFSET=0 # log was rotated/truncated if [ "$cur" -gt "$OFFSET" ] && tail -n +"$((OFFSET + 1))" "$LOG" | grep -q "Done ("; then log "Server is UP after ~${i}s." log "Console: tmux attach -t $SESSION (detach: Ctrl-b d)" exit 0 fi # pane exited or left java => boot failed / crashed if ! tmux has-session -t "$SESSION" 2>/dev/null || ! running; then sleep 5 # give the shell a moment to print any error die "Server process exited during boot -- check: tail -50 $LOG" fi sleep 1 done die "Timed out waiting for 'Done' after 4 min -- check: tail -50 $LOG"