#!/usr/bin/env bash
set -euo pipefail
# Ensure acme.sh and cron renewal run under root's home, not the invoking user's
HOME=/root
# ── Configuration ────────────────────────────────────────────────────────────
NATS_VERSION="${NATS_VERSION:?NATS_VERSION must be set, e.g. export NATS_VERSION=2.10.22}"
NATS_BIN=/usr/local/bin/nats-server
NATS_CONF_DIR=/etc/nats
NATS_DATA_DIR=/var/nats
CF_TOKEN_FILE="${NATS_CONF_DIR}/cf-token.env"
DOMAIN=sync.craftdesign.group
RELOAD_CMD="systemctl kill --signal=SIGHUP nats"
# ── Helpers ───────────────────────────────────────────────────────────────────
log() { echo "[install.sh] $*"; }
die() { echo "[install.sh] ERROR: $*" >&2; exit 1; }
# ── Step 1: Download and install nats-server binary ──────────────────────────
if [[ -x "$NATS_BIN" ]] && "$NATS_BIN" --version 2>/dev/null | grep -q "v${NATS_VERSION}$"; then
log "nats-server $NATS_VERSION already installed, skipping download."
else
log "Downloading nats-server v${NATS_VERSION} linux/arm64..."
ARCHIVE="nats-server-v${NATS_VERSION}-linux-arm64.tar.gz"
URL="https://github.com/nats-io/nats-server/releases/download/v${NATS_VERSION}/${ARCHIVE}"
TMP=$(mktemp -d)
curl -fsSL "$URL" -o "${TMP}/${ARCHIVE}"
tar -xzf "${TMP}/${ARCHIVE}" -C "$TMP"
install -m 0755 "${TMP}/nats-server-v${NATS_VERSION}-linux-arm64/nats-server" "$NATS_BIN"
rm -rf "$TMP"
log "nats-server installed to $NATS_BIN"
fi
# ── Step 2: Create nats system user ─────────────────────────────────────────
if ! id nats &>/dev/null; then
useradd --system --no-create-home --shell /usr/sbin/nologin nats
log "Created nats system user."
else
log "nats user already exists, skipping."
fi
# ── Step 3: Create data directories ─────────────────────────────────────────
for DIR in "${NATS_DATA_DIR}/jetstream" "${NATS_DATA_DIR}/jwt" "${NATS_DATA_DIR}/tls"; do
mkdir -p "$DIR"
chown nats:nats "$DIR"
chmod 0750 "$DIR"
log "Directory ready: $DIR"
done
# ── Step 4: Create /etc/nats and copy config template ───────────────────────
mkdir -p "$NATS_CONF_DIR"
chown root:root "$NATS_CONF_DIR"
chmod 0755 "$NATS_CONF_DIR"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cp "${SCRIPT_DIR}/nats-server.conf.template" "${NATS_CONF_DIR}/nats-server.conf.template"
log "Config template copied to ${NATS_CONF_DIR}/nats-server.conf.template"
# ── Step 5: Verify Cloudflare token file ────────────────────────────────────
[[ -f "$CF_TOKEN_FILE" ]] || die \
"Missing $CF_TOKEN_FILE. Create it with: echo 'CF_Token=\"<token>\"' > $CF_TOKEN_FILE && chmod 0600 $CF_TOKEN_FILE"
[[ "$(stat -c %a "$CF_TOKEN_FILE")" == "600" ]] || \
{ chmod 0600 "$CF_TOKEN_FILE"; log "Fixed permissions on $CF_TOKEN_FILE"; }
# ── Step 6: Install acme.sh and issue TLS cert ──────────────────────────────
ACME="${HOME}/.acme.sh/acme.sh"
if [[ ! -x "$ACME" ]]; then
log "Installing acme.sh..."
curl -fsSL https://get.acme.sh | sh -s [email protected]
fi
CERT_FILE="${NATS_DATA_DIR}/tls/${DOMAIN}.crt"
KEY_FILE="${NATS_DATA_DIR}/tls/${DOMAIN}.key"
# shellcheck source=/dev/null
source "$CF_TOKEN_FILE"
export CF_Token
if "$ACME" --list 2>/dev/null | grep -q "$DOMAIN"; then
log "Certificate for $DOMAIN already exists in acme.sh store."
else
log "Issuing TLS certificate for $DOMAIN via DNS-01..."
"$ACME" --issue --dns dns_cf -d "$DOMAIN" --server letsencrypt --ecc
fi
# Always install cert to target paths (ensures files exist on re-runs)
"$ACME" --install-cert -d "$DOMAIN" --ecc \
--key-file "$KEY_FILE" \
--fullchain-file "$CERT_FILE" \
--reloadcmd "$RELOAD_CMD"
chown nats:nats "$CERT_FILE" "$KEY_FILE"
chmod 0640 "$CERT_FILE" "$KEY_FILE"
log "TLS cert and key written to $NATS_DATA_DIR/tls/ and owned by nats:nats"
# ── Step 7: Install systemd unit ────────────────────────────────────────────
cp "${SCRIPT_DIR}/nats.service" /etc/systemd/system/nats.service
systemctl daemon-reload
log "nats.service installed and daemon reloaded."
log ""
log "Phase 1 complete."
log "Next: run bootstrap-creds.sh (as dmestas, not root)"