release v0.1.5

This commit is contained in:
gpuk-release
2026-09-17 00:02:06 +00:00
parent 406d457b8d
commit 983733003b
4 changed files with 524 additions and 128 deletions
+279 -85
View File
@@ -7,9 +7,12 @@
# channel repo is written by .gitea/workflows/release.yml, see specs/developpement/ci-cd.md.)
#
# What it does, and nothing more:
# 0. existing detect an install already on this host (manifest, or the traces
# a previous one left) and KEEP its settings — ports, data root,
# profile — unless a flag says otherwise (INS-03)
# 1. preflight docker, the NVIDIA driver, a REAL `--gpus all` smoke test, and
# the listening ports (INS-46 — a taken port fails HERE, not three
# minutes later in a health-check timeout)
# the listening ports (INS-46 — a taken port is resolved HERE, on
# the terminal, not three minutes later in a health-check timeout)
# 2. resolve the current release from the channel (a TAG — never a floating
# `latest`: an install that silently changes version under you is
# not an install, it is a surprise)
@@ -47,12 +50,26 @@ CACHE_DIR="${GPUK_CACHE_DIR:-}"
# 1337, not 8080: the single most-squatted port in existence would make the
# conflict preflight fire on half the lab boxes out there (INS-01).
PORT="${GPUK_PORT:-1337}"
# Fixed listeners: mirror of the gpuk-proxy default (core/cluster-settings.ts
# proxyPublicPort) and of the worker mTLS channel — no install-time flag moves
# them (INS-46).
INFERENCE_PORT=8200
MTLS_PORT=8443
# The worker mTLS channel and the gpuk-proxy inference endpoint (defaults mirror
# core/cluster-settings.ts). Movable at install time like the UI port (INS-46):
# 8443 is every second appliance's HTTPS alias and 8200 is HashiCorp Vault's.
MTLS_PORT="${GPUK_MTLS_PORT:-8443}"
INFERENCE_PORT="${GPUK_INFERENCE_PORT:-8200}"
CLUSTER="${GPUK_CLUSTER:-default}"
# Which of those came from the operator (flag or env) — an existing install keeps
# its own value for everything the operator did not ask to change (INS-03).
PORT_GIVEN=0; [ -z "${GPUK_PORT:-}" ] || PORT_GIVEN=1
MTLS_GIVEN=0; [ -z "${GPUK_MTLS_PORT:-}" ] || MTLS_GIVEN=1
INFERENCE_GIVEN=0; [ -z "${GPUK_INFERENCE_PORT:-}" ] || INFERENCE_GIVEN=1
DATA_ROOT_GIVEN=0; [ -z "${GPUK_DATA_ROOT:-}" ] || DATA_ROOT_GIVEN=1
CACHE_GIVEN=0; [ -z "${GPUK_CACHE_DIR:-}" ] || CACHE_GIVEN=1
CLUSTER_GIVEN=0; [ -z "${GPUK_CLUSTER:-}" ] || CLUSTER_GIVEN=1
# Where an existing install keeps its manifest. Same override as gpuk's, and for
# the same reason: it is the only way to exercise the re-run path without root.
ETC_DIR="${GPUK_ETC_DIR:-/etc/gpu-kitchen}"
MANIFEST="$ETC_DIR/manifest.json"
UNIT_DEST="${GPUK_UNIT_DEST:-/etc/systemd/system/gpu-kitchen-worker.service}"
BIN_DEST="${GPUK_BIN_DEST:-/usr/local/bin/gpu-kitchen-worker}"
PROFILE=""
DOMAIN=""
VERSION=""
@@ -102,6 +119,8 @@ Options:
--image <ref> Use this controller image outright (implies --version none)
--edition <ed> community (default) | enterprise
--port <p> Port the UI listens on (default 1337)
--mtls-port <p> Port workers dial to join this controller (default 8443)
--inference-port <p> Port of the OpenAI-compatible inference endpoint (default 8200)
--data-root <path> Where the database and secrets live (default /var/lib/gpu-kitchen)
--cache-dir <path> Model cache (default <data-root>/hf)
--cluster <name> Cluster name workers join (default "default")
@@ -113,7 +132,8 @@ Options:
--worker-binary <p> Use a locally-built gpu-kitchen-worker instead of downloading one
--gpuk-script <p> Use a local copy of the gpuk installer
--skip-gpu-check Skip the 'docker run --gpus all' smoke test
--skip-preflight Skip the host checks entirely (CI: no docker, no GPU)
--skip-preflight Skip the docker, driver, GPU and disk checks (CI: no docker,
no GPU); the listening ports are still checked
--dry-run Run the preflight and resolve the release, change nothing
-h, --help This
EOF
@@ -124,10 +144,12 @@ while [ $# -gt 0 ]; do
--version) VERSION="$2"; shift 2 ;;
--image) IMAGE="$2"; shift 2 ;;
--edition) EDITION="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--data-root) DATA_ROOT="$2"; shift 2 ;;
--cache-dir) CACHE_DIR="$2"; shift 2 ;;
--cluster) CLUSTER="$2"; shift 2 ;;
--port) PORT="$2"; PORT_GIVEN=1; shift 2 ;;
--mtls-port) MTLS_PORT="$2"; MTLS_GIVEN=1; shift 2 ;;
--inference-port) INFERENCE_PORT="$2"; INFERENCE_GIVEN=1; shift 2 ;;
--data-root) DATA_ROOT="$2"; DATA_ROOT_GIVEN=1; shift 2 ;;
--cache-dir) CACHE_DIR="$2"; CACHE_GIVEN=1; shift 2 ;;
--cluster) CLUSTER="$2"; CLUSTER_GIVEN=1; shift 2 ;;
--profile) PROFILE="$2"; shift 2 ;;
--domain) DOMAIN="$2"; shift 2 ;;
--non-interactive) NON_INTERACTIVE=1; shift ;;
@@ -141,8 +163,6 @@ while [ $# -gt 0 ]; do
esac
done
[ -n "$CACHE_DIR" ] || CACHE_DIR="$DATA_ROOT/hf"
case "$EDITION" in
community|enterprise) ;;
*) die "--edition must be community or enterprise (got '$EDITION')" ;;
@@ -157,9 +177,132 @@ case "$DOMAIN" in
*[!A-Za-z0-9.-]*) die "--domain must be a bare domain name (got '$DOMAIN')" ;;
esac
for _pv in "$PORT" "$MTLS_PORT" "$INFERENCE_PORT"; do
case "$_pv" in
''|*[!0-9]*) die "not a port number: '$_pv'" ;;
esac
[ "$_pv" -ge 1 ] && [ "$_pv" -le 65535 ] || die "port out of range: $_pv"
done
echo
echo "${BOLD}GPU Kitchen — installing the $EDITION controller${RESET}"
# ── 0. An existing install (INS-03) ──────────────────────────────────────────
# Re-running this script is the update path, so before checking anything it
# reads what is already here — and KEEPS it. An update that silently moved the
# UI to another port, re-asked the profile (Enter = homelab would downgrade a
# public install) or pointed at a fresh data root beside the real one is not an
# update. Read-only. The manifest is the authority (WRK-55); without it, the
# traces a previous install leaves (container, unit, binary, data root) still
# mean "take over", never "start beside", and the container's own ports are ours.
manifest_str() { # $1 = key of a string field, anywhere in the manifest
sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$MANIFEST" 2>/dev/null | head -1
}
manifest_binding() { # $1 = container port → the host port the ports table publishes it on
sed -n "s/.*\"$1\(\/tcp\)\{0,1\}\"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\2/p" "$MANIFEST" 2>/dev/null | head -1
}
C_STATUS=""; C_NETMODE=""
E_PORT=""; E_MTLS_PORT=""; E_LISTEN_ADDR=""
E_PUBLIC_PORT=""; E_PUBLIC_MTLS_PORT=""; E_PROXY_PUBLIC_PORT=""
B_8080=""; B_8443=""; B_8200=""
container_facts() { # $1 = name → C_*, E_*, B_* from docker; 1 when absent
command -v docker >/dev/null 2>&1 || return 1
_f=$(docker inspect -f '{{.State.Status}}|{{.HostConfig.NetworkMode}}|{{range $p, $b := .HostConfig.PortBindings}}{{range $b}}{{$p}}={{.HostPort}} {{end}}{{end}}{{"\n"}}{{range .Config.Env}}{{.}}{{"\n"}}{{end}}' "$1" 2>/dev/null) \
|| return 1
[ -n "$_f" ] || return 1
_head=$(printf '%s\n' "$_f" | head -1)
C_STATUS=${_head%%|*}; _r=${_head#*|}; C_NETMODE=${_r%%|*}; _bind=${_r#*|}
E_PORT=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_PORT=//p' | head -1)
E_MTLS_PORT=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_MTLS_PORT=//p' | head -1)
E_LISTEN_ADDR=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_LISTEN_ADDR=//p' | head -1)
E_PUBLIC_PORT=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_PUBLIC_PORT=//p' | head -1)
E_PUBLIC_MTLS_PORT=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_PUBLIC_MTLS_PORT=//p' | head -1)
E_PROXY_PUBLIC_PORT=$(printf '%s\n' "$_f" | sed -n 's/^GPUK_PROXY_PUBLIC_PORT=//p' | head -1)
B_8080=$(printf '%s\n' "$_bind" | tr ' ' '\n' | sed -n 's/^8080\/tcp=//p' | head -1)
B_8443=$(printf '%s\n' "$_bind" | tr ' ' '\n' | sed -n 's/^8443\/tcp=//p' | head -1)
B_8200=$(printf '%s\n' "$_bind" | tr ' ' '\n' | sed -n 's/^8200\/tcp=//p' | head -1)
}
# The ports an install is REACHED on, with the precedence the controller itself
# applies (core/published-ports.ts, INS-43): host networking moves the listeners
# (GPUK_PORT, GPUK_MTLS_PORT, GPUK_LISTEN_ADDR); anything else keeps the image's
# fixed listeners and publishes them (GPUK_PUBLIC_*, then the ports table).
published_ports() { # $1 = network mode → OURS_UI OURS_MTLS OURS_INF
if [ "$1" = "host" ]; then
OURS_UI="${E_PORT:-8080}"
OURS_MTLS="${E_MTLS_PORT:-8443}"
OURS_INF="${E_PROXY_PUBLIC_PORT:-${E_LISTEN_ADDR##*:}}"
else
OURS_UI="${E_PUBLIC_PORT:-${B_8080:-8080}}"
OURS_MTLS="${E_PUBLIC_MTLS_PORT:-${B_8443:-8443}}"
OURS_INF="${E_PROXY_PUBLIC_PORT:-${B_8200:-8200}}"
fi
[ -n "$OURS_INF" ] || OURS_INF=8200
}
EXISTING=""; OUR_PORTS=""; CONTAINER_NAME="gpu-kitchen"
OURS_UI=""; OURS_MTLS=""; OURS_INF=""
if [ -f "$MANIFEST" ] && [ ! -r "$MANIFEST" ]; then
EXISTING="unreadable"
elif [ -f "$MANIFEST" ]; then
EXISTING="manifest"
_cn=$(manifest_str containerName); [ -z "$_cn" ] || CONTAINER_NAME="$_cn"
C_NETMODE=$(manifest_str networkMode)
E_PORT=$(manifest_str GPUK_PORT); E_MTLS_PORT=$(manifest_str GPUK_MTLS_PORT)
E_LISTEN_ADDR=$(manifest_str GPUK_LISTEN_ADDR)
E_PUBLIC_PORT=$(manifest_str GPUK_PUBLIC_PORT)
E_PUBLIC_MTLS_PORT=$(manifest_str GPUK_PUBLIC_MTLS_PORT)
E_PROXY_PUBLIC_PORT=$(manifest_str GPUK_PROXY_PUBLIC_PORT)
B_8080=$(manifest_binding 8080); B_8443=$(manifest_binding 8443); B_8200=$(manifest_binding 8200)
published_ports "${C_NETMODE:-host}"
OUR_PORTS="$OURS_UI $OURS_MTLS $OURS_INF"
elif container_facts "$CONTAINER_NAME"; then
EXISTING="leftovers"
published_ports "$C_NETMODE"
OUR_PORTS="$OURS_UI $OURS_MTLS $OURS_INF"
elif [ -f "$UNIT_DEST" ] || [ -x "$BIN_DEST" ] || [ -d "$DATA_ROOT/secrets" ]; then
EXISTING="leftovers"
fi
case "$EXISTING" in
manifest)
step "Existing install — $MANIFEST"
ok "image $(manifest_str image)"
_root=$(manifest_str dataRoot); _cache=$(manifest_str hostPath)
_cluster=$(manifest_str GPUK_CLUSTER); _profile=$(manifest_str GPUK_INSTALL_PROFILE)
[ "$DATA_ROOT_GIVEN" -eq 1 ] || [ -z "$_root" ] || DATA_ROOT="$_root"
[ "$CACHE_GIVEN" -eq 1 ] || [ -z "$_cache" ] || CACHE_DIR="$_cache"
[ "$CLUSTER_GIVEN" -eq 1 ] || [ -z "$_cluster" ] || CLUSTER="$_cluster"
[ "$PORT_GIVEN" -eq 1 ] || PORT="$OURS_UI"
[ "$MTLS_GIVEN" -eq 1 ] || MTLS_PORT="$OURS_MTLS"
[ "$INFERENCE_GIVEN" -eq 1 ] || INFERENCE_PORT="$OURS_INF"
case "$_profile" in
homelab|studio|enterprise|public) [ -n "$PROFILE" ] || PROFILE="$_profile" ;;
esac
ok "data root $DATA_ROOT"
ok "ports UI $OURS_UI, worker channel $OURS_MTLS, inference $OURS_INF"
[ -z "$_profile" ] || ok "profile $_profile"
ok "re-running updates it in place. Its settings are kept unless a flag says otherwise."
;;
unreadable)
step "Existing install — $MANIFEST"
warn "present, but not readable from here: run as root to keep its settings"
;;
leftovers)
step "Existing install — traces of a previous install, no manifest"
if [ -n "$C_STATUS" ]; then
ok "container $CONTAINER_NAME ($C_STATUS; UI $OURS_UI, worker channel $OURS_MTLS, inference $OURS_INF) — the install replaces it"
fi
[ ! -f "$UNIT_DEST" ] || ok "systemd unit $UNIT_DEST — rewritten"
[ ! -x "$BIN_DEST" ] || ok "daemon binary $BIN_DEST — replaced"
[ ! -d "$DATA_ROOT/secrets" ] || ok "data root $DATA_ROOT — reused, nothing in it is touched"
warn "without $MANIFEST no setting can be kept: the flags and the defaults apply"
;;
esac
[ -n "$CACHE_DIR" ] || CACHE_DIR="$DATA_ROOT/hf"
if [ "$PORT" = "$MTLS_PORT" ] || [ "$PORT" = "$INFERENCE_PORT" ] || [ "$MTLS_PORT" = "$INFERENCE_PORT" ]; then
die "the UI, worker channel and inference ports must differ (got $PORT, $MTLS_PORT, $INFERENCE_PORT)"
fi
# ── 1. Preflight ─────────────────────────────────────────────────────────────
# The same checks tools/provision-feeder.sh makes, minus the compose ones: the
# all-in-one image is driven by workerd through the plain docker CLI, so there is
@@ -173,7 +316,10 @@ else
step "Preflight — docker, NVIDIA driver, container toolkit"
[ "$DRY_RUN" -eq 1 ] || [ "$(id -u)" -eq 0 ] \
# Root, or a user-owned prefix (GPUK_ETC_DIR) — the same rule as gpuk's
# need_root, and the only way the full path is testable without handing root
# to a test suite.
[ "$DRY_RUN" -eq 1 ] || [ "$(id -u)" -eq 0 ] || [ -w "$ETC_DIR" ] \
|| die "run as root: curl -fsSL … | sudo sh"
command -v curl >/dev/null 2>&1 || die "curl not found. Install curl first."
@@ -230,7 +376,16 @@ else
warn "only ${FREE_GB:-?}G free under $CACHE_PARENT. Model weights need 100G or more."
fi
# ── Port conflicts (INS-46) ──
fi # end preflight
# ── Listening ports (INS-46) ──────────────────────────────────────────────────
# Deliberately OUTSIDE the preflight branch: --skip-preflight skips docker, the
# driver, the GPU smoke test and the disk (things a runner or a VM cannot have),
# but a taken port is exactly as fatal there, and checking it costs nothing.
# Skipping it here only moved the failure to gpuk's non-interactive refusal.
if [ "$SKIP_PREFLIGHT" -eq 1 ]; then
step "Listening ports — checked even without the preflight"
fi
# A taken port must fail HERE, before anything mutates the host — today's
# alternative is a 3-minute health-check timeout with zero diagnosis. Best-effort
# detection (ss, then netstat); neither present is a warn, never a false red.
@@ -258,82 +413,111 @@ port_busy() { # $1 = port → 0 iff something listens on TCP :$1
esac
}
port_owner() { # $1 = port → best-effort process name (needs root for -p)
case "$PORT_TOOL" in
ss) ss -ltnpH "sport = :$1" 2>/dev/null \
| sed -n 's/.*users:((\"\([^"]*\)\".*/\1/p' | head -1 ;;
netstat) netstat -ltnp 2>/dev/null \
| awk -v p="$1" '{n=split($4,a,":"); if (a[n]==p) {print $NF; exit}}' \
| sed 's|^[0-9]*/||' ;;
esac
# Which container a listener belongs to, if any: the process's cgroup names the
# container id (host networking — the listener IS the container's process), and a
# bridged publication shows up as the container's port mapping in `docker ps`
# (the host-side holder is docker-proxy, which says nothing by itself). "nginx"
# is a riddle; "nginx in container gpu-kitchen-dev" is the answer.
port_container() { # $1 = port, $2 = pid ("" if unknown) → container name or ""
command -v docker >/dev/null 2>&1 || return 0
if [ -n "$2" ] && [ -r "/proc/$2/cgroup" ]; then
_cid=$(sed -n 's#.*docker[-/]\([0-9a-f]\{64\}\).*#\1#p' "/proc/$2/cgroup" 2>/dev/null | head -1)
if [ -n "$_cid" ]; then
docker inspect -f '{{.Name}}' "$_cid" 2>/dev/null | sed 's|^/||'
return 0
fi
fi
docker ps --format '{{.Names}} {{.Ports}}' 2>/dev/null \
| awk -v p=":$1->" 'index($0, p) { print $1; exit }'
}
manifest_ui_port() { # the port an existing install already owns, if any
[ -f /etc/gpu-kitchen/manifest.json ] || return 0
# Bridge publishes GPUK_PUBLIC_PORT over the fixed container 8080; host
# networking moves the listener itself (GPUK_PORT). Same precedence as gpuk.
_p=$(sed -n 's/.*"GPUK_PUBLIC_PORT"[[:space:]]*:[[:space:]]*"\([0-9]*\)".*/\1/p' \
/etc/gpu-kitchen/manifest.json | head -1)
[ -n "$_p" ] || _p=$(sed -n 's/.*"GPUK_PORT"[[:space:]]*:[[:space:]]*"\([0-9]*\)".*/\1/p' \
/etc/gpu-kitchen/manifest.json | head -1)
printf '%s' "$_p"
port_owner() { # $1 = port → best-effort "process", "process in container NAME", or ""
_proc=""; _pid=""
case "$PORT_TOOL" in
ss)
_line=$(ss -ltnpH "sport = :$1" 2>/dev/null | head -1)
_proc=$(printf '%s' "$_line" | sed -n 's/.*users:((\"\([^"]*\)\".*/\1/p')
_pid=$(printf '%s' "$_line" | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
;;
netstat)
_field=$(netstat -ltnp 2>/dev/null \
| awk -v p="$1" '{n=split($4,a,":"); if (a[n]==p) {print $NF; exit}}')
case "$_field" in
*/*) _pid=${_field%%/*}; _proc=${_field#*/} ;;
*) _proc="$_field" ;;
esac
;;
esac
case "$_pid" in *[!0-9]*|"") _pid="" ;; esac
_ctr=$(port_container "$1" "$_pid")
if [ -n "$_ctr" ]; then
printf '%s' "${_proc:-a process} in container $_ctr"
else
printf '%s' "$_proc"
fi
}
# A port is ours when the existing install (step 0) already holds it: the
# re-run replaces that container, so what it listens on is not a conflict.
port_is_ours() { case " $OUR_PORTS " in *" $1 "*) return 0 ;; esac; return 1; }
# Ports this run may not hand out twice: the three requested ones, plus every
# alternative already accepted. Without it, a busy 8442 would be offered 8443
# and collide with the worker channel one question later.
RESERVED_PORTS="$PORT $MTLS_PORT $INFERENCE_PORT"
port_available() { # $1 → free on the host AND not reserved by this run
case " $RESERVED_PORTS " in *" $1 "*) return 1 ;; esac
port_is_ours "$1" && return 1
! port_busy "$1"
}
# resolve_port <label> <port> <flag> → RESOLVED. Same rules for all three
# listeners: ours = fine; busy on a terminal = propose the next free port
# (Enter accepts, a number picks, q aborts) — never auto-pick silently, the URL
# printed at the end and the idempotent re-run both need the operator to KNOW
# the port; busy without a terminal = fail now, naming the process and the flag.
resolve_port() {
_label="$1"; _want="$2"; _flag="$3"
if port_is_ours "$_want"; then
ok "$_label port $_want — already ours (re-running is how you update)"
elif port_busy "$_want"; then
OWNER=$(port_owner "$_want")
OWNER="${OWNER:-an unknown process}"
if can_prompt; then
ALT=$((_want + 1))
while ! port_available "$ALT"; do ALT=$((ALT + 1)); done
ask " ${YELLOW}!${RESET} $_label port $_want is busy ($OWNER). Use $ALT instead? [$ALT], another port, or 'q' to abort: "
case "$REPLY" in
q|Q) die "$_label port $_want is in use by $OWNER. Run the install again with $_flag <p>." ;;
"") _want="$ALT" ;;
*)
case "$REPLY" in
*[!0-9]*) die "not a port number: $REPLY" ;;
esac
port_available "$REPLY" \
|| die "port $REPLY is busy, or already taken by another GPU Kitchen listener. Run the install again with $_flag <p>."
_want="$REPLY"
;;
esac
RESERVED_PORTS="$RESERVED_PORTS $_want"
ok "$_label port $_want is free"
else
die "$_label port $_want is already in use by $OWNER. Pass $_flag <p> to choose another port."
fi
else
ok "$_label port $_want is free"
fi
RESOLVED="$_want"
}
if [ -z "$PORT_TOOL" ]; then
warn "cannot check for port conflicts (neither ss nor netstat found)"
else
HAVE_MANIFEST=0
[ ! -f /etc/gpu-kitchen/manifest.json ] || HAVE_MANIFEST=1
if [ "$HAVE_MANIFEST" -eq 1 ] && [ "$(manifest_ui_port)" = "$PORT" ]; then
ok "UI port $PORT — already ours (re-running is how you update)"
elif port_busy "$PORT"; then
OWNER=$(port_owner "$PORT")
OWNER="${OWNER:-an unknown process}"
if can_prompt; then
ALT=$((PORT + 1))
while port_busy "$ALT"; do ALT=$((ALT + 1)); done
# Propose, never auto-pick: the URL printed at the end and the idempotent
# re-run both need the operator to KNOW which port they chose.
ask " ${YELLOW}!${RESET} port $PORT is busy ($OWNER). Use $ALT instead? [$ALT], another port, or 'q' to abort: "
case "$REPLY" in
q|Q) die "port $PORT is in use by $OWNER. Run the install again with --port <p>." ;;
"") PORT="$ALT" ;;
*)
case "$REPLY" in
*[!0-9]*) die "not a port number: $REPLY" ;;
esac
if port_busy "$REPLY"; then
die "port $REPLY is busy too. Run the install again with --port <p>."
fi
PORT="$REPLY"
;;
esac
ok "UI port $PORT is free"
else
die "port $PORT is already in use by $OWNER. Pass --port <p> to choose another port."
fi
else
ok "UI port $PORT is free"
fi
# The mTLS and inference listeners have no install-time flag — assumed
# limitation (INS-46): the published mTLS port moves later via
# Settings -> Network. With a manifest present they are our own listeners.
if [ "$HAVE_MANIFEST" -eq 0 ]; then
if port_busy "$MTLS_PORT"; then
OWNER=$(port_owner "$MTLS_PORT")
die "port $MTLS_PORT (worker channel) is in use by ${OWNER:-an unknown process}. Free it first."
fi
if port_busy "$INFERENCE_PORT"; then
OWNER=$(port_owner "$INFERENCE_PORT")
die "port $INFERENCE_PORT (inference endpoint) is in use by ${OWNER:-an unknown process}. Free it first.
($INFERENCE_PORT is also HashiCorp Vault's default port.)"
fi
ok "worker channel port $MTLS_PORT and inference port $INFERENCE_PORT are free"
fi
resolve_port "UI" "$PORT" "--port"; PORT="$RESOLVED"
resolve_port "worker channel" "$MTLS_PORT" "--mtls-port"; MTLS_PORT="$RESOLVED"
resolve_port "inference" "$INFERENCE_PORT" "--inference-port"; INFERENCE_PORT="$RESOLVED"
fi
fi # end preflight
# ── 2. Resolve the release ───────────────────────────────────────────────────
step "Release — resolving the version to install"
@@ -420,6 +604,13 @@ if [ "$DRY_RUN" -eq 1 ]; then
echo " data root : $DATA_ROOT"
echo " model cache : $CACHE_DIR"
echo " UI port : $PORT"
echo " worker channel: $MTLS_PORT"
echo " inference : $INFERENCE_PORT"
case "$EXISTING" in
manifest) echo " existing : yes — updated in place" ;;
leftovers) echo " existing : traces of a previous install — taken over" ;;
*) echo " existing : no" ;;
esac
if [ -n "$PROFILE" ]; then
echo " profile : $PROFILE"
else
@@ -531,7 +722,9 @@ set -- install \
--cluster "$CLUSTER" \
--data-root "$DATA_ROOT" \
--cache-dir "$CACHE_DIR" \
--http-port "$PORT"
--http-port "$PORT" \
--mtls-port "$MTLS_PORT" \
--inference-port "$INFERENCE_PORT"
[ -z "$PROFILE" ] || set -- "$@" --profile "$PROFILE"
[ -z "$DOMAIN" ] || set -- "$@" --domain "$DOMAIN"
@@ -646,5 +839,6 @@ if [ -n "$EXISTING_CACHE" ]; then
echo
fi
echo " Update : re-run this command, or press Update in the UI, or: gpuk update"
echo " Status : gpuk status Logs: gpuk logs Remove: gpuk uninstall"
echo " Status : gpuk status Logs: gpuk logs"
echo " Remove : gpuk uninstall (service only) or gpuk uninstall --purge (all but the data root)"
echo