2026-05-17 08:57:58 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# install.sh — Triton Manage Server installer.
|
|
|
|
|
#
|
|
|
|
|
# Idempotent. Generates secrets on first run, reuses .env afterwards.
|
|
|
|
|
# Container-based via Podman or Docker (auto-detected).
|
|
|
|
|
#
|
|
|
|
|
# Usage:
|
2026-05-19 23:29:19 +08:00
|
|
|
# sudo bash install.sh --license-file /path/to/bundle/license.lic
|
2026-05-17 08:57:58 +02:00
|
|
|
#
|
2026-05-20 00:08:17 +08:00
|
|
|
# The license bundle (provided by your vendor) is a single file:
|
2026-05-19 23:29:19 +08:00
|
|
|
# license.lic — signed offline licence token
|
2026-05-20 00:08:17 +08:00
|
|
|
# The vendor's public key is baked into the image at build time.
|
2026-05-19 23:29:19 +08:00
|
|
|
#
|
|
|
|
|
# Flags:
|
|
|
|
|
# --license-file PATH Path to license.lic from your vendor bundle. Required.
|
2026-05-17 08:57:58 +02:00
|
|
|
# --gateway-hostname HOST Agent mTLS hostname (defaults to current FQDN).
|
|
|
|
|
# --manage-host-ip IP Host LAN IP — used for "+ This machine".
|
2026-05-19 19:45:00 +08:00
|
|
|
# --port PORT Host port for the web UI (default: 8082).
|
2026-05-17 08:33:44 +00:00
|
|
|
# --image TAG Pin a specific manage-server image tag.
|
2026-05-17 08:57:58 +02:00
|
|
|
# --no-tls Skip the TLS-required sanity check (dev).
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
2026-05-19 19:38:47 +08:00
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" &>/dev/null && pwd)"
|
2026-05-17 08:57:58 +02:00
|
|
|
cd "$SCRIPT_DIR"
|
|
|
|
|
|
|
|
|
|
info() { printf '[manage-server] %s\n' "$*"; }
|
|
|
|
|
die() { printf '[manage-server] error: %s\n' "$*" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
# ── arg parsing ──────────────────────────────────────────────────────────
|
2026-05-19 23:29:19 +08:00
|
|
|
LICENSE_FILE=""
|
2026-05-17 08:57:58 +02:00
|
|
|
GATEWAY_HOST=""
|
|
|
|
|
HOST_IP=""
|
2026-05-19 19:45:00 +08:00
|
|
|
PORT=""
|
2026-05-17 08:57:58 +02:00
|
|
|
IMAGE=""
|
|
|
|
|
NO_TLS=0
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
2026-05-19 23:29:19 +08:00
|
|
|
--license-file) LICENSE_FILE="$2"; shift 2 ;;
|
2026-05-19 23:10:06 +08:00
|
|
|
--gateway-hostname) GATEWAY_HOST="$2"; shift 2 ;;
|
|
|
|
|
--manage-host-ip) HOST_IP="$2"; shift 2 ;;
|
|
|
|
|
--port) PORT="$2"; shift 2 ;;
|
|
|
|
|
--image) IMAGE="$2"; shift 2 ;;
|
|
|
|
|
--no-tls) NO_TLS=1; shift ;;
|
2026-05-17 08:57:58 +02:00
|
|
|
-h|--help) grep '^#' "$0" | sed 's/^# //;s/^#//'; exit 0 ;;
|
|
|
|
|
*) die "unknown flag: $1 (try --help)" ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
[[ $EUID -eq 0 ]] || die "must run as root"
|
|
|
|
|
|
2026-05-19 23:29:19 +08:00
|
|
|
# ── validate license bundle ──────────────────────────────────────────────
|
|
|
|
|
[[ -n "$LICENSE_FILE" ]] || die "--license-file is required (path to license.lic from your vendor bundle)"
|
|
|
|
|
[[ -f "$LICENSE_FILE" ]] || die "license file not found: $LICENSE_FILE"
|
|
|
|
|
|
|
|
|
|
LICENSE_TOKEN="$(cat "$LICENSE_FILE")"
|
|
|
|
|
|
2026-05-17 08:57:58 +02:00
|
|
|
# ── runtime detection ────────────────────────────────────────────────────
|
|
|
|
|
if command -v podman-compose >/dev/null 2>&1; then
|
|
|
|
|
COMPOSE=(podman-compose)
|
|
|
|
|
RUNTIME=podman
|
|
|
|
|
elif podman compose version >/dev/null 2>&1; then
|
|
|
|
|
COMPOSE=(podman compose)
|
|
|
|
|
RUNTIME=podman
|
|
|
|
|
elif docker compose version >/dev/null 2>&1; then
|
|
|
|
|
COMPOSE=(docker compose)
|
|
|
|
|
RUNTIME=docker
|
|
|
|
|
else
|
|
|
|
|
die "no compose runtime found. Install podman-compose or docker compose."
|
|
|
|
|
fi
|
|
|
|
|
info "using runtime: $RUNTIME"
|
|
|
|
|
|
|
|
|
|
# ── .env bootstrap ───────────────────────────────────────────────────────
|
|
|
|
|
ENV_FILE="$SCRIPT_DIR/.env"
|
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
|
|
|
info "writing .env from env.template"
|
|
|
|
|
cp env.template "$ENV_FILE"
|
|
|
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
|
|
|
|
|
|
PG_PASS=$(openssl rand -hex 24)
|
|
|
|
|
JWT_KEY=$(openssl rand -hex 32)
|
|
|
|
|
WORKER_KEY=$(openssl rand -hex 16)
|
|
|
|
|
VAULT_KEY=$(openssl rand -hex 32)
|
|
|
|
|
|
|
|
|
|
sed -i \
|
|
|
|
|
-e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$PG_PASS|" \
|
|
|
|
|
-e "s|^TRITON_MANAGE_JWT_SIGNING_KEY=.*|TRITON_MANAGE_JWT_SIGNING_KEY=$JWT_KEY|" \
|
|
|
|
|
-e "s|^TRITON_MANAGE_WORKER_KEY=.*|TRITON_MANAGE_WORKER_KEY=$WORKER_KEY|" \
|
|
|
|
|
-e "s|^TRITON_VAULT_KEY=.*|TRITON_VAULT_KEY=$VAULT_KEY|" \
|
|
|
|
|
"$ENV_FILE"
|
2026-05-19 23:29:19 +08:00
|
|
|
info "secrets generated"
|
|
|
|
|
|
2026-05-20 00:08:17 +08:00
|
|
|
sed -i "s|^TRITON_LICENSE_KEY=.*|TRITON_LICENSE_KEY=$LICENSE_TOKEN|" "$ENV_FILE"
|
2026-05-19 23:29:19 +08:00
|
|
|
info "licence configured"
|
|
|
|
|
|
2026-05-20 07:59:56 +02:00
|
|
|
[[ -n "$GATEWAY_HOST" ]] && sed -i "s|^TRITON_MANAGE_GATEWAY_HOSTNAME=.*|TRITON_MANAGE_GATEWAY_HOSTNAME=$GATEWAY_HOST|" "$ENV_FILE"
|
2026-05-19 23:29:19 +08:00
|
|
|
[[ -n "$HOST_IP" ]] && sed -i "s|^TRITON_MANAGE_HOST_IP=.*|TRITON_MANAGE_HOST_IP=$HOST_IP|" "$ENV_FILE"
|
|
|
|
|
[[ -n "$PORT" ]] && sed -i "s|^TRITON_MANAGE_HOST_PORT=.*|TRITON_MANAGE_HOST_PORT=$PORT|" "$ENV_FILE"
|
|
|
|
|
[[ -n "$IMAGE" ]] && sed -i "s|^TRITON_MANAGE_IMAGE=.*|TRITON_MANAGE_IMAGE=$IMAGE|" "$ENV_FILE"
|
2026-05-17 08:57:58 +02:00
|
|
|
|
|
|
|
|
info ".env created at $ENV_FILE"
|
2026-05-17 08:33:44 +00:00
|
|
|
info " back this up — it contains the JWT signing key, worker key, and vault key"
|
2026-05-17 08:57:58 +02:00
|
|
|
else
|
|
|
|
|
info "reusing existing .env at $ENV_FILE"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-19 19:50:53 +08:00
|
|
|
# ── pull latest image ────────────────────────────────────────────────────
|
|
|
|
|
info "pulling latest image..."
|
|
|
|
|
"${COMPOSE[@]}" --env-file "$ENV_FILE" pull manage-server
|
|
|
|
|
|
2026-05-17 08:57:58 +02:00
|
|
|
# ── start ────────────────────────────────────────────────────────────────
|
|
|
|
|
info "starting containers..."
|
|
|
|
|
"${COMPOSE[@]}" --env-file "$ENV_FILE" up -d
|
|
|
|
|
|
|
|
|
|
# ── wait for health ──────────────────────────────────────────────────────
|
|
|
|
|
HOST_PORT=$(grep -E '^TRITON_MANAGE_HOST_PORT=' "$ENV_FILE" | cut -d= -f2)
|
|
|
|
|
HOST_PORT=${HOST_PORT:-8082}
|
|
|
|
|
|
|
|
|
|
info "waiting for manage server to become healthy on :${HOST_PORT}..."
|
|
|
|
|
for i in $(seq 1 30); do
|
|
|
|
|
CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${HOST_PORT}/" || echo "000")
|
|
|
|
|
if [[ "$CODE" == "302" || "$CODE" == "200" ]]; then
|
2026-05-17 08:33:44 +00:00
|
|
|
info "manage server is up"
|
2026-05-17 08:57:58 +02:00
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
sleep 2
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
info ""
|
2026-05-17 08:33:44 +00:00
|
|
|
info "Installation complete. Next steps:"
|
2026-05-17 08:57:58 +02:00
|
|
|
info " 1. Open http://localhost:${HOST_PORT} (or your public URL)"
|
2026-05-19 23:29:19 +08:00
|
|
|
info " 2. Complete the setup wizard"
|
2026-05-17 08:33:44 +00:00
|
|
|
info " 3. Configure TLS via reverse proxy (see docs)"
|
2026-05-17 08:57:58 +02:00
|
|
|
info ""
|
2026-05-20 10:19:44 +08:00
|
|
|
|
2026-05-20 07:29:01 +02:00
|
|
|
# ── machine ID ───────────────────────────────────────────────────────────
|
|
|
|
|
# Print the SHA-3-256 hash of /etc/machine-id so the customer can share
|
|
|
|
|
# it with the vendor when requesting an offline .lic bound to this host.
|
|
|
|
|
# The hash is stable: /etc/machine-id never changes after OS installation.
|
2026-05-20 10:19:44 +08:00
|
|
|
if [[ -f /etc/machine-id ]]; then
|
2026-05-20 07:29:01 +02:00
|
|
|
MACHINE_ID_RAW=$(cat /etc/machine-id | tr -d '[:space:]')
|
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
|
|
|
MACHINE_ID_HASH=$(python3 -c "import hashlib; print(hashlib.sha3_256('${MACHINE_ID_RAW}'.encode()).hexdigest())")
|
|
|
|
|
elif command -v sha3sum >/dev/null 2>&1; then
|
|
|
|
|
MACHINE_ID_HASH=$(echo -n "$MACHINE_ID_RAW" | sha3sum -a 256 | awk '{print $1}')
|
|
|
|
|
elif command -v openssl >/dev/null 2>&1; then
|
|
|
|
|
MACHINE_ID_HASH=$(printf '%s' "${MACHINE_ID_RAW}" | openssl dgst -sha3-256 -hex 2>/dev/null | awk '{print $2}')
|
|
|
|
|
else
|
|
|
|
|
MACHINE_ID_HASH=""
|
|
|
|
|
fi
|
2026-05-20 10:19:44 +08:00
|
|
|
if [[ -n "$MACHINE_ID_HASH" ]]; then
|
|
|
|
|
info "── Host Machine ID ──────────────────────────────────────────────────────"
|
|
|
|
|
info " Provide this value to your vendor when requesting a host-bound .lic file."
|
|
|
|
|
info " Machine ID (SHA-3-256): $MACHINE_ID_HASH"
|
|
|
|
|
info "────────────────────────────────────────────────────────────────────────"
|
|
|
|
|
fi
|
|
|
|
|
fi
|