#!/usr/bin/env bash # # Single entry point for deploying from removable media. # # ./install.sh reads deploy.conf next to this script # ./install.sh -c other.conf reads a different config # ./install.sh --check verify prerequisites and exit # # Copies itself off read-only media, installs prerequisites, and runs the # deploy. Everything is configured in deploy.conf, so there are no flags to # remember at the console. set -euo pipefail HERE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CONF="$HERE/deploy.conf" CHECK_ONLY=0 WORKDIR="" die() { printf '\n\033[1;31merror:\033[0m %s\n\n' "$*" >&2; exit 1; } step() { printf '\n\033[1m=== %s ===\033[0m\n\n' "$*"; } info() { echo ">>> $*"; } warn() { echo " warning: $*" >&2; } while [ $# -gt 0 ]; do case "$1" in -c|--config) CONF="${2:-}"; shift 2 ;; --check) CHECK_ONLY=1; shift ;; -w|--workdir) WORKDIR="${2:-}"; shift 2 ;; -h|--help) awk 'NR>1 && /^#/ {sub(/^# ?/,""); print; next} NR>1 {exit}' "$0"; exit 0 ;; *) die "unknown option: $1" ;; esac done [ "$(id -u)" -ne 0 ] || die "run this as your normal user, not root — it calls sudo itself" # ------------------------------------------------------------------ config --- [ -f "$CONF" ] || die "no config at $CONF Copy deploy.conf.example to deploy.conf and edit it first." # shellcheck source=/dev/null . "$CONF" for v in PACK_NAME AUTHOR MIRROR_URL SHARE; do [ -n "${!v:-}" ] || die "$v is not set in $CONF" done MC_VERSION="${MC_VERSION:-1.21.1}" NEOFORGE="${NEOFORGE:-latest}" MC_PORT="${MC_PORT:-25565}" HTTP_PORT="${HTTP_PORT:-18080}" HTTP_BIND="${HTTP_BIND:-0.0.0.0}" XMX="${XMX:-10G}" XMS="${XMS:-$XMX}" ACCEPT_EULA="${ACCEPT_EULA:-no}" START_SERVER="${START_SERVER:-no}" CAP_ZFS_ARC="${CAP_ZFS_ARC:-}" # ------------------------------------------------------------- prequisites --- step "Checking prerequisites" MISSING=() need() { command -v "$1" >/dev/null || MISSING+=("$2"); } need java java-21-openjdk-headless need curl curl need go golang need python3 python3 need zfs zfs need findmnt util-linux need mkfifo coreutils if [ "${#MISSING[@]}" -gt 0 ]; then info "missing: ${MISSING[*]}" if (( CHECK_ONLY )); then die "prerequisites missing (see above)" fi info "installing with dnf" sudo dnf install -y "${MISSING[@]}" \ || die "could not install: ${MISSING[*]} Install them by hand and re-run." else info "all present" fi jver="$(java -version 2>&1 | head -1 | grep -oE '"[0-9]+' | tr -d '"' || echo 0)" [ "${jver:-0}" -ge 21 ] || warn "java is $jver; NeoForge for 1.21.x wants 21" if ! mountpoint -q "$SHARE"; then die "$SHARE is not a mountpoint — the ZFS dataset is not mounted. Import and mount the pool first: sudo zpool import -a && sudo zfs mount -a Then re-run. Deploying onto an unmounted path would fill the root disk." fi info "$SHARE is mounted ($(findmnt -no SOURCE "$SHARE"))" if (( CHECK_ONLY )); then step "Prerequisites OK" exit 0 fi # --------------------------------------------------------------- workspace --- # The media is very likely read-only, and the deploy needs somewhere writable # for the go build and the pack. Copy the kit onto the host first. if [ -z "$WORKDIR" ]; then if [ -w "$HERE" ]; then WORKDIR="$HERE" else WORKDIR="$HOME/mc-packwiz-deploy" info "media is read-only — copying the kit to $WORKDIR" mkdir -p "$WORKDIR" cp -f "$HERE"/*.sh "$HERE"/*.py "$WORKDIR"/ 2>/dev/null || true cp -f "$CONF" "$WORKDIR/deploy.conf" chmod +x "$WORKDIR"/*.sh "$WORKDIR"/*.py 2>/dev/null || true fi fi cd "$WORKDIR" [ -x ./deploy.sh ] || die "deploy.sh not found in $WORKDIR" # ----------------------------------------------------------------- zfs arc --- if [ -n "$CAP_ZFS_ARC" ]; then if [ ! -f /etc/modprobe.d/zfs.conf ] || ! grep -q zfs_arc_max /etc/modprobe.d/zfs.conf; then step "Capping the ZFS ARC at $CAP_ZFS_ARC bytes" echo "options zfs zfs_arc_max=$CAP_ZFS_ARC" | sudo tee /etc/modprobe.d/zfs.conf >/dev/null warn "the ARC cap needs a reboot (and 'sudo dracut --force') to take effect" else info "ZFS ARC already capped in /etc/modprobe.d/zfs.conf" fi fi # ------------------------------------------------------------------ deploy --- EULA_FLAG="" case "${ACCEPT_EULA,,}" in yes|true|1) EULA_FLAG="--accept-eula" ;; *) die "ACCEPT_EULA is not set to yes in $CONF. Read https://aka.ms/MinecraftEULA, then set ACCEPT_EULA=\"yes\" to accept it." ;; esac step "Deploying $PACK_NAME" ./deploy.sh \ -n "$PACK_NAME" \ -a "$AUTHOR" \ -u "$MIRROR_URL" \ -m "$MC_VERSION" \ -N "$NEOFORGE" \ -s "$SHARE" \ -p "$MC_PORT" \ -H "$HTTP_PORT" \ -B "$HTTP_BIND" \ -X "$XMX" \ -x "$XMS" \ $EULA_FLAG # ------------------------------------------------------------------- start --- case "${START_SERVER,,}" in yes|true|1) step "Starting Minecraft" sudo systemctl start minecraft sleep 3 if systemctl is-active --quiet minecraft; then info "minecraft.service is running" else warn "minecraft.service did not come up — journalctl -u minecraft -n 50" fi ;; esac # ------------------------------------------------------------------- done ---- SITE="${MIRROR_URL%/*}" HOSTONLY="$(sed -e 's#^[a-z]*://##' -e 's#/.*##' -e 's#:.*##' <<<"$SITE")" [ "$MC_PORT" = "25565" ] && SHOWN="$HOSTONLY" || SHOWN="$HOSTONLY:$MC_PORT" step "Done" cat < this) Players connect to: $SHOWN Setup guides: $SITE/setup-windows-packwiz.html $SITE/setup-linux-packwiz.html Still to do by hand: 1. Point your reverse proxy at ${HTTP_BIND}:${HTTP_PORT} 2. Drop mod jars into $SHARE/mods 3. Run $WORKDIR/mc-refresh-restart.sh to register them sudo systemctl start minecraft journalctl -u minecraft -f echo "op YourName" | sudo tee /run/minecraft/console EOF