release v0.1.5
This commit is contained in:
@@ -104,6 +104,72 @@ install_binary() { # [local-path]
|
||||
|
||||
gen_secret() { head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '='; }
|
||||
|
||||
# The host ports a leftover app container is reached on ("ui mtls inference"),
|
||||
# read off the container itself with the precedence the controller applies
|
||||
# (core/published-ports.ts): 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 port bindings). Empty
|
||||
# when there is no such container or no docker.
|
||||
leftover_container_ports() { # $1 = container name
|
||||
command -v docker >/dev/null 2>&1 || return 0
|
||||
# Same format string as install.sh's container_facts — one reading, two scripts.
|
||||
_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 0
|
||||
[ -n "$_f" ] || return 0
|
||||
_head=$(printf '%s\n' "$_f" | head -1)
|
||||
_r=${_head#*|}; _net=${_r%%|*}; _bind=${_r#*|}
|
||||
_env() { printf '%s\n' "$_f" | sed -n "s/^$1=//p" | head -1; }
|
||||
_pub() { printf '%s\n' "$_bind" | tr ' ' '\n' | sed -n "s/^$1\/tcp=//p" | head -1; }
|
||||
if [ "$_net" = "host" ]; then
|
||||
_ui=$(_env GPUK_PORT); _mtls=$(_env GPUK_MTLS_PORT)
|
||||
_inf=$(_env GPUK_PROXY_PUBLIC_PORT)
|
||||
[ -n "$_inf" ] || { _la=$(_env GPUK_LISTEN_ADDR); _inf=${_la##*:}; }
|
||||
else
|
||||
_ui=$(_env GPUK_PUBLIC_PORT); [ -n "$_ui" ] || _ui=$(_pub 8080)
|
||||
_mtls=$(_env GPUK_PUBLIC_MTLS_PORT); [ -n "$_mtls" ] || _mtls=$(_pub 8443)
|
||||
_inf=$(_env GPUK_PROXY_PUBLIC_PORT); [ -n "$_inf" ] || _inf=$(_pub 8200)
|
||||
fi
|
||||
printf '%s %s %s' "${_ui:-8080}" "${_mtls:-8443}" "${_inf:-8200}"
|
||||
}
|
||||
|
||||
# Who holds a port: "process", "process in container NAME", or "". The process's
|
||||
# cgroup names its container (host networking); a bridged publication is found
|
||||
# as the container's port mapping in `docker ps` (the host-side holder is
|
||||
# docker-proxy, which says nothing by itself). Same reading as install.sh.
|
||||
port_owner() { # $1 = ss|netstat, $2 = port
|
||||
_proc=""; _pid=""
|
||||
case "$1" in
|
||||
ss)
|
||||
_line=$(ss -ltnpH "sport = :$2" 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="$2" '{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=""
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
if [ -n "$_pid" ] && [ -r "/proc/$_pid/cgroup" ]; then
|
||||
_cid=$(sed -n 's#.*docker[-/]\([0-9a-f]\{64\}\).*#\1#p' "/proc/$_pid/cgroup" 2>/dev/null | head -1)
|
||||
[ -z "$_cid" ] || _ctr=$(docker inspect -f '{{.Name}}' "$_cid" 2>/dev/null | sed 's|^/||')
|
||||
fi
|
||||
[ -n "$_ctr" ] || _ctr=$(docker ps --format '{{.Names}} {{.Ports}}' 2>/dev/null \
|
||||
| awk -v p=":$2->" 'index($0, p) { print $1; exit }')
|
||||
fi
|
||||
if [ -n "$_ctr" ]; then
|
||||
printf '%s' "${_proc:-a process} in container $_ctr"
|
||||
else
|
||||
printf '%s' "$_proc"
|
||||
fi
|
||||
}
|
||||
|
||||
seed_secrets() { # DATA_ROOT
|
||||
_sd="$1/secrets"
|
||||
mkdir -p "$_sd"; chmod 0700 "$_sd"
|
||||
@@ -191,7 +257,7 @@ write_caddyfile() {
|
||||
#
|
||||
# The app already runs with GPUK_HSTS=true and GPUK_SESSION_COOKIE_SECURE=true
|
||||
# (set by the public profile). What does NOT go through this proxy:
|
||||
# - the worker mTLS channel (:8443): workers pin the controller CA and must
|
||||
# - the worker mTLS channel (:$MTLS_PORT): workers pin the controller CA and must
|
||||
# reach it DIRECTLY — terminating it here would break the pin.
|
||||
# - worker<->worker data transfers (:8300): LAN-only by contract (OPS-68).
|
||||
|
||||
@@ -205,7 +271,7 @@ $DOMAIN {
|
||||
#
|
||||
# inference.$DOMAIN {
|
||||
# encode zstd gzip
|
||||
# reverse_proxy localhost:8200
|
||||
# reverse_proxy localhost:$INFERENCE_PORT
|
||||
# }
|
||||
CADDY
|
||||
chmod 0644 "$DATA_ROOT/caddy/Caddyfile"
|
||||
@@ -242,8 +308,14 @@ hint_existing_caches() {
|
||||
cmd_install() {
|
||||
IMAGE=""; MODE="worker"; CLUSTER="default"; DATA_ROOT="/var/lib/gpu-kitchen"
|
||||
CACHE_DIR=""; NETWORK="host"; CONTROLLER_URL=""; BIN_SRC=""; HEALTH_PORT="8001"
|
||||
# 1337, not 8080: kept in lockstep with install.sh's default (INS-01).
|
||||
ENROLL_TOKEN=""; HTTP_PORT="1337"; PROFILE=""; DOMAIN=""; DRY_RUN=0
|
||||
# 1337, not 8080: kept in lockstep with install.sh's default (INS-01). The
|
||||
# worker channel and the inference endpoint move the same way (INS-46).
|
||||
ENROLL_TOKEN=""; HTTP_PORT="1337"; MTLS_PORT="8443"; INFERENCE_PORT="8200"
|
||||
# Worker data plane (OPS-68): the daemon reads GPUK_DATA_PORT,
|
||||
# GPUK_MODEL_TRANSFER_PORT and GPUK_HANDOVER_DATA_PORT from worker.env and
|
||||
# announces the first two to the controller, so moving them here is complete.
|
||||
DATA_PORT="8300"; TRANSFER_PORT="8301"; HANDOVER_PORT="8302"
|
||||
PROFILE=""; DOMAIN=""; DRY_RUN=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--image) IMAGE="$2"; shift 2 ;;
|
||||
@@ -261,6 +333,11 @@ cmd_install() {
|
||||
--enroll-token|--token) ENROLL_TOKEN="$2"; shift 2 ;;
|
||||
--health-port) HEALTH_PORT="$2"; shift 2 ;;
|
||||
--http-port) HTTP_PORT="$2"; shift 2 ;;
|
||||
--mtls-port) MTLS_PORT="$2"; shift 2 ;;
|
||||
--inference-port) INFERENCE_PORT="$2"; shift 2 ;;
|
||||
--data-port) DATA_PORT="$2"; shift 2 ;;
|
||||
--transfer-port) TRANSFER_PORT="$2"; shift 2 ;;
|
||||
--handover-port) HANDOVER_PORT="$2"; shift 2 ;;
|
||||
--binary) BIN_SRC="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
*) die "unknown install option: $1" ;;
|
||||
@@ -279,6 +356,26 @@ cmd_install() {
|
||||
if [ "$MODE" = "worker" ] && [ -n "$PROFILE" ]; then
|
||||
die "--profile applies only to --mode controller; workers do not have an installation profile"
|
||||
fi
|
||||
|
||||
for _pv in "$HTTP_PORT" "$MTLS_PORT" "$INFERENCE_PORT" "$HEALTH_PORT" \
|
||||
"$DATA_PORT" "$TRANSFER_PORT" "$HANDOVER_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
|
||||
if [ "$HTTP_PORT" = "$MTLS_PORT" ] || [ "$HTTP_PORT" = "$INFERENCE_PORT" ] || [ "$MTLS_PORT" = "$INFERENCE_PORT" ]; then
|
||||
die "--http-port, --mtls-port and --inference-port must differ (got $HTTP_PORT, $MTLS_PORT, $INFERENCE_PORT)"
|
||||
fi
|
||||
# The worker's four listeners (health + data plane) must differ too; the
|
||||
# handover port is the data port's temporary twin (WRK-173), never the same.
|
||||
_seen=""
|
||||
for _pv in "$HEALTH_PORT" "$DATA_PORT" "$TRANSFER_PORT" "$HANDOVER_PORT"; do
|
||||
case " $_seen " in
|
||||
*" $_pv "*) die "--health-port, --data-port, --transfer-port and --handover-port must differ (got $HEALTH_PORT, $DATA_PORT, $TRANSFER_PORT, $HANDOVER_PORT)" ;;
|
||||
esac
|
||||
_seen="$_seen $_pv"
|
||||
done
|
||||
case "$PROFILE" in
|
||||
""|homelab|studio|enterprise|public) ;;
|
||||
*) die "--profile must be homelab, studio, enterprise or public (got '$PROFILE')" ;;
|
||||
@@ -319,32 +416,54 @@ cmd_install() {
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
[ "$MODE" != "controller" ] || CLAIM_CODE_AVAILABLE=1
|
||||
echo "==> dry run: no file, service or container was changed"
|
||||
[ ! -f "$MANIFEST" ] \
|
||||
|| echo "==> dry run: $MANIFEST exists — this render would be MERGED into it, controller-owned settings kept"
|
||||
if [ "$MODE" = "worker" ]; then render_worker_manifest; else render_controller_manifest; fi
|
||||
[ -z "$DOMAIN" ] || echo "==> dry run: would write $DATA_ROOT/caddy/Caddyfile for $DOMAIN"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Port conflicts (INS-46) — the mutator's own guard ────────────────────────
|
||||
# install.sh's preflight already checks these, and on a terminal it can offer
|
||||
# an alternative port. gpuk is the actual mutator and contributors call it
|
||||
# DIRECTLY, so it re-checks and refuses, non-interactively. Same helpers as
|
||||
# install.sh (both scripts ship standalone from the channel). A listener owned
|
||||
# by an existing install is not a conflict: a manifest on disk means the
|
||||
# re-run is the update path, and every checked port is then our own.
|
||||
# install.sh's preflight already checks these, and on a terminal it offers an
|
||||
# alternative port. gpuk is the actual mutator and contributors call it
|
||||
# DIRECTLY, so it re-checks and refuses, non-interactively, naming the flag
|
||||
# that moves the port. Same helpers as install.sh (both scripts ship standalone
|
||||
# from the channel). A listener owned by an existing install is not a conflict:
|
||||
# a manifest on disk means the re-run is the update path, and every checked
|
||||
# port is then our own; without a manifest, a leftover app container still
|
||||
# holds OUR ports — apply() renames and stops it before the new one starts.
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
_port_tool=""
|
||||
if command -v ss >/dev/null 2>&1; then _port_tool="ss"
|
||||
elif command -v netstat >/dev/null 2>&1; then _port_tool="netstat"; fi
|
||||
_port_tool="${GPUK_PORT_CHECK_TOOL:-}"
|
||||
case "$_port_tool" in
|
||||
""|ss|netstat) ;;
|
||||
*) die "GPUK_PORT_CHECK_TOOL must be ss or netstat (got '$_port_tool')" ;;
|
||||
esac
|
||||
if [ -z "$_port_tool" ]; then
|
||||
if command -v ss >/dev/null 2>&1; then _port_tool="ss"
|
||||
elif command -v netstat >/dev/null 2>&1; then _port_tool="netstat"; fi
|
||||
fi
|
||||
_ours=""
|
||||
if [ "$MODE" = "controller" ]; then
|
||||
_ours=$(leftover_container_ports gpu-kitchen)
|
||||
[ -z "$_ours" ] || echo "==> existing app container gpu-kitchen found (ports $_ours): the install replaces it"
|
||||
fi
|
||||
if [ -z "$_port_tool" ]; then
|
||||
echo "==> warning: cannot check for port conflicts (no ss or netstat)"
|
||||
else
|
||||
if [ "$MODE" = "controller" ]; then
|
||||
set -- "$HTTP_PORT" 8443 8200
|
||||
set -- "$HTTP_PORT:UI:--http-port" \
|
||||
"$MTLS_PORT:worker channel:--mtls-port" \
|
||||
"$INFERENCE_PORT:inference endpoint:--inference-port"
|
||||
else
|
||||
# Worker data-plane ports (OPS-68) plus the local health listener.
|
||||
set -- "$HEALTH_PORT" 8300 8301 8302
|
||||
set -- "$HEALTH_PORT:worker health:--health-port" \
|
||||
"$DATA_PORT:worker data plane:--data-port" \
|
||||
"$TRANSFER_PORT:model transfers:--transfer-port" \
|
||||
"$HANDOVER_PORT:handover data plane:--handover-port"
|
||||
fi
|
||||
for _p in "$@"; do
|
||||
for _spec in "$@"; do
|
||||
_p=${_spec%%:*}; _rest=${_spec#*:}; _label=${_rest%%:*}; _flag=${_rest#*:}
|
||||
case " $_ours " in *" $_p "*) continue ;; esac
|
||||
_busy=1
|
||||
case "$_port_tool" in
|
||||
ss) [ -n "$(ss -ltnH "sport = :$_p" 2>/dev/null)" ] || _busy=0 ;;
|
||||
@@ -354,7 +473,11 @@ cmd_install() {
|
||||
|| _busy=0
|
||||
;;
|
||||
esac
|
||||
[ "$_busy" -eq 0 ] || die "port $_p is already in use. Free it first, then run the install again."
|
||||
[ "$_busy" -eq 0 ] && continue
|
||||
_owner=$(port_owner "$_port_tool" "$_p")
|
||||
_hint="Free it first, then run the install again."
|
||||
[ -z "$_flag" ] || _hint="Pass $_flag <p> to choose another port, or free it first."
|
||||
die "port $_p ($_label) is already in use by ${_owner:-an unknown process}. $_hint"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
@@ -378,6 +501,9 @@ cmd_install() {
|
||||
{
|
||||
echo "GPUK_CLUSTER=$CLUSTER"
|
||||
echo "GPUK_WORKER_HEALTH_PORT=$HEALTH_PORT"
|
||||
echo "GPUK_DATA_PORT=$DATA_PORT"
|
||||
echo "GPUK_MODEL_TRANSFER_PORT=$TRANSFER_PORT"
|
||||
echo "GPUK_HANDOVER_DATA_PORT=$HANDOVER_PORT"
|
||||
echo "GPUK_MACHINE_ID_FILE=$MACHINE_ID_FILE"
|
||||
echo "NODE_DISPLAY_NAME=$(hostname)"
|
||||
[ -n "$CONTROLLER_URL" ] && echo "GPUK_CONTROLLER_URLS=$CONTROLLER_URL"
|
||||
@@ -392,7 +518,7 @@ cmd_install() {
|
||||
echo "GPUK_CLUSTER=$CLUSTER"
|
||||
echo "GPUK_WORKER_HEALTH_PORT=$HEALTH_PORT"
|
||||
echo "GPUK_MACHINE_ID_FILE=$MACHINE_ID_FILE"
|
||||
echo "GPUK_CONTROLLER_URLS=wss://127.0.0.1:8443"
|
||||
echo "GPUK_CONTROLLER_URLS=wss://127.0.0.1:$MTLS_PORT"
|
||||
echo "GPUK_SELF_ENROLL_FILE=$SELF_ENROLL_FILE"
|
||||
echo "NODE_DISPLAY_NAME=$(hostname)"
|
||||
} > "$WORKER_ENV"
|
||||
@@ -439,7 +565,7 @@ cmd_install() {
|
||||
# No unix socket any more: workerd reconciles the app container in-process from
|
||||
# the on-disk manifest (there is no backend to relay through on the very first
|
||||
# boot). Steady-state updates go through the daemon over WS.
|
||||
"$BIN_DEST" apply || die "apply failed. Check: gpuk logs"
|
||||
GPUK_MANIFEST_PATH="$MANIFEST" "$BIN_DEST" apply || die "apply failed. Check: gpuk logs"
|
||||
echo
|
||||
echo "Done. The worker daemon is running and the app container is up."
|
||||
echo " Status : gpuk status App logs: gpuk logs Daemon: journalctl -u $SERVICE_NAME"
|
||||
@@ -463,7 +589,36 @@ render_worker_manifest() {
|
||||
JSON
|
||||
}
|
||||
|
||||
write_worker_manifest() { render_worker_manifest > "$MANIFEST"; }
|
||||
# Every env key gpuk may ever write into a manifest — conditional ones included.
|
||||
# On a re-run the merge sets each of them to the fresh value or DELETES it when the
|
||||
# fresh render no longer carries it (a profile change drops GPUK_HSTS); any other
|
||||
# key was pushed by the controller and is kept. Keep this list in step with
|
||||
# render_controller_manifest.
|
||||
INSTALL_ENV_KEYS="NODE_ENV,GPUK_MODE,GPUK_CLUSTER,GPUK_SELF_ENROLL_FILE,NODE_DISPLAY_NAME,HF_HOME,GPUK_DATA_ROOT,GPUK_INSTALL_PROFILE,GPUK_HSTS,GPUK_SESSION_COOKIE_SECURE,GPUK_BOOTSTRAP_MUST_CHANGE,BACKEND_DOCKER_NETWORK,GPUK_PORT,GPUK_PUBLIC_PORT,GPUK_MTLS_PORT,GPUK_PUBLIC_MTLS_PORT,GPUK_LISTEN_ADDR,GPUK_PROXY_PUBLIC_PORT"
|
||||
|
||||
# Write the manifest — or, when one exists, MERGE into it (INS-03). Re-running the
|
||||
# installer is the update path, and the manifest is not ours alone: since the first
|
||||
# install the controller has patched it (extra cache disks, shared origin, eviction,
|
||||
# LED binary, ports moved from Settings -> Network…). Rewriting it from flags would
|
||||
# silently undo all of that, so the fresh render goes through the daemon's own
|
||||
# `install-manifest`, which only replaces what the installer owns (image, mode,
|
||||
# cluster, network, data root, ports, secret references, the primary cache disk,
|
||||
# the env keys above) and keeps the rest. The first write is the render as-is.
|
||||
write_manifest() { # $1 = render function
|
||||
if [ -f "$MANIFEST" ]; then
|
||||
"$1" > "$MANIFEST.new"
|
||||
chmod 0600 "$MANIFEST.new"
|
||||
GPUK_MANIFEST_PATH="$MANIFEST" "$BIN_DEST" install-manifest \
|
||||
--from "$MANIFEST.new" --own-env "$INSTALL_ENV_KEYS" >/dev/null \
|
||||
|| { rm -f "$MANIFEST.new"; die "could not merge the new settings into $MANIFEST"; }
|
||||
rm -f "$MANIFEST.new"
|
||||
echo "==> merged into the existing $MANIFEST (controller-owned settings kept)"
|
||||
else
|
||||
"$1" > "$MANIFEST"
|
||||
fi
|
||||
}
|
||||
|
||||
write_worker_manifest() { write_manifest render_worker_manifest; }
|
||||
|
||||
# Controller manifest: the declarative app-container description workerd applies.
|
||||
render_controller_manifest() {
|
||||
@@ -490,18 +645,25 @@ render_controller_manifest() {
|
||||
[ "$NETWORK" = "host" ] || EXTRA_ENV="$EXTRA_ENV,\"BACKEND_DOCKER_NETWORK\":\"$(json_str "$NETWORK")\""
|
||||
|
||||
# Published != bound (INS-43). On a bridged network the container keeps the image's
|
||||
# FIXED listeners — nginx 8080, worker mTLS 8443 — and --http-port only moves the HOST
|
||||
# side of the publication; Settings -> Network moves it later by patching this same
|
||||
# manifest, so the container port must never become a variable. With host networking
|
||||
# nothing is published and the listener itself takes the port.
|
||||
# FIXED listeners — nginx 8080, worker mTLS 8443, gpuk-proxy 8200 — and the port
|
||||
# flags only move the HOST side of the publication; Settings -> Network moves it
|
||||
# later by patching this same manifest, so the container port must never become a
|
||||
# variable. With host networking nothing is published and the listeners themselves
|
||||
# take the ports (core/published-ports.ts reads this back with the same rules; the
|
||||
# proxy port is GPUK_LISTEN_ADDR for the proxy, GPUK_PROXY_PUBLIC_PORT for what the
|
||||
# backend shows — core/cluster-settings.ts proxyPublicPort).
|
||||
PORTS_JSON="{}"
|
||||
if [ "$NETWORK" = "host" ]; then
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_PORT\":\"$(json_str "$HTTP_PORT")\""
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_MTLS_PORT\":\"$(json_str "$MTLS_PORT")\""
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_LISTEN_ADDR\":\"0.0.0.0:$(json_str "$INFERENCE_PORT")\""
|
||||
else
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_PORT\":\"8080\""
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_PUBLIC_PORT\":\"$(json_str "$HTTP_PORT")\""
|
||||
PORTS_JSON="{\"8080\":$HTTP_PORT,\"8200\":8200,\"8443\":8443}"
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_PUBLIC_MTLS_PORT\":\"$(json_str "$MTLS_PORT")\""
|
||||
PORTS_JSON="{\"8080\":$HTTP_PORT,\"8200\":$INFERENCE_PORT,\"8443\":$MTLS_PORT}"
|
||||
fi
|
||||
EXTRA_ENV="$EXTRA_ENV,\"GPUK_PROXY_PUBLIC_PORT\":\"$(json_str "$INFERENCE_PORT")\""
|
||||
|
||||
cat <<JSON
|
||||
{
|
||||
@@ -532,7 +694,7 @@ render_controller_manifest() {
|
||||
JSON
|
||||
}
|
||||
|
||||
write_controller_manifest() { render_controller_manifest > "$MANIFEST"; }
|
||||
write_controller_manifest() { write_manifest render_controller_manifest; }
|
||||
|
||||
# ── control subcommands ────────────────────────────────────────────────────────
|
||||
container_name() {
|
||||
@@ -770,7 +932,7 @@ cmd_enroll() {
|
||||
# has no app container — `apply` there is a no-op with a clear message.
|
||||
cmd_apply() {
|
||||
need_root
|
||||
"$BIN_DEST" apply
|
||||
GPUK_MANIFEST_PATH="$MANIFEST" "$BIN_DEST" apply
|
||||
}
|
||||
|
||||
# ── update ─────────────────────────────────────────────────────────────────────
|
||||
@@ -824,7 +986,7 @@ cmd_update() {
|
||||
fi
|
||||
if [ -z "$_want" ]; then
|
||||
echo "==> cannot reach $CHANNEL_URL — re-applying the pinned $_current"
|
||||
"$BIN_DEST" apply
|
||||
GPUK_MANIFEST_PATH="$MANIFEST" "$BIN_DEST" apply
|
||||
return 0
|
||||
fi
|
||||
# Pick the digest matching the installed edition by image basename — the
|
||||
@@ -849,15 +1011,51 @@ cmd_update() {
|
||||
else
|
||||
echo "==> already on $_current — re-pulling and recreating"
|
||||
fi
|
||||
"$BIN_DEST" apply
|
||||
GPUK_MANIFEST_PATH="$MANIFEST" "$BIN_DEST" apply
|
||||
}
|
||||
|
||||
# ── uninstall ──────────────────────────────────────────────────────────────────
|
||||
# Plain: stop and remove the service, touch nothing else (a pause, reversible by
|
||||
# `gpuk install`). --purge: everything the installer created goes — the app
|
||||
# container (and a leftover -old twin), $ETC_DIR with the manifest and the
|
||||
# enrolled identity, the binary — EXCEPT the data root: database, models and the
|
||||
# secrets (ENCRYPTION_KEY above all, OPS-04) are the operator's to delete, by
|
||||
# hand, knowingly. After a purge the next install is a first install (INS-03);
|
||||
# without it, install.sh sees the manifest and treats the re-run as an update.
|
||||
cmd_uninstall() {
|
||||
need_root
|
||||
_purge=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--purge) _purge=1; shift ;;
|
||||
*) die "unknown uninstall option: $1 (expected: --purge)" ;;
|
||||
esac
|
||||
done
|
||||
_cn=$(container_name)
|
||||
_root=$(manifest_data_root)
|
||||
systemctl disable --now "$SERVICE_NAME" 2>/dev/null || true
|
||||
rm -f "$UNIT_DEST"; systemctl daemon-reload 2>/dev/null || true
|
||||
echo "Removed the systemd service. Left in place: $BIN_DEST, $ETC_DIR (incl. identity),"
|
||||
echo "the data root and any app container. Delete them manually for a full cleanup."
|
||||
echo "==> removed the systemd service"
|
||||
if [ "$_purge" -eq 0 ]; then
|
||||
echo "Left in place: $BIN_DEST, $ETC_DIR (manifest, identity), the data root and any"
|
||||
echo "app container — 'gpuk install' brings the service back on them."
|
||||
echo "For a clean slate (everything but the data root): gpuk uninstall --purge"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$_cn" ] && command -v docker >/dev/null 2>&1; then
|
||||
for _c in "$_cn" "$_cn-old"; do
|
||||
docker rm -f "$_c" >/dev/null 2>&1 && echo "==> removed container $_c"
|
||||
done
|
||||
fi
|
||||
for _d in "$ETC_DIR" "$IDENTITY_DIR"; do
|
||||
case "$_d" in ""|/|/etc|/usr|/var) die "refusing to remove $_d" ;; esac
|
||||
[ ! -e "$_d" ] || { rm -rf "$_d"; echo "==> removed $_d"; }
|
||||
done
|
||||
[ ! -e "$MACHINE_ID_FILE" ] || rm -f "$MACHINE_ID_FILE"
|
||||
[ ! -e "$BIN_DEST" ] || { rm -f "$BIN_DEST"; echo "==> removed $BIN_DEST"; }
|
||||
echo
|
||||
echo "Kept: the data root${_root:+ $_root} — database, models and secrets (ENCRYPTION_KEY)."
|
||||
echo "A new install over it reuses them. To delete it too, knowingly: rm -rf ${_root:-<data-root>}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
@@ -865,10 +1063,12 @@ usage() {
|
||||
gpuk — GPU Kitchen host daemon (gpu-kitchen-worker)
|
||||
|
||||
gpuk install --mode controller --image <ref> [--profile homelab|studio|enterprise|public]
|
||||
[--cluster N] [--cache-dir P]
|
||||
[--data-root P] [--http-port P] [--network host|bridge|<net>] [--binary <path>]
|
||||
[--cluster N] [--cache-dir P] [--data-root P]
|
||||
[--http-port P] [--mtls-port P] [--inference-port P]
|
||||
[--network host|bridge|<net>] [--binary <path>]
|
||||
gpuk install --mode worker --controller wss://<host>:<port> --enroll-token gk_enroll_...
|
||||
[--cluster N] [--cache-dir P] [--binary <path>]
|
||||
[--health-port P] [--data-port P] [--transfer-port P] [--handover-port P]
|
||||
gpuk install ... --dry-run Validate inputs and print the manifest without changing the host
|
||||
gpuk status Service state, enrollment, /health, app container status
|
||||
gpuk enroll --controller wss://<host>:<port> --token gk_enroll_...
|
||||
@@ -880,7 +1080,9 @@ gpuk — GPU Kitchen host daemon (gpu-kitchen-worker)
|
||||
gpuk backup [DIR] (controller) Cold snapshot of the embedded pgdata (stop → tar → restart)
|
||||
gpuk manifest Print the current manifest
|
||||
gpuk logs Follow the app container logs (controller) or the daemon journal
|
||||
gpuk uninstall Remove the systemd service
|
||||
gpuk uninstall Remove the systemd service, leave everything else in place
|
||||
gpuk uninstall --purge Also remove the app container, /etc/gpu-kitchen and the binary
|
||||
(never the data root: database, models, secrets)
|
||||
|
||||
Most people never run this directly: the channel's install.sh installs it
|
||||
(https://repo.byterain.io/gpukitchen/channel/raw/branch/main/install.sh).
|
||||
@@ -902,7 +1104,7 @@ case "$cmd" in
|
||||
_img=$(manifest_image)
|
||||
if [ -n "$_img" ]; then exec docker logs -f "$(container_name)"; else exec journalctl -u "$SERVICE_NAME" -f; fi
|
||||
;;
|
||||
uninstall) cmd_uninstall ;;
|
||||
uninstall) cmd_uninstall "$@" ;;
|
||||
help|-h|--help) usage ;;
|
||||
*) usage; exit 1 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user