Add install.sh: install or upgrade Megalodon as a systemd service
CI / test (push) Successful in 2m15s
CI / test (push) Successful in 2m15s
Downloads the release for this machine and verifies its checksum (or builds from source), creates the service user and folders, writes and opens the environment file, installs the hardened unit and prints the first-run admin password. Re-running upgrades; --uninstall [--purge] removes it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d2cb31abf
commit
81e3f73521
2 files changed
+239
-1
No files matched your search
@@ -59,7 +59,22 @@ password you set there is the RCON password.
|
|||||||
admin password. Keep it on your LAN or a VPN; only Megalodon needs to reach it. If Megalodon and
|
admin password. Keep it on your LAN or a VPN; only Megalodon needs to reach it. If Megalodon and
|
||||||
the server are on the same machine or Docker network, nothing needs to be opened at all.
|
the server are on the same machine or Docker network, nothing needs to be opened at all.
|
||||||
|
|
||||||
## Running it
|
## Installing on Linux (systemd)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://git.veillax.com/Veillax/megalodon/raw/branch/main/deploy/install.sh | sudo sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads the latest release (checking its SHA-256), installs `/usr/local/bin/megalodon`,
|
||||||
|
creates a `megalodon` user, `/etc/megalodon/` (settings) and `/var/lib/megalodon/` (data), opens
|
||||||
|
`/etc/megalodon/megalodon.env` in your editor, then starts the service and prints the first-run
|
||||||
|
admin password. Run it again to upgrade; your settings are kept.
|
||||||
|
|
||||||
|
Options: `--version v0.1.0`, `--listen 0.0.0.0:8480`, `--source` (build from the repository with Go
|
||||||
|
instead), `--no-edit`, `--no-start`, and `--uninstall` (add `--purge` to delete settings and data).
|
||||||
|
Download the script and read it first if you prefer: it is [`deploy/install.sh`](deploy/install.sh).
|
||||||
|
|
||||||
|
## Running it by hand
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# download a release, or: go build ./cmd/megalodon
|
# download a release, or: go build ./cmd/megalodon
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
#!/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 [--version v0.1.0] [--listen 127.0.0.1:8480] [--source] [--no-edit] [--no-start]
|
||||||
|
# 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 opens it in your editor,
|
||||||
|
# 5. installs and starts the systemd service, and prints the first-run admin password.
|
||||||
|
#
|
||||||
|
# Running it again upgrades the binary and keeps your settings.
|
||||||
|
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
|
||||||
|
EDIT=1
|
||||||
|
START=1
|
||||||
|
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 ;;
|
||||||
|
--no-edit) EDIT=0 ;;
|
||||||
|
--no-start) START=0 ;;
|
||||||
|
--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 0640 "$ENV_FILE"
|
||||||
|
chgrp "$USER_NAME" "$ENV_FILE" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$EDIT" -eq 1 ] && [ -r /dev/tty ]; then
|
||||||
|
ED=${VISUAL:-${EDITOR:-}}
|
||||||
|
[ -n "$ED" ] || for c in nano vim vi; do command -v "$c" >/dev/null 2>&1 && { ED=$c; break; }; done
|
||||||
|
if [ -n "$ED" ]; then
|
||||||
|
say "Opening $ENV_FILE in $ED ..."
|
||||||
|
$ED "$ENV_FILE" </dev/tty >/dev/tty || say "(the editor exited with an error; continuing)"
|
||||||
|
fi
|
||||||
|
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 ]; 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
|
||||||
|
pw=$(journalctl -u megalodon --no-pager 2>/dev/null | sed -n 's/.*first run: created user "admin" with password \([^ ]*\).*/\1/p' | tail -n 1)
|
||||||
|
if [ "$UPGRADE" -eq 0 ] && [ -n "$pw" ]; then
|
||||||
|
say ""
|
||||||
|
say " Sign in as: admin"
|
||||||
|
say " Password: $pw (shown once; change it under Settings > Users)"
|
||||||
|
fi
|
||||||
|
say ""
|
||||||
|
say "Dashboard: http://$LISTEN"
|
||||||
|
say "Logs: journalctl -u megalodon -f"
|
||||||
|
say "Settings: $CONF_FILE and $ENV_FILE"
|
||||||
|
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
|
||||||
|
fi
|
||||||
Reference in new issue
Block a user