CI / test (push) Successful in 2m17s
The installer no longer opens an editor or starts the service on a fresh install. It writes the files, then prints: sudoedit the env file, systemctl start, journalctl -f. --start starts (or restarts) the service straight away, and an already running service is restarted on upgrade. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
212 lines
7.6 KiB
Bash
212 lines
7.6 KiB
Bash
#!/bin/sh
|
|
# Installs (or upgrades) Megalodon as a systemd service.
|
|
#
|
|
# curl -fsSL https://git.veillax.com/Veillax/megalodon/raw/branch/main/deploy/install.sh | sudo sh
|
|
# sudo sh install.sh [--start] [--version v0.1.0] [--listen 127.0.0.1:8480] [--source]
|
|
# sudo sh install.sh --uninstall [--purge]
|
|
#
|
|
# What it does:
|
|
# 1. downloads the release archive for this machine and checks its SHA-256 (or, with --source,
|
|
# clones the repository and builds it with Go),
|
|
# 2. installs the binary to /usr/local/bin/megalodon,
|
|
# 3. creates a "megalodon" user, /etc/megalodon (settings) and /var/lib/megalodon (data),
|
|
# 4. writes /etc/megalodon/megalodon.env and the systemd service,
|
|
# 5. tells you what to do next: edit the env file, start the service, watch the log.
|
|
#
|
|
# Running it again upgrades the binary and keeps your settings; a service that is already running
|
|
# is restarted. Add --start to start it straight away instead of editing the env file first.
|
|
set -eu
|
|
|
|
REPO_URL=${MEGALODON_REPO_URL:-https://git.veillax.com/Veillax/megalodon}
|
|
API_URL=${MEGALODON_API_URL:-https://git.veillax.com/api/v1/repos/Veillax/megalodon}
|
|
BIN=/usr/local/bin/megalodon
|
|
CONF_DIR=/etc/megalodon
|
|
DATA_DIR=/var/lib/megalodon
|
|
ENV_FILE=$CONF_DIR/megalodon.env
|
|
CONF_FILE=$CONF_DIR/megalodon.json
|
|
UNIT=/etc/systemd/system/megalodon.service
|
|
USER_NAME=megalodon
|
|
|
|
VERSION=""
|
|
LISTEN="127.0.0.1:8480"
|
|
SOURCE=0
|
|
START=0
|
|
UNINSTALL=0
|
|
PURGE=0
|
|
|
|
say() { printf '%s\n' "$*"; }
|
|
die() { printf 'install.sh: %s\n' "$*" >&2; exit 1; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case $1 in
|
|
--version) VERSION=${2:?--version needs a value}; shift ;;
|
|
--listen) LISTEN=${2:?--listen needs a value}; shift ;;
|
|
--source) SOURCE=1 ;;
|
|
--start) START=1 ;;
|
|
--uninstall) UNINSTALL=1 ;;
|
|
--purge) PURGE=1 ;;
|
|
-h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) die "unknown option $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ "$(id -u)" -eq 0 ] || die "run as root (sudo sh install.sh)"
|
|
[ "$(uname -s)" = Linux ] || die "this installer is for Linux; on Windows, download the .zip from the releases page"
|
|
command -v systemctl >/dev/null 2>&1 || HAVE_SYSTEMD=0
|
|
HAVE_SYSTEMD=${HAVE_SYSTEMD:-1}
|
|
|
|
if [ "$UNINSTALL" -eq 1 ]; then
|
|
if [ "$HAVE_SYSTEMD" -eq 1 ]; then
|
|
systemctl disable --now megalodon 2>/dev/null || true
|
|
rm -f "$UNIT"
|
|
systemctl daemon-reload
|
|
fi
|
|
rm -f "$BIN"
|
|
if [ "$PURGE" -eq 1 ]; then
|
|
rm -rf "$CONF_DIR" "$DATA_DIR"
|
|
userdel "$USER_NAME" 2>/dev/null || true
|
|
say "Megalodon removed, including its settings and data."
|
|
else
|
|
say "Megalodon removed. Settings ($CONF_DIR) and data ($DATA_DIR) were kept; add --purge to delete them."
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
fetch() { # fetch URL DEST
|
|
if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1"
|
|
elif command -v wget >/dev/null 2>&1; then wget -qO "$2" "$1"
|
|
else die "need curl or wget"; fi
|
|
}
|
|
|
|
TMP=$(mktemp -d)
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
# ---- 1. get the binary ----
|
|
if [ "$SOURCE" -eq 1 ]; then
|
|
command -v git >/dev/null 2>&1 || die "--source needs git"
|
|
command -v go >/dev/null 2>&1 || die "--source needs Go (https://go.dev/dl/)"
|
|
say "Cloning $REPO_URL ..."
|
|
git clone --quiet ${VERSION:+--branch "$VERSION"} --depth 1 "$REPO_URL" "$TMP/src"
|
|
(cd "$TMP/src" && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=${VERSION:-source}" -o "$TMP/megalodon" ./cmd/megalodon)
|
|
else
|
|
case $(uname -m) in
|
|
x86_64|amd64) ARCH=amd64 ;;
|
|
aarch64|arm64) ARCH=arm64 ;;
|
|
*) die "no release build for $(uname -m); try --source" ;;
|
|
esac
|
|
if [ -z "$VERSION" ]; then
|
|
fetch "$API_URL/releases/latest" "$TMP/latest.json"
|
|
VERSION=$(sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$TMP/latest.json" | head -n 1)
|
|
[ -n "$VERSION" ] || die "could not find the latest release; use --version or --source"
|
|
fi
|
|
ARCHIVE="megalodon_${VERSION}_linux_${ARCH}.tar.gz"
|
|
say "Downloading Megalodon $VERSION ($ARCH) ..."
|
|
fetch "$REPO_URL/releases/download/$VERSION/$ARCHIVE" "$TMP/$ARCHIVE"
|
|
fetch "$REPO_URL/releases/download/$VERSION/SHA256SUMS" "$TMP/SHA256SUMS"
|
|
want=$(grep " $ARCHIVE\$" "$TMP/SHA256SUMS" | cut -d' ' -f1)
|
|
[ -n "$want" ] || die "$ARCHIVE is not listed in SHA256SUMS"
|
|
got=$(sha256sum "$TMP/$ARCHIVE" | cut -d' ' -f1)
|
|
[ "$want" = "$got" ] || die "checksum mismatch for $ARCHIVE; refusing to install"
|
|
tar -xzf "$TMP/$ARCHIVE" -C "$TMP"
|
|
cp "$TMP/megalodon_${VERSION}_linux_${ARCH}/megalodon" "$TMP/megalodon"
|
|
fi
|
|
|
|
# ---- 2. install it ----
|
|
UPGRADE=0
|
|
[ -x "$BIN" ] && UPGRADE=1
|
|
if [ "$UPGRADE" -eq 1 ] && [ "$HAVE_SYSTEMD" -eq 1 ]; then systemctl stop megalodon 2>/dev/null || true; fi
|
|
install -m 0755 "$TMP/megalodon" "$BIN"
|
|
say "Installed $("$BIN" version) to $BIN"
|
|
|
|
# ---- 3. user and folders ----
|
|
id "$USER_NAME" >/dev/null 2>&1 || useradd --system --home-dir "$DATA_DIR" --shell /usr/sbin/nologin "$USER_NAME" 2>/dev/null \
|
|
|| adduser --system --home "$DATA_DIR" --shell /usr/sbin/nologin "$USER_NAME" 2>/dev/null \
|
|
|| die "could not create the $USER_NAME user"
|
|
# The dashboard rewrites its settings file, so the service must own the folder that holds it.
|
|
mkdir -p "$CONF_DIR" "$DATA_DIR"
|
|
chown "$USER_NAME" "$CONF_DIR" "$DATA_DIR"
|
|
chmod 0750 "$CONF_DIR" "$DATA_DIR"
|
|
|
|
# ---- 4. settings and environment file ----
|
|
if [ ! -f "$CONF_FILE" ]; then
|
|
printf '{\n "listen": "%s",\n "data_dir": "%s"\n}\n' "$LISTEN" "$DATA_DIR" > "$CONF_FILE"
|
|
chown "$USER_NAME" "$CONF_FILE"
|
|
chmod 0600 "$CONF_FILE"
|
|
fi
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
cat > "$ENV_FILE" <<EOF
|
|
# Environment for the megalodon service. Restart it after editing: systemctl restart megalodon
|
|
|
|
# Where the settings file is. The dashboard edits this file, so leave it where the service can write.
|
|
MEGALODON_CONFIG=$CONF_FILE
|
|
|
|
# The time zone used by scheduled jobs ("04:30" means 04:30 in this zone). Examples: Europe/London,
|
|
# America/New_York, UTC. Leave it commented to use the server's own zone.
|
|
#TZ=Europe/London
|
|
EOF
|
|
chmod 0600 "$ENV_FILE" # systemd reads it as root, so the service user does not need access
|
|
say "Created $ENV_FILE. Edit it before starting."
|
|
fi
|
|
|
|
# ---- 5. service ----
|
|
if [ "$HAVE_SYSTEMD" -eq 0 ]; then
|
|
say "systemd was not found, so no service was installed. Run it yourself with:"
|
|
say " sudo -u $USER_NAME env \$(grep -v '^#' $ENV_FILE | xargs) $BIN serve -config $CONF_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
cat > "$UNIT" <<EOF
|
|
[Unit]
|
|
Description=Megalodon, ARK server management over RCON
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
User=$USER_NAME
|
|
EnvironmentFile=$ENV_FILE
|
|
WorkingDirectory=$DATA_DIR
|
|
ExecStart=$BIN serve
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
# Megalodon only needs the network and its own folders.
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ReadWritePaths=$CONF_DIR $DATA_DIR
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectControlGroups=true
|
|
RestrictSUIDSGID=true
|
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable megalodon >/dev/null 2>&1
|
|
|
|
if [ "$START" -eq 1 ] || systemctl is-active --quiet megalodon; then
|
|
systemctl restart megalodon
|
|
sleep 3
|
|
if systemctl is-active --quiet megalodon; then
|
|
say "Megalodon is running."
|
|
else
|
|
say "Megalodon did not start. Look at: journalctl -u megalodon -n 50"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
say "Installed $("$BIN" version)."
|
|
say "Next:"
|
|
say " 1. sudoedit $ENV_FILE (the time zone for schedules; the settings file is $CONF_FILE)"
|
|
say " 2. systemctl start megalodon (or run this again with --start)"
|
|
say " 3. journalctl -u megalodon -f to watch it; a first install prints the admin password here, once"
|
|
say ""
|
|
say "The dashboard is at http://$LISTEN"
|
|
case $LISTEN in
|
|
127.*|localhost*|[::1]*) say "It only listens on this machine. To reach it elsewhere, put an HTTPS reverse proxy (or Hammerhead) in front of it." ;;
|
|
esac
|