143 lines
4.8 KiB
Bash
Executable File
143 lines
4.8 KiB
Bash
Executable File
#!/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"
|