diff --git a/manage-server/compose.yaml b/manage-server/compose.yaml index c456c00..4a75c54 100644 --- a/manage-server/compose.yaml +++ b/manage-server/compose.yaml @@ -64,7 +64,6 @@ services: volumes: - triton-manage-bins:/bins - ${TLS_CERT_HOST_DIR:-/etc/triton/tls}:/etc/triton/tls:ro - - /etc/machine-id:/etc/machine-id:ro ports: - "${TRITON_MANAGE_HOST_PORT:-8082}:8082" - "${TRITON_MANAGE_GATEWAY_HOST_PORT:-8443}:8443" diff --git a/manage-server/env.template b/manage-server/env.template index c99cffa..f88f8a4 100644 --- a/manage-server/env.template +++ b/manage-server/env.template @@ -54,18 +54,5 @@ TLS_CERT_HOST_DIR=/etc/triton/tls # ─── Sessions ──────────────────────────────────────────────────────────── TRITON_MANAGE_SESSION_TTL=24h -# ─── Licence ───────────────────────────────────────────────────────────── -# Offline licence token from your vendor bundle (license.lic). Set by -# install.sh automatically — do not edit manually. -TRITON_LICENSE_KEY= - -# Vendor's Ed25519 public key (64 hex chars). Baked into the image at -# build time — leave empty unless you need to override the compiled-in key. -TRITON_MANAGE_LICENSE_SERVER_PUBKEY= - -# Vendor's License Server URL. Optional — enables ongoing heartbeats and -# binary sync. Leave empty for fully air-gapped deployments. -TRITON_LICENSE_SERVER_URL= - # ─── Image ─────────────────────────────────────────────────────────────── TRITON_MANAGE_IMAGE=ghcr.io/primatekuntech/triton-manage-server:latest diff --git a/manage-server/install.sh b/manage-server/install.sh index 9a80fca..7f66d9e 100755 --- a/manage-server/install.sh +++ b/manage-server/install.sh @@ -5,42 +5,32 @@ # Container-based via Podman or Docker (auto-detected). # # Usage: -# sudo bash install.sh --license-file /path/to/bundle/license.lic +# sudo bash install.sh # -# The license bundle (provided by your vendor) is a single file: -# license.lic — signed offline licence token -# The vendor's public key is baked into the image at build time. -# -# Flags: -# --license-file PATH Path to license.lic from your vendor bundle. Required. +# Flags (all optional): # --gateway-hostname HOST Agent mTLS hostname (defaults to current FQDN). # --manage-host-ip IP Host LAN IP — used for "+ This machine". -# --port PORT Host port for the web UI (default: 8082). # --image TAG Pin a specific manage-server image tag. # --no-tls Skip the TLS-required sanity check (dev). set -euo pipefail -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" &>/dev/null && pwd)" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" cd "$SCRIPT_DIR" info() { printf '[manage-server] %s\n' "$*"; } die() { printf '[manage-server] error: %s\n' "$*" >&2; exit 1; } # ── arg parsing ────────────────────────────────────────────────────────── -LICENSE_FILE="" GATEWAY_HOST="" HOST_IP="" -PORT="" IMAGE="" NO_TLS=0 while [[ $# -gt 0 ]]; do case "$1" in - --license-file) LICENSE_FILE="$2"; shift 2 ;; - --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 ;; + --gateway-hostname) GATEWAY_HOST="$2"; shift 2 ;; + --manage-host-ip) HOST_IP="$2"; shift 2 ;; + --image) IMAGE="$2"; shift 2 ;; + --no-tls) NO_TLS=1; shift ;; -h|--help) grep '^#' "$0" | sed 's/^# //;s/^#//'; exit 0 ;; *) die "unknown flag: $1 (try --help)" ;; esac @@ -48,12 +38,6 @@ done [[ $EUID -eq 0 ]] || die "must run as root" -# ── 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")" - # ── runtime detection ──────────────────────────────────────────────────── if command -v podman-compose >/dev/null 2>&1; then COMPOSE=(podman-compose) @@ -87,15 +71,11 @@ if [[ ! -f "$ENV_FILE" ]]; then -e "s|^TRITON_MANAGE_WORKER_KEY=.*|TRITON_MANAGE_WORKER_KEY=$WORKER_KEY|" \ -e "s|^TRITON_VAULT_KEY=.*|TRITON_VAULT_KEY=$VAULT_KEY|" \ "$ENV_FILE" - info "secrets generated" - - sed -i "s|^TRITON_LICENSE_KEY=.*|TRITON_LICENSE_KEY=$LICENSE_TOKEN|" "$ENV_FILE" - info "licence configured" + info "vault key generated (PostgreSQL AES-256-GCM)" [[ -n "$GATEWAY_HOST" ]] && sed -i "s|^TRITON_MANAGE_GATEWAY_HOSTNAME=.*|TRITON_MANAGE_GATEWAY_HOSTNAME=$GATEWAY_HOST|" "$ENV_FILE" - [[ -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" + [[ -n "$HOST_IP" ]] && sed -i "s|^TRITON_MANAGE_HOST_IP=.*|TRITON_MANAGE_HOST_IP=$HOST_IP|" "$ENV_FILE" + [[ -n "$IMAGE" ]] && sed -i "s|^TRITON_MANAGE_IMAGE=.*|TRITON_MANAGE_IMAGE=$IMAGE|" "$ENV_FILE" info ".env created at $ENV_FILE" info " back this up — it contains the JWT signing key, worker key, and vault key" @@ -103,10 +83,6 @@ else info "reusing existing .env at $ENV_FILE" fi -# ── pull latest image ──────────────────────────────────────────────────── -info "pulling latest image..." -"${COMPOSE[@]}" --env-file "$ENV_FILE" pull manage-server - # ── start ──────────────────────────────────────────────────────────────── info "starting containers..." "${COMPOSE[@]}" --env-file "$ENV_FILE" up -d @@ -118,6 +94,7 @@ 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") + # 302 (redirect to setup or login) means the server is up. if [[ "$CODE" == "302" || "$CODE" == "200" ]]; then info "manage server is up" break @@ -128,29 +105,9 @@ done info "" info "Installation complete. Next steps:" info " 1. Open http://localhost:${HOST_PORT} (or your public URL)" -info " 2. Complete the setup wizard" +info " 2. Complete the setup wizard:" +info " - Set your manage server name" +info " - Enter your Triton licence server URL and licence ID" +info " - Or upload an air-gap licence file" info " 3. Configure TLS via reverse proxy (see docs)" info "" - -# ── 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. -if [[ -f /etc/machine-id ]]; then - 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 - 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 diff --git a/manage-server/uninstall.sh b/manage-server/uninstall.sh index f677ce3..52d87af 100755 --- a/manage-server/uninstall.sh +++ b/manage-server/uninstall.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash -# uninstall.sh — stop and remove Manage Server containers and image. +# uninstall.sh — stop and remove Manage Server containers. # # By default, KEEPS the PostgreSQL volume (scan history, hosts, users). # Pass --purge-data to delete the volumes as well — irreversible. # # Usage: -# sudo bash uninstall.sh # stop + remove containers + image, keep DB +# sudo bash uninstall.sh # stop + remove containers, keep DB # sudo bash uninstall.sh --purge-data # also delete DB + binaries volume set -euo pipefail -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" &>/dev/null && pwd)" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" cd "$SCRIPT_DIR" info() { printf '[manage-server] %s\n' "$*"; } @@ -39,15 +39,11 @@ else podman rm -f triton-manageserver triton-manage-db 2>/dev/null || true fi -# ── remove image ────────────────────────────────────────────────────────── -IMAGE=$(grep -E '^TRITON_MANAGE_IMAGE=' .env 2>/dev/null | cut -d= -f2) -IMAGE=${IMAGE:-ghcr.io/primatekuntech/triton-manage-server:latest} -info "removing image ${IMAGE}..." -podman rmi "$IMAGE" 2>/dev/null || docker rmi "$IMAGE" 2>/dev/null || true - if [[ $PURGE -eq 1 ]]; then info "DESTRUCTIVE: removing manage server volumes..." info " this deletes: scan history, hosts, users, worker binaries" + read -r -p " Are you sure? Type 'yes' to confirm: " CONFIRM + [[ "$CONFIRM" == "yes" ]] || die "aborted" for vol in triton-manage-db-data triton-manage-bins; do podman volume rm -f "$vol" 2>/dev/null \ || docker volume rm -f "$vol" 2>/dev/null \ diff --git a/manage-server/upgrade.sh b/manage-server/upgrade.sh index 8101ec2..ffc2668 100755 --- a/manage-server/upgrade.sh +++ b/manage-server/upgrade.sh @@ -1,16 +1,14 @@ #!/usr/bin/env bash # upgrade.sh — pull the latest manage-server image and restart. # -# Takes a pre-upgrade pg_dump backup. DB schema migrations run automatically -# on container startup — no manual migration step required. +# Takes a pre-upgrade pg_dump. DB schema migrations run on startup. # # Usage: # sudo bash upgrade.sh # latest from ghcr.io -# sudo bash upgrade.sh --image TAG # pin a specific image tag -# sudo bash upgrade.sh --port PORT # change the web UI host port +# sudo bash upgrade.sh --image TAG # pin a specific image set -euo pipefail -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" &>/dev/null && pwd)" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" cd "$SCRIPT_DIR" info() { printf '[manage-server] %s\n' "$*"; } @@ -19,74 +17,48 @@ die() { printf '[manage-server] error: %s\n' "$*" >&2; exit 1; } [[ $EUID -eq 0 ]] || die "must run as root" [[ -f .env ]] || die ".env not found — run install.sh first" -# ── arg parsing ─────────────────────────────────────────────────────────── IMAGE="" -PORT="" while [[ $# -gt 0 ]]; do case "$1" in --image) IMAGE="$2"; shift 2 ;; - --port) PORT="$2"; shift 2 ;; -h|--help) grep '^#' "$0" | sed 's/^# //;s/^#//'; exit 0 ;; *) die "unknown flag: $1" ;; esac done -# ── 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"; fi - -# ── pin image if requested ──────────────────────────────────────────────── if [[ -n "$IMAGE" ]]; then sed -i "s|^TRITON_MANAGE_IMAGE=.*|TRITON_MANAGE_IMAGE=$IMAGE|" .env info "pinned image to $IMAGE" fi -if [[ -n "$PORT" ]]; then - sed -i "s|^TRITON_MANAGE_HOST_PORT=.*|TRITON_MANAGE_HOST_PORT=$PORT|" .env - info "host port set to $PORT" -fi -# ── pre-upgrade DB backup ───────────────────────────────────────────────── -case "$(uname -s)" in - Linux) BACKUP_DIR="/var/backups/triton" ;; - Darwin) BACKUP_DIR="${HOME}/Library/Application Support/triton/backups" ;; - *) BACKUP_DIR="$SCRIPT_DIR/backups" ;; -esac -mkdir -p "$BACKUP_DIR" -DUMP_FILE="${BACKUP_DIR}/manage-pre-upgrade-$(date +%F-%H%M%S).sql.gz" +if command -v podman-compose >/dev/null 2>&1; then COMPOSE=(podman-compose) +elif podman compose version >/dev/null 2>&1; then COMPOSE=(podman compose) +elif docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose) +else die "no compose runtime found"; fi info "pre-upgrade DB backup..." -PG_USER=$(grep -E '^POSTGRES_USER=' .env | cut -d= -f2) -PG_USER=${PG_USER:-triton} -PG_DB=$(grep -E '^POSTGRES_DB=' .env | cut -d= -f2) -PG_DB=${PG_DB:-triton_manage} +mkdir -p /var/backups/triton +DUMP_FILE="/var/backups/triton/manage-pre-upgrade-$(date +%F-%H%M%S).sql.gz" +podman exec triton-manage-db pg_dump -U triton triton_manage 2>/dev/null \ + | gzip > "$DUMP_FILE" || die "pg_dump failed — aborting upgrade" +info " saved: $DUMP_FILE" -"$RUNTIME" exec triton-manage-db pg_dump -U "$PG_USER" "$PG_DB" 2>/dev/null \ - | gzip > "$DUMP_FILE" || die "pg_dump failed — aborting upgrade (DB container may not be running)" -info " backup saved: $DUMP_FILE" - -# ── pull new image ──────────────────────────────────────────────────────── info "pulling latest image..." "${COMPOSE[@]}" --env-file .env pull manage-server -# ── recreate container (DB migrations run on startup) ───────────────────── info "recreating manage-server container..." -info " DB schema migrations will run automatically on startup" "${COMPOSE[@]}" --env-file .env up -d --no-deps manage-server -# ── wait for healthy (confirms migrations succeeded) ────────────────────── HOST_PORT=$(grep -E '^TRITON_MANAGE_HOST_PORT=' .env | cut -d= -f2) HOST_PORT=${HOST_PORT:-8082} -info "waiting for server to become healthy on :${HOST_PORT}..." +info "waiting for new container to become healthy..." 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 - info "upgrade complete — server is healthy (migrations applied)" - info " rollback if needed: gunzip -c ${DUMP_FILE} | $RUNTIME exec -i triton-manage-db psql -U ${PG_USER} ${PG_DB}" + info "upgrade complete" exit 0 fi sleep 2 done -die "server did not become healthy in 60s — check logs: $RUNTIME logs triton-manageserver" +die "new container did not become healthy in 60s — check logs"