morphit/ops/scripts/install-systemd-units.sh

136 lines
5.2 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# install-systemd-units.sh — install Morphit's core systemd units,
# pointed at THIS checkout wherever it happens to live.
#
# Why this exists
# ---------------
# The shipped unit files in ops/systemd/ reference /opt/morphit by
# default (the path morphit-ops uses). If you cloned the repo
# somewhere else — e.g. ~/morphit following the manual guide —
# copying the units verbatim would point WorkingDirectory/ExecStart
# at a path that doesn't exist, and the services would fail to start.
# Rather than hand-editing each unit (or layering a `systemctl edit`
# drop-in), this script detects the real repo root from its own
# location and writes the three core units to /etc/systemd/system/
# with the correct paths substituted in. Run it from wherever you
# cloned; it just works.
#
# Scope
# -----
# Handles the three services that run out of the monorepo checkout:
# - morphit-indexer (the Blurt-chain indexer + REST API)
# - morphit-relay (broadcasts user-signed chain ops)
# - morphit-matrix-bot (operator alert DMs; only if you run it)
#
# It deliberately does NOT touch morphit-mcp: it runs from its own
# restricted directory as a separate low-privilege user (a
# least-privilege isolation so it can't read the main install's
# secrets). If you run it, install its unit per
# docs/OPERATIONS.md — that isolation is intentional.
#
# Usage
# -----
# sudo bash ops/scripts/install-systemd-units.sh
# then enable the services you actually run, e.g.:
# sudo systemctl enable --now morphit-indexer
# sudo systemctl enable --now morphit-relay
#
# Note: services run as a non-root user (matrix-bot) need their
# WorkingDirectory readable by that user. Under /opt/morphit that's
# automatic; if you cloned into a home directory, make sure the path
# is traversable (a home on the root filesystem with normal perms is
# fine; an encrypted or 0700 home is not).
#
set -euo pipefail
# Repo root = two levels up from this script (ops/scripts/ -> repo root).
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
UNIT_SRC="$REPO_DIR/ops/systemd"
UNIT_DEST="/etc/systemd/system"
# The base path the shipped units hardcode. Every occurrence is
# rewritten to $REPO_DIR. This is the ONLY thing the installer
# changes in each unit.
DEFAULT_DIR="/opt/morphit"
# The three monorepo services (see "Scope" above).
CORE_UNITS=(
morphit-indexer.service
morphit-relay.service
morphit-matrix-bot.service
)
echo "Morphit systemd unit installer"
echo " repo: $REPO_DIR"
echo " target: $UNIT_DEST"
echo
if [[ ! -d "$UNIT_SRC" ]]; then
echo "ERROR: $UNIT_SRC not found — run this from inside the Morphit repo." >&2
exit 1
fi
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
echo "ERROR: must run as root (writes to $UNIT_DEST)." >&2
echo " Try: sudo bash $0" >&2
exit 1
fi
installed=0
for unit in "${CORE_UNITS[@]}"; do
src="$UNIT_SRC/$unit"
dest="$UNIT_DEST/$unit"
if [[ ! -f "$src" ]]; then
echo " skip $unit (not found in repo)"
continue
fi
# Rewrite the hardcoded base path to this checkout. '#' delimiter
# so the '/' in paths needs no escaping.
sed "s#${DEFAULT_DIR}#${REPO_DIR}#g" "$src" >"$dest"
if [[ "$REPO_DIR" == "$DEFAULT_DIR" ]]; then
echo " installed $unit (paths already $DEFAULT_DIR)"
else
echo " installed $unit ($DEFAULT_DIR -> $REPO_DIR)"
fi
installed=$((installed + 1))
done
if [[ "$installed" -eq 0 ]]; then
echo "Nothing installed." >&2
exit 1
fi
systemctl daemon-reload
echo
echo "Done ($installed unit(s) written, systemd reloaded)."
echo
echo "Enable + start the services you run:"
echo " sudo systemctl enable --now morphit-indexer"
echo " sudo systemctl enable --now morphit-relay"
echo " sudo systemctl enable --now morphit-matrix-bot # only if you run the alert bot"
echo
echo "Optional surfaces — deliberately NOT installed above because they run"
echo "ISOLATED (their own low-priv user + dir, so they can't read the main"
echo "install's secrets). The Ansible playbook sets both up by default:"
echo
echo " - MCP server (AI-agent discovery): deploy to /opt/morphit-mcp, then enable:"
echo " sudo bash ops/scripts/deploy-mcp.sh"
echo " sudo useradd --system --no-create-home --shell /usr/sbin/nologin morphit-mcp 2>/dev/null || true"
echo " # point it at YOUR instance (else it surfaces the default morphit.io):"
echo " echo 'MORPHIT_MCP_INSTANCE_URL=https://YOUR-DOMAIN' | sudo tee /etc/morphit/mcp.env >/dev/null"
echo " sudo cp ops/systemd/morphit-mcp.service /etc/systemd/system/ && sudo systemctl daemon-reload"
echo " sudo systemctl enable --now morphit-mcp"
echo " # toggle it on/off any time with: sudo morphit-ops mcp"
echo
echo " - Web Push: generate a VAPID keypair ONCE (rotating it drops all"
echo " subscriptions) into the file the relay sources, then restart the relay:"
echo " sudo bash scripts/generate-vapid-keys.sh --bare --subject https://YOUR-DOMAIN \\"
echo " | sudo tee /etc/morphit/relay-vapid.env >/dev/null"
echo " sudo chmod 640 /etc/morphit/relay-vapid.env"
echo " sudo systemctl restart morphit-relay"
echo
echo "If any of these were running manually (not under systemd), stop those"
echo "processes first so the systemd-managed ones can bind their ports."