feat(install): license bundle approach — --license-file replaces pubkey fetch

Remove auto-fetching pubkey from license server. Instead the vendor ships
a bundle (license.lic + pubkey) and the installer reads both files from
the same directory. Works for both online and air-gapped deployments.
--license-server-url is now optional (heartbeats only, not required to start).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
amir-climy 2026-05-19 23:29:19 +08:00
parent bbdc8aa292
commit 9780ee3601
3 changed files with 60 additions and 36 deletions

View file

@ -4,31 +4,39 @@ Production installer for the Triton Manage Server. Container-based (Docker or Po
## Install ## Install
```bash Your vendor provides a licence bundle — a folder containing two files:
curl -fsSL https://raw.githubusercontent.com/primatekuntech/triton-install/main/get.sh | sudo bash
```
triton-bundle/
├── license.lic # signed offline licence token
└── pubkey # vendor's Ed25519 public key
``` ```
That's it. The setup wizard walks you through the rest. Point the installer at the bundle:
```bash
curl -fsSL https://raw.githubusercontent.com/primatekuntech/triton-install/main/get.sh | sudo bash -s -- --license-file /path/to/triton-bundle/license.lic
```
## Setup wizard ## Setup wizard
After install, open `http://localhost:8082` and complete the wizard: After install, open `http://localhost:8082` and complete the wizard:
1. Set your manage server name 1. Set your manage server name
2. Enter your Triton licence server URL and licence ID — or upload an air-gap licence file 2. Create the admin account
3. Create the admin account
## Optional flags ## Optional flags
Pass flags after `--`: Pass flags after `--`:
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/primatekuntech/triton-install/main/get.sh | sudo bash -s -- [flags] curl -fsSL https://raw.githubusercontent.com/primatekuntech/triton-install/main/get.sh | sudo bash -s -- --license-file /path/to/license.lic [flags]
``` ```
| Flag | Description | | Flag | Description |
|------|-------------| |------|-------------|
| `--license-server-url URL` | Vendor's License Server URL. Public key is fetched automatically. | | `--license-file PATH` | Path to `license.lic` from your vendor bundle. **Required.** |
| `--license-server-url URL` | License Server URL for ongoing heartbeats (optional, omit for air-gap). |
| `--gateway-hostname HOST` | Agent mTLS hostname (defaults to current FQDN). | | `--gateway-hostname HOST` | Agent mTLS hostname (defaults to current FQDN). |
| `--manage-host-ip IP` | Host LAN IP for "+ This machine" auto-registration. | | `--manage-host-ip IP` | Host LAN IP for "+ This machine" auto-registration. |
| `--port PORT` | Host port for the web UI (default: `8082`). | | `--port PORT` | Host port for the web UI (default: `8082`). |

View file

@ -54,12 +54,17 @@ TLS_CERT_HOST_DIR=/etc/triton/tls
# ─── Sessions ──────────────────────────────────────────────────────────── # ─── Sessions ────────────────────────────────────────────────────────────
TRITON_MANAGE_SESSION_TTL=24h TRITON_MANAGE_SESSION_TTL=24h
# ─── License server ────────────────────────────────────────────────────── # ─── Licence ─────────────────────────────────────────────────────────────
# Vendor's Ed25519 public key (64 hex chars). Required — get this from # Offline licence token from your vendor bundle (license.lic). Set by
# your Triton vendor. The manage server refuses to start without it. # install.sh automatically — do not edit manually.
TRITON_LICENSE_KEY=
# Vendor's Ed25519 public key (64 hex chars). Set by install.sh from the
# pubkey file in your vendor bundle — do not edit manually.
TRITON_MANAGE_LICENSE_SERVER_PUBKEY= TRITON_MANAGE_LICENSE_SERVER_PUBKEY=
# Vendor's License Server URL. Required for activation and heartbeat. # Vendor's License Server URL. Optional — enables ongoing heartbeats and
# binary sync. Leave empty for fully air-gapped deployments.
TRITON_LICENSE_SERVER_URL= TRITON_LICENSE_SERVER_URL=
# ─── Image ─────────────────────────────────────────────────────────────── # ─── Image ───────────────────────────────────────────────────────────────

View file

@ -5,11 +5,16 @@
# Container-based via Podman or Docker (auto-detected). # Container-based via Podman or Docker (auto-detected).
# #
# Usage: # Usage:
# sudo bash install.sh # sudo bash install.sh --license-file /path/to/bundle/license.lic
# #
# Flags (all optional): # The license bundle (provided by your vendor) contains two files:
# --license-server-url URL Vendor's License Server URL. The public key is # license.lic — signed offline licence token
# fetched automatically from the license server. # pubkey — vendor's Ed25519 public key (64 hex chars)
# Both files must be in the same directory.
#
# Flags:
# --license-file PATH Path to license.lic from your vendor bundle. Required.
# --license-server-url URL License Server URL for ongoing heartbeats (optional).
# --gateway-hostname HOST Agent mTLS hostname (defaults to current FQDN). # --gateway-hostname HOST Agent mTLS hostname (defaults to current FQDN).
# --manage-host-ip IP Host LAN IP — used for "+ This machine". # --manage-host-ip IP Host LAN IP — used for "+ This machine".
# --port PORT Host port for the web UI (default: 8082). # --port PORT Host port for the web UI (default: 8082).
@ -24,6 +29,7 @@ info() { printf '[manage-server] %s\n' "$*"; }
die() { printf '[manage-server] error: %s\n' "$*" >&2; exit 1; } die() { printf '[manage-server] error: %s\n' "$*" >&2; exit 1; }
# ── arg parsing ────────────────────────────────────────────────────────── # ── arg parsing ──────────────────────────────────────────────────────────
LICENSE_FILE=""
LICENSE_SERVER_URL="" LICENSE_SERVER_URL=""
GATEWAY_HOST="" GATEWAY_HOST=""
HOST_IP="" HOST_IP=""
@ -32,6 +38,7 @@ IMAGE=""
NO_TLS=0 NO_TLS=0
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--license-file) LICENSE_FILE="$2"; shift 2 ;;
--license-server-url) LICENSE_SERVER_URL="$2"; shift 2 ;; --license-server-url) LICENSE_SERVER_URL="$2"; shift 2 ;;
--gateway-hostname) GATEWAY_HOST="$2"; shift 2 ;; --gateway-hostname) GATEWAY_HOST="$2"; shift 2 ;;
--manage-host-ip) HOST_IP="$2"; shift 2 ;; --manage-host-ip) HOST_IP="$2"; shift 2 ;;
@ -45,6 +52,18 @@ done
[[ $EUID -eq 0 ]] || die "must run as root" [[ $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"
BUNDLE_DIR="$(cd -- "$(dirname -- "$LICENSE_FILE")" && pwd)"
PUBKEY_FILE="$BUNDLE_DIR/pubkey"
[[ -f "$PUBKEY_FILE" ]] || die "pubkey file not found: $PUBKEY_FILE (must be in the same directory as license.lic)"
LICENSE_TOKEN="$(cat "$LICENSE_FILE")"
LICENSE_PUBKEY="$(cat "$PUBKEY_FILE" | tr -d '[:space:]')"
[[ ${#LICENSE_PUBKEY} -eq 64 ]] || die "pubkey file must contain a 64 hex-char Ed25519 public key"
# ── runtime detection ──────────────────────────────────────────────────── # ── runtime detection ────────────────────────────────────────────────────
if command -v podman-compose >/dev/null 2>&1; then if command -v podman-compose >/dev/null 2>&1; then
COMPOSE=(podman-compose) COMPOSE=(podman-compose)
@ -78,23 +97,19 @@ if [[ ! -f "$ENV_FILE" ]]; then
-e "s|^TRITON_MANAGE_WORKER_KEY=.*|TRITON_MANAGE_WORKER_KEY=$WORKER_KEY|" \ -e "s|^TRITON_MANAGE_WORKER_KEY=.*|TRITON_MANAGE_WORKER_KEY=$WORKER_KEY|" \
-e "s|^TRITON_VAULT_KEY=.*|TRITON_VAULT_KEY=$VAULT_KEY|" \ -e "s|^TRITON_VAULT_KEY=.*|TRITON_VAULT_KEY=$VAULT_KEY|" \
"$ENV_FILE" "$ENV_FILE"
info "vault key generated (PostgreSQL AES-256-GCM)" info "secrets generated"
if [[ -n "$LICENSE_SERVER_URL" ]]; then sed -i \
sed -i "s|^TRITON_LICENSE_SERVER_URL=.*|TRITON_LICENSE_SERVER_URL=$LICENSE_SERVER_URL|" "$ENV_FILE" -e "s|^TRITON_MANAGE_LICENSE_SERVER_PUBKEY=.*|TRITON_MANAGE_LICENSE_SERVER_PUBKEY=$LICENSE_PUBKEY|" \
info "fetching public key from license server..." -e "s|^TRITON_LICENSE_KEY=.*|TRITON_LICENSE_KEY=$LICENSE_TOKEN|" \
LICENSE_PUBKEY=$(curl -fsSL "${LICENSE_SERVER_URL}/api/v1/license/pubkey" \ "$ENV_FILE"
| grep -o '"pubkey":"[^"]*"' | cut -d'"' -f4) \ info "licence configured"
|| die "failed to fetch public key from ${LICENSE_SERVER_URL}"
[[ ${#LICENSE_PUBKEY} -eq 64 ]] \ [[ -n "$LICENSE_SERVER_URL" ]] && sed -i "s|^TRITON_LICENSE_SERVER_URL=.*|TRITON_LICENSE_SERVER_URL=$LICENSE_SERVER_URL|" "$ENV_FILE"
|| die "license server returned an invalid public key (expected 64 hex chars)" [[ -n "$GATEWAY_HOST" ]] && sed -i "s|^TRITON_MANAGE_GATEWAY_HOSTNAME=.*|TRITON_MANAGE_GATEWAY_HOSTNAME=$GATEWAY_HOST|" "$ENV_FILE"
sed -i "s|^TRITON_MANAGE_LICENSE_SERVER_PUBKEY=.*|TRITON_MANAGE_LICENSE_SERVER_PUBKEY=$LICENSE_PUBKEY|" "$ENV_FILE" [[ -n "$HOST_IP" ]] && sed -i "s|^TRITON_MANAGE_HOST_IP=.*|TRITON_MANAGE_HOST_IP=$HOST_IP|" "$ENV_FILE"
info "public key configured" [[ -n "$PORT" ]] && sed -i "s|^TRITON_MANAGE_HOST_PORT=.*|TRITON_MANAGE_HOST_PORT=$PORT|" "$ENV_FILE"
fi [[ -n "$IMAGE" ]] && sed -i "s|^TRITON_MANAGE_IMAGE=.*|TRITON_MANAGE_IMAGE=$IMAGE|" "$ENV_FILE"
[[ -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"
info ".env created at $ENV_FILE" info ".env created at $ENV_FILE"
info " back this up — it contains the JWT signing key, worker key, and vault key" info " back this up — it contains the JWT signing key, worker key, and vault key"
@ -117,7 +132,6 @@ HOST_PORT=${HOST_PORT:-8082}
info "waiting for manage server to become healthy on :${HOST_PORT}..." info "waiting for manage server to become healthy on :${HOST_PORT}..."
for i in $(seq 1 30); do for i in $(seq 1 30); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${HOST_PORT}/" || echo "000") 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 if [[ "$CODE" == "302" || "$CODE" == "200" ]]; then
info "manage server is up" info "manage server is up"
break break
@ -128,9 +142,6 @@ done
info "" info ""
info "Installation complete. Next steps:" info "Installation complete. Next steps:"
info " 1. Open http://localhost:${HOST_PORT} (or your public URL)" 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 " 3. Configure TLS via reverse proxy (see docs)"
info "" info ""