Add install.sh, deploy.conf and ISO output

Deploying from removable media needed a single entry point that does not
require remembering a long flag line at a console.

install.sh reads deploy.conf, installs missing prerequisites with dnf, checks
that the share is actually mounted, optionally caps the ZFS ARC, runs the
deploy and can start the server. It copies itself off read-only media to the
home directory first, since the deploy needs somewhere writable. --check
validates the machine and changes nothing.

ACCEPT_EULA must be set to yes in the config; the installer will not infer it.

build.sh --iso produces a bootable-media image via xorrisofs/genisoimage/
mkisofs, carrying a ready-to-edit deploy.conf and a README.FIRST.txt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-08-06 00:39:38 +02:00
parent 6c85e30cc8
commit c36b54c7d3
4 changed files with 314 additions and 0 deletions

View File

@ -34,6 +34,34 @@ Send the players `https://<your-host>/setup-windows-packwiz.html` or
`setup-linux-packwiz.html`. Those are generated with your real pack URL and
server address already filled in.
## Deploying from an ISO or USB stick
```bash
./build.sh --iso # -> dist/mc-packwiz-deploy-<version>.iso
```
Burn or `dd` it, mount it on the target, then:
```bash
mkdir -p ~/mc-packwiz-deploy
cp -r /run/media/*/mc-packwiz-deploy/. ~/mc-packwiz-deploy/
cd ~/mc-packwiz-deploy && chmod +x *.sh *.py
$EDITOR deploy.conf # set MIRROR_URL and ACCEPT_EULA at minimum
./install.sh --check # verify the machine is ready
./install.sh
```
`install.sh` reads everything from `deploy.conf`, so there are no flags to type
at a console. It installs missing prerequisites with `dnf`, optionally caps the
ZFS ARC, runs the deploy, and can start the server. `--check` validates
prerequisites and the mount without changing anything.
The one thing it will not do is guess about the EULA: `ACCEPT_EULA` must be set
to `yes` in the config, or it stops and points at the licence.
It also refuses to run when the share is not a mountpoint, since deploying onto
an unmounted path would fill the root disk instead of the pool.
## Re-running
`deploy.sh` is safe to run repeatedly, and re-running is the intended way to

View File

@ -3,6 +3,7 @@
# Build the release zip.
#
# ./build.sh -> dist/mc-packwiz-deploy-<version>.zip
# ./build.sh --iso -> also dist/mc-packwiz-deploy-<version>.iso
# ./build.sh -o /tmp -> writes there instead
# ./build.sh -v 1.2.0 -> override the version
#
@ -15,12 +16,15 @@ set -euo pipefail
VERSION="0.1.0"
NAME="mc-packwiz-deploy"
OUTDIR=""
MAKE_ISO=0
HERE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# Everything the zip ships. Anything not listed here stays out, so stray notes
# or test output in the working tree can't leak into a release.
FILES=(
README.md
install.sh
deploy.conf.example
deploy.sh
mc-service-setup.sh
packwiz-setup.sh
@ -35,6 +39,7 @@ usage() { awk 'NR>1 && /^#/ {sub(/^# ?/,""); print; next} NR>1 {exit}' "$0"; exi
while [ $# -gt 0 ]; do
case "$1" in
-o|--outdir) OUTDIR="${2:-}"; shift 2 ;;
--iso) MAKE_ISO=1; shift ;;
-v|--version) VERSION="${2:-}"; VERSION_FORCED=1; shift 2 ;;
-h|--help) usage 0 ;;
*) echo "unknown option: $1" >&2; usage 1 ;;
@ -104,3 +109,54 @@ unzip -l "$ZIP" | sed 's/^/ /'
if command -v sha256sum >/dev/null; then
sha256sum "$ZIP" | tee "$ZIP.sha256" | sed 's/^/ /'
fi
# -------------------------------------------------------------------- iso ---
if (( MAKE_ISO )); then
ISO="$OUTDIR/${NAME}-${VERSION}.iso"
mkisofs=""
for c in xorrisofs genisoimage mkisofs; do
command -v "$c" >/dev/null && { mkisofs="$c"; break; }
done
[ -n "$mkisofs" ] || die "no ISO tool found — sudo dnf install xorriso"
# A ready-to-edit config goes on the media so the target host only needs
# the file opened and one command run.
cp "$HERE/deploy.conf.example" "$DEST/deploy.conf"
cat > "$DEST/README.FIRST.txt" <<'TXT'
Minecraft + packwiz hosting kit
===============================
The media is read-only, so install.sh copies itself to your home directory
before doing anything.
1. Mount this media, then copy the kit somewhere writable:
mkdir -p ~/mc-packwiz-deploy
cp -r /run/media/*/mc-packwiz-deploy/. ~/mc-packwiz-deploy/
cd ~/mc-packwiz-deploy && chmod +x *.sh *.py
2. Edit deploy.conf. At minimum set MIRROR_URL and ACCEPT_EULA.
3. Check the machine is ready:
./install.sh --check
4. Install:
./install.sh
Requires a ZFS dataset already mounted at the SHARE path in deploy.conf.
install.sh refuses to run otherwise, rather than filling the root disk.
TXT
rm -f "$ISO"
"$mkisofs" -quiet -o "$ISO" -V "MCPACKWIZ" -J -r "$STAGE" \
|| die "$mkisofs failed"
info "built $ISO"
ls -lh "$ISO" | sed 's/^/ /'
command -v sha256sum >/dev/null && sha256sum "$ISO" | tee "$ISO.sha256" | sed 's/^/ /'
fi

34
deploy.conf.example Normal file
View File

@ -0,0 +1,34 @@
# Deployment settings. Copy to deploy.conf next to install.sh and edit.
# install.sh reads this, so there are no flags to remember on the target host.
# --- identity ---------------------------------------------------------------
PACK_NAME="AbdelpakDelta" # display name; the directory slug is derived
AUTHOR="themiro"
MC_VERSION="1.21.1"
NEOFORGE="latest" # or pin e.g. 21.1.248
# --- where things live ------------------------------------------------------
SHARE="/minecraft" # ZFS dataset mountpoint
# --- how clients reach it ---------------------------------------------------
# The public URL of the mods directory. Everything else is derived from it:
# the pack URL, the guide URLs, and the address shown to players.
MIRROR_URL="https://games.abdelbaki.eu/abdelpakDelta/mods"
# --- ports ------------------------------------------------------------------
MC_PORT="27960" # Minecraft
HTTP_PORT="18080" # pack/mod HTTP server, behind your proxy
HTTP_BIND="0.0.0.0" # 127.0.0.1 if the proxy is on this host
# --- memory -----------------------------------------------------------------
XMX="10G"
XMS="10G"
# --- Minecraft EULA ---------------------------------------------------------
# Setting this to yes writes eula=true and is your acceptance of
# https://aka.ms/MinecraftEULA. Leave it as no and the install stops there.
ACCEPT_EULA="no"
# --- optional ---------------------------------------------------------------
START_SERVER="no" # yes to start Minecraft at the end
CAP_ZFS_ARC="2147483648" # bytes; empty to leave ARC alone

196
install.sh Executable file
View File

@ -0,0 +1,196 @@
#!/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 <<EOF
Kit installed in: $WORKDIR
Pack HTTP server: ${HTTP_BIND}:${HTTP_PORT} (proxy / -> 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