docs(reference): import Dune: Awakening server-manager references
Phase 2 references for the host-agent Dune adapter, moved out of volatile /tmp
into docs/reference-repos/ (per Commander). Three upstream projects, .git +
node_modules + compiled binaries stripped (16MB source). Nested AI-instruction
files (.claude/, CLAUDE.md) removed so they don't pollute Corrosion sessions.
- icehunter/ dune-admin (Go+React) — 4 control planes; SETUP_DOCKER.md is the
closest analog to our agent's Dune docker control plane (compose
lifecycle, docker logs, RabbitMQ-via-exec, dune Postgres schema)
- adainrivers/ Rust/Tauri desktop — SSH+k8s BattleGroup control, maintenance
daemon, in-game admin console (Rust idiom reference)
- the4rchangel/ Node web UI replacing battlegroup.bat — matches the Commander's
Hyper-V self-host path + game-config schema
See docs/reference-repos/README.md for the full index + how we use each.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
353
docs/reference-repos/icehunter/scripts/install.sh
Executable file
353
docs/reference-repos/icehunter/scripts/install.sh
Executable file
@@ -0,0 +1,353 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh — install the dune-admin binary on an Ubuntu host
|
||||
#
|
||||
# This script ONLY installs the binary. It does not configure it. To configure
|
||||
# dune-admin after installing, run the built-in setup wizard:
|
||||
#
|
||||
# /opt/dune-admin/dune-admin -setup
|
||||
#
|
||||
# The wizard asks you to pick a control plane (kubectl / docker / local / amp)
|
||||
# and writes ~/.dune-admin/config.yaml.
|
||||
#
|
||||
# Prerequisites (this script does NOT install these for you):
|
||||
# - Ubuntu 22.04 or 24.04 with passwordless sudo
|
||||
# - Whatever your control plane needs already running:
|
||||
# * amp → AMP installed with a running Dune instance
|
||||
# (verify with: sudo -u amp ampinstmgr -l)
|
||||
# * kubectl → kubeconfig pointing at a k3s/k8s cluster with the
|
||||
# dune workloads deployed
|
||||
# * docker → docker daemon + named containers for dune services
|
||||
# * local → game-server processes reachable on localhost
|
||||
# - PostgreSQL reachable (typically 127.0.0.1:15432 with the AMP module)
|
||||
# - Outbound internet (to fetch Go, Node, pnpm, the source repo)
|
||||
#
|
||||
# What this script does:
|
||||
# 1. Installs build toolchain: Go 1.26.3, Node 22 LTS, pnpm 10.28.1, build-essential
|
||||
# 2. Clones dune-admin source into $SOURCE_DIR (default: ~/src/dune-admin)
|
||||
# 3. Builds the Linux binary + frontend assets
|
||||
# 4. Copies the binary, SPA, and data files into $INSTALL_DIR (default: /opt/dune-admin)
|
||||
# 5. Writes the systemd unit (Restart=always) — but does not enable/start it
|
||||
# 6. Prints next steps: setup wizard, sudoers entry, service enable/start
|
||||
#
|
||||
# What this script does NOT do:
|
||||
# - Install AMP, k3s, Docker, or set up game services — those are prerequisites
|
||||
# - Run the setup wizard (interactive; you do that, see above)
|
||||
# - Apply sudoers grants (security-sensitive; you review and apply)
|
||||
# - Enable or start the systemd unit (it writes the unit, but you enable/start
|
||||
# it after running the setup wizard)
|
||||
#
|
||||
# Re-running this script is safe and idempotent. If a toolchain version is
|
||||
# already correct, it's skipped. If source is already cloned, it's fetched
|
||||
# and reset to the target branch. If artifacts already exist in $INSTALL_DIR,
|
||||
# they are replaced atomically with a `.prev` backup left in place.
|
||||
#
|
||||
# Local patches:
|
||||
# If a "patches" directory exists next to this script (or one is specified
|
||||
# with --patches-dir), every *.patch file in it is applied with `git apply`
|
||||
# after the source checkout. Use this to layer in unmerged fixes or local
|
||||
# modifications without forking the repo. Pass --no-patches to skip.
|
||||
#
|
||||
# Usage:
|
||||
# ./install.sh # main branch, /opt/dune-admin, current user
|
||||
# ./install.sh --branch chore/phase-0-bug-fixes
|
||||
# ./install.sh --install-dir /opt/dune-admin --service-user dune-admin
|
||||
# ./install.sh --patches-dir ./my-patches
|
||||
# ./install.sh --no-patches
|
||||
# ./install.sh --help
|
||||
|
||||
# Re-exec under bash when started by another shell (`sh install.sh`, `sh -c …`,
|
||||
# `zsh install.sh`) so the bash-only features below — arrays, [[ … ]],
|
||||
# `set -o pipefail`, ${BASH_SOURCE} — work regardless of the caller's shell (#76).
|
||||
# POSIX-safe test, and placed after the comment header so it stays out of the
|
||||
# `sed -n '2,30p'` range that usage() prints. The README's `curl … | bash`
|
||||
# already runs under bash, so the guard is a no-op there.
|
||||
if [ -z "${BASH_VERSION:-}" ]; then
|
||||
exec bash "$0" "$@"
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults (override via flags) ─────────────────────────────────────────────
|
||||
REPO_URL="https://github.com/Icehunter/dune-admin.git"
|
||||
BRANCH="main"
|
||||
SOURCE_DIR="${HOME}/src/dune-admin"
|
||||
INSTALL_DIR="/opt/dune-admin"
|
||||
SERVICE_USER="${USER}"
|
||||
SKIP_TOOLCHAIN=0
|
||||
|
||||
# Patches directory: defaults to ./patches alongside this script. Empty/missing
|
||||
# is fine — just means no patches will be applied.
|
||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
PATCHES_DIR="${SCRIPT_DIR}/patches"
|
||||
APPLY_PATCHES=1
|
||||
|
||||
GO_VERSION="1.26.3"
|
||||
NODE_MAJOR="22"
|
||||
PNPM_VERSION="10.28.1"
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
log() { printf '\033[1;34m[install]\033[0m %s\n' "$*"; }
|
||||
ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[warn ]\033[0m %s\n' "$*"; }
|
||||
die() { printf '\033[1;31m[fail ]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Argument parsing ─────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo) REPO_URL="$2"; shift 2 ;;
|
||||
--branch) BRANCH="$2"; shift 2 ;;
|
||||
--source-dir) SOURCE_DIR="$2"; shift 2 ;;
|
||||
--install-dir) INSTALL_DIR="$2"; shift 2 ;;
|
||||
--service-user) SERVICE_USER="$2"; shift 2 ;;
|
||||
--skip-toolchain) SKIP_TOOLCHAIN=1; shift ;;
|
||||
--patches-dir) PATCHES_DIR="$2"; shift 2 ;;
|
||||
--no-patches) APPLY_PATCHES=0; shift ;;
|
||||
-h|--help) usage ;;
|
||||
*) die "unknown flag: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
log "config:"
|
||||
log " repo: $REPO_URL"
|
||||
log " branch: $BRANCH"
|
||||
log " source dir: $SOURCE_DIR"
|
||||
log " install dir: $INSTALL_DIR"
|
||||
log " service user: $SERVICE_USER"
|
||||
log ""
|
||||
|
||||
# ── Sanity checks ────────────────────────────────────────────────────────────
|
||||
[[ "$(id -u)" -eq 0 ]] && die "run this as a normal user with sudo, not as root directly"
|
||||
sudo -n true 2>/dev/null || die "this user needs passwordless sudo (or you need to authenticate sudo first)"
|
||||
id "$SERVICE_USER" >/dev/null 2>&1 || die "service user '$SERVICE_USER' does not exist"
|
||||
command -v sudo >/dev/null || die "sudo is required"
|
||||
|
||||
# Don't try to migrate a running service silently — make the operator stop it first.
|
||||
if systemctl is-active --quiet dune-admin 2>/dev/null; then
|
||||
warn "dune-admin.service is currently active. Stop it before re-running this script:"
|
||||
warn " sudo systemctl stop dune-admin"
|
||||
die "refusing to swap binary under a running service"
|
||||
fi
|
||||
|
||||
# ── 1. Toolchain ─────────────────────────────────────────────────────────────
|
||||
if [[ "$SKIP_TOOLCHAIN" -eq 0 ]]; then
|
||||
log "installing build toolchain (apt: build-essential, git, curl, ca-certificates)…"
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||||
build-essential git curl ca-certificates
|
||||
|
||||
# ── Go ─────────────────────────────────────────────────────────────────────
|
||||
if /usr/local/go/bin/go version 2>/dev/null | grep -q "go${GO_VERSION}"; then
|
||||
ok "go ${GO_VERSION} already installed"
|
||||
else
|
||||
log "installing go ${GO_VERSION} to /usr/local/go…"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o "$tmp/go.tgz"
|
||||
sudo rm -rf /usr/local/go
|
||||
sudo tar -C /usr/local -xzf "$tmp/go.tgz"
|
||||
ok "go $(/usr/local/go/bin/go version | awk '{print $3}') installed"
|
||||
fi
|
||||
export PATH="/usr/local/go/bin:${PATH}"
|
||||
|
||||
# ── Node.js ────────────────────────────────────────────────────────────────
|
||||
if command -v node >/dev/null && node -v | grep -q "^v${NODE_MAJOR}\."; then
|
||||
ok "node $(node -v) already installed"
|
||||
else
|
||||
log "installing Node ${NODE_MAJOR} LTS from NodeSource…"
|
||||
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | sudo -E bash -
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq nodejs
|
||||
ok "node $(node -v) installed"
|
||||
fi
|
||||
|
||||
# ── pnpm ───────────────────────────────────────────────────────────────────
|
||||
if command -v pnpm >/dev/null && pnpm -v 2>/dev/null | grep -q "^${PNPM_VERSION}$"; then
|
||||
ok "pnpm ${PNPM_VERSION} already installed"
|
||||
else
|
||||
log "installing pnpm ${PNPM_VERSION}…"
|
||||
sudo npm install -g "pnpm@${PNPM_VERSION}"
|
||||
ok "pnpm $(pnpm -v) installed"
|
||||
fi
|
||||
else
|
||||
log "skipping toolchain installation (--skip-toolchain)"
|
||||
export PATH="/usr/local/go/bin:${PATH}"
|
||||
fi
|
||||
log ""
|
||||
|
||||
# ── 2. Source ────────────────────────────────────────────────────────────────
|
||||
log "syncing source into $SOURCE_DIR @ branch $BRANCH…"
|
||||
mkdir -p "$(dirname "$SOURCE_DIR")"
|
||||
if [[ -d "$SOURCE_DIR/.git" ]]; then
|
||||
git -C "$SOURCE_DIR" fetch --quiet origin
|
||||
else
|
||||
git clone --quiet "$REPO_URL" "$SOURCE_DIR"
|
||||
fi
|
||||
git -C "$SOURCE_DIR" checkout --quiet "$BRANCH" 2>/dev/null \
|
||||
|| git -C "$SOURCE_DIR" checkout --quiet -B "$BRANCH" "origin/$BRANCH"
|
||||
git -C "$SOURCE_DIR" reset --hard --quiet "origin/$BRANCH"
|
||||
ok "checked out $(git -C "$SOURCE_DIR" rev-parse --short HEAD) ($(git -C "$SOURCE_DIR" log -1 --format=%s))"
|
||||
log ""
|
||||
|
||||
# ── 2b. Local patches ────────────────────────────────────────────────────────
|
||||
# Apply *.patch files (in lexical order) from $PATCHES_DIR. This lets you
|
||||
# layer in unmerged fixes (e.g. our spaHandler restoration while the upstream
|
||||
# PR is in review) without maintaining a fork.
|
||||
if [[ "$APPLY_PATCHES" -eq 1 && -d "$PATCHES_DIR" ]]; then
|
||||
shopt -s nullglob
|
||||
patches=( "$PATCHES_DIR"/*.patch )
|
||||
shopt -u nullglob
|
||||
if [[ ${#patches[@]} -gt 0 ]]; then
|
||||
log "applying ${#patches[@]} local patch(es) from $PATCHES_DIR…"
|
||||
for p in "${patches[@]}"; do
|
||||
if git -C "$SOURCE_DIR" apply --check "$p" 2>/dev/null; then
|
||||
git -C "$SOURCE_DIR" apply "$p"
|
||||
ok " applied $(basename "$p")"
|
||||
else
|
||||
# Already applied? Skip silently if reverse-apply works (idempotent re-runs).
|
||||
if git -C "$SOURCE_DIR" apply --reverse --check "$p" 2>/dev/null; then
|
||||
ok " $(basename "$p") already applied (skipped)"
|
||||
else
|
||||
die " $(basename "$p") does not apply cleanly. Inspect the patch and the target file:\n git -C $SOURCE_DIR apply --check $p"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
log "no *.patch files in $PATCHES_DIR (skipping)"
|
||||
fi
|
||||
elif [[ "$APPLY_PATCHES" -eq 0 ]]; then
|
||||
log "patch application disabled (--no-patches)"
|
||||
else
|
||||
log "no patches dir at $PATCHES_DIR (skipping)"
|
||||
fi
|
||||
log ""
|
||||
|
||||
# ── 3. Build ─────────────────────────────────────────────────────────────────
|
||||
log "building frontend (pnpm)…"
|
||||
# Workaround for pnpm + rolldown native bindings on Linux/Windows: hoist
|
||||
# node_modules so @rolldown/binding-* is resolvable from the top level.
|
||||
echo 'node-linker=hoisted' > "$SOURCE_DIR/web/.npmrc"
|
||||
(cd "$SOURCE_DIR/web" && pnpm install --frozen-lockfile && pnpm build) >/dev/null
|
||||
[[ -f "$SOURCE_DIR/web/dist/index.html" ]] || die "frontend build produced no dist/index.html"
|
||||
ok "frontend built ($(du -sh "$SOURCE_DIR/web/dist" | awk '{print $1}'))"
|
||||
|
||||
log "building backend (go)…"
|
||||
(cd "$SOURCE_DIR" && make linux) >/dev/null
|
||||
[[ -f "$SOURCE_DIR/dune-admin-linux" ]] || die "backend build produced no dune-admin-linux"
|
||||
ok "backend built ($(du -sh "$SOURCE_DIR/dune-admin-linux" | awk '{print $1}'))"
|
||||
log ""
|
||||
|
||||
# ── 4. Install into $INSTALL_DIR ─────────────────────────────────────────────
|
||||
log "installing into $INSTALL_DIR (as service user '$SERVICE_USER')…"
|
||||
sudo mkdir -p "$INSTALL_DIR"
|
||||
sudo chown "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
|
||||
|
||||
# Backup existing binary (move to .prev for one-step rollback).
|
||||
if [[ -f "$INSTALL_DIR/dune-admin" ]]; then
|
||||
sudo cp -f "$INSTALL_DIR/dune-admin" "$INSTALL_DIR/dune-admin.prev"
|
||||
fi
|
||||
sudo install -m 0755 -o "$SERVICE_USER" -g "$SERVICE_USER" \
|
||||
"$SOURCE_DIR/dune-admin-linux" "$INSTALL_DIR/dune-admin"
|
||||
|
||||
# Backup existing dist (move to dist.prev for one-step rollback).
|
||||
if [[ -d "$INSTALL_DIR/dist" ]]; then
|
||||
sudo rm -rf "$INSTALL_DIR/dist.prev"
|
||||
sudo mv "$INSTALL_DIR/dist" "$INSTALL_DIR/dist.prev"
|
||||
fi
|
||||
sudo cp -r "$SOURCE_DIR/web/dist" "$INSTALL_DIR/dist"
|
||||
sudo chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR/dist"
|
||||
|
||||
# Data files (only copy if newer or missing — these change less often).
|
||||
for f in item-data.json quality-data.json tags-data.json; do
|
||||
if [[ -f "$SOURCE_DIR/$f" ]]; then
|
||||
sudo install -m 0644 -o "$SERVICE_USER" -g "$SERVICE_USER" \
|
||||
"$SOURCE_DIR/$f" "$INSTALL_DIR/$f"
|
||||
fi
|
||||
done
|
||||
ok "installed: $(ls -la "$INSTALL_DIR/dune-admin" | awk '{print $NF, $5, "bytes"}')"
|
||||
log ""
|
||||
|
||||
# ── 4b. systemd unit ─────────────────────────────────────────────────────────
|
||||
# Write (or repair) the unit with Restart=always. This is REQUIRED for in-app
|
||||
# self-update: after swapping the binary the process re-execs/exits, and only
|
||||
# Restart=always reliably brings it back on a clean exit (a hand-made unit with
|
||||
# Restart=on-failure leaves the service down after an update). We deliberately
|
||||
# do NOT enable/start here — the service needs config.yaml from the setup
|
||||
# wizard first — but we DO restart it if it is already enabled (re-install).
|
||||
UNIT_PATH="/etc/systemd/system/dune-admin.service"
|
||||
log "writing systemd unit $UNIT_PATH (Restart=always)…"
|
||||
sudo tee "$UNIT_PATH" >/dev/null <<UNIT
|
||||
[Unit]
|
||||
Description=Dune Admin
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$SERVICE_USER
|
||||
Group=$SERVICE_USER
|
||||
WorkingDirectory=$INSTALL_DIR
|
||||
ExecStart=$INSTALL_DIR/dune-admin
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNIT
|
||||
sudo systemctl daemon-reload
|
||||
if systemctl is-enabled --quiet dune-admin 2>/dev/null; then
|
||||
log "existing service detected — restarting onto the new binary…"
|
||||
sudo systemctl restart dune-admin || warn "restart failed; check: sudo journalctl -u dune-admin -e"
|
||||
fi
|
||||
ok "systemd unit installed (Restart=always)"
|
||||
log ""
|
||||
|
||||
# ── 5. Next steps ────────────────────────────────────────────────────────────
|
||||
cat <<EOF
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
install complete. dune-admin binary + SPA are in $INSTALL_DIR.
|
||||
|
||||
NEXT STEPS (each is intentionally manual so you can review):
|
||||
|
||||
1) RUN THE SETUP WIZARD to generate ~/.dune-admin/config.yaml
|
||||
|
||||
cd $INSTALL_DIR
|
||||
./dune-admin -setup
|
||||
|
||||
Select 'amp' as the control plane. Have these handy:
|
||||
- AMP instance name (e.g. DuneAwakening01) — run \`sudo -u amp ampinstmgr -l\`
|
||||
- OS user that runs AMP (typically 'amp')
|
||||
- PostgreSQL password (AMP module sets this during instance creation)
|
||||
|
||||
2) APPLY SUDOERS GRANTS — the wizard prints an example at the end.
|
||||
Save it to /etc/sudoers.d/dune-admin and validate:
|
||||
|
||||
sudo visudo -c
|
||||
|
||||
Without this, the Server Settings tab cannot write the INI files.
|
||||
|
||||
3) START THE SERVICE — the systemd unit is already installed at
|
||||
/etc/systemd/system/dune-admin.service (Restart=always, User=$SERVICE_USER).
|
||||
After the setup wizard has written the config, enable and start it:
|
||||
|
||||
sudo systemctl enable --now dune-admin
|
||||
sudo journalctl -u dune-admin -f # tail logs
|
||||
|
||||
Browse to http://<this-host>:9090 (or whatever listen_addr you chose).
|
||||
|
||||
NOTE: this installer writes the unit with Restart=always, which is required
|
||||
for in-app self-update (Settings → Check for Updates) to restart cleanly.
|
||||
|
||||
ROLLBACK (if something is wrong):
|
||||
|
||||
sudo systemctl stop dune-admin
|
||||
sudo cp $INSTALL_DIR/dune-admin.prev $INSTALL_DIR/dune-admin
|
||||
sudo rm -rf $INSTALL_DIR/dist && sudo mv $INSTALL_DIR/dist.prev $INSTALL_DIR/dist
|
||||
sudo systemctl start dune-admin
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────────
|
||||
EOF
|
||||
42
docs/reference-repos/icehunter/scripts/patches/README.md
Normal file
42
docs/reference-repos/icehunter/scripts/patches/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# scripts/patches/
|
||||
|
||||
`install.sh` applies every `*.patch` file in this directory (in lexical
|
||||
order) to the source tree after the `git checkout`. Use this slot for:
|
||||
|
||||
- **Unmerged upstream fixes** you want to layer in while a PR is in review.
|
||||
- **Operator-local modifications** (custom branding, internal tweaks)
|
||||
that you don't want to commit upstream.
|
||||
|
||||
## How patches are applied
|
||||
|
||||
The script runs `git apply --check` first; if the patch fails *and* a
|
||||
reverse-apply succeeds, the patch is assumed to be already applied and
|
||||
is skipped silently. Otherwise the install aborts and prints the
|
||||
`git apply --check` output so you can debug.
|
||||
|
||||
Re-running the install is safe — the source tree is reset to the
|
||||
upstream branch before patches are applied, so you always get the same
|
||||
result.
|
||||
|
||||
## How to generate a patch
|
||||
|
||||
From a working tree that already contains the change you want to ship:
|
||||
|
||||
```bash
|
||||
git diff origin/<branch> -- <files> > scripts/patches/0099-my-change.patch
|
||||
# Sanity-check that it applies clean on a fresh checkout:
|
||||
git stash && git apply --check scripts/patches/0099-my-change.patch && git stash pop
|
||||
```
|
||||
|
||||
Number patches `NNNN-name.patch` (4 digits) so lexical order matches
|
||||
intended apply order if patches depend on each other.
|
||||
|
||||
## How to opt out
|
||||
|
||||
```bash
|
||||
./install.sh --no-patches
|
||||
# or point at an alternate directory:
|
||||
./install.sh --patches-dir /path/to/other/patches
|
||||
```
|
||||
|
||||
This directory ships empty in the repo. Operators populate it as needed.
|
||||
Reference in New Issue
Block a user