From 81e3f73521913e2ddbc44c508d8c02494be64684 Mon Sep 17 00:00:00 2001 From: Veillax <126523099+Veillax@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:21:53 -0600 Subject: [PATCH] Add install.sh: install or upgrade Megalodon as a systemd service 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 --- README.md | 17 +++- deploy/install.sh | 223 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 deploy/install.sh diff --git a/README.md b/README.md index 8337fdc..f7ca9c7 100644 --- a/README.md +++ b/README.md @@ -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 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 # download a release, or: go build ./cmd/megalodon diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100644 index 0000000..aa69508 --- /dev/null +++ b/deploy/install.sh @@ -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" </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 || 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" </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