Files
LLM-Server/scripts/update.sh
T
haylanandClaude Bot 05effe5c88 chore: move code comments into docs, leave one-line pointers
Explanations from docker-compose.yml, .env.example and
scripts/update.sh now live in docs/architecture.md, gateway.md,
network-access.md and operations.md. Code keeps `# see docs/...`
pointers, one-line warnings (storage key, SSRF guard, WAL grace period,
git pull ordering) and "auto-filled — leave blank" markers.

Refs #55 (comment destination map #61)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:52:00 +02:00

188 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Brings the running stack in sync with this repo; safe to re-run any time.
# What it does and why: docs/operations.md
set -euo pipefail
cd "$(dirname "$0")/.."
FORCE=false
for arg in "$@"; do
case "$arg" in
--force) FORCE=true ;;
*) echo "Usage: $0 [--force]" >&2; exit 1 ;;
esac
done
# Keep this first — see docs/operations.md#self-update
echo "==> git pull"
git pull --ff-only
[ -f .env ] || cp .env.example .env
echo "==> syncing tracked config values from .env.example"
# see docs/operations.md#gum
GUM_VERSION="0.14.5"
GUM_DIR="$(pwd)/.cache/gum"
GUM_BIN="$GUM_DIR/gum"
ensure_gum() {
command -v gum >/dev/null 2>&1 && { echo "gum"; return; }
[ -x "$GUM_BIN" ] && { echo "$GUM_BIN"; return; }
mkdir -p "$GUM_DIR"
local arch tmpdir vendored
case "$(uname -m)" in
x86_64) arch="x86_64" ;;
aarch64|arm64) arch="arm64" ;;
*) echo "no gum build for $(uname -m), falling back to plain prompts" >&2; echo ""; return ;;
esac
tmpdir="$(mktemp -d)"
vendored="$(pwd)/scripts/vendor/gum_${GUM_VERSION}_Linux_${arch}.tar.gz"
if [ -f "$vendored" ]; then
tar -xz -C "$tmpdir" -f "$vendored"
else
local url="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_Linux_${arch}.tar.gz"
if ! curl -fsSL "$url" | tar -xz -C "$tmpdir" 2>/dev/null; then
echo "no vendored gum for $arch and couldn't download from $url (no internet egress? falling back to plain prompts)" >&2
fi
fi
if [ -n "$(find "$tmpdir" -name gum -type f 2>/dev/null)" ]; then
find "$tmpdir" -name gum -type f -exec cp {} "$GUM_BIN" \;
chmod +x "$GUM_BIN" 2>/dev/null || true
fi
rm -rf "$tmpdir"
[ -x "$GUM_BIN" ] && echo "$GUM_BIN" || echo ""
}
# see docs/operations.md#env-sync
conflict_keys=()
conflict_old=()
conflict_new=()
while IFS='=' read -r key value; do
[ -n "$value" ] || continue
if ! grep -qE "^${key}=" .env; then
echo "${key}=${value}" >> .env
continue
fi
current="$(grep -E "^${key}=" .env | head -1 | cut -d= -f2-)"
if [ "$current" != "$value" ]; then
conflict_keys+=("$key")
conflict_old+=("$current")
conflict_new+=("$value")
fi
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=.+' .env.example)
if [ "${#conflict_keys[@]}" -gt 0 ]; then
if [ "$FORCE" = true ]; then
for i in "${!conflict_keys[@]}"; do
key="${conflict_keys[$i]}"; new="${conflict_new[$i]}"
sed -i "s|^${key}=.*|${key}=${new}|" .env
echo "${key}: ${conflict_old[$i]} -> ${new} (--force)"
done
elif [ ! -t 0 ] || [ ! -t 1 ]; then
echo "ERROR: ${#conflict_keys[@]} config value(s) in .env differ from .env.example, and this isn't an interactive terminal:" >&2
for i in "${!conflict_keys[@]}"; do
echo " ${conflict_keys[$i]}: ${conflict_old[$i]} (current) vs ${conflict_new[$i]} (.env.example)" >&2
done
echo "Re-run interactively to choose per-key, or pass --force to accept every new value." >&2
exit 1
else
gum_bin="$(ensure_gum)"
labels=()
for i in "${!conflict_keys[@]}"; do
labels+=("${conflict_keys[$i]}: ${conflict_old[$i]} -> ${conflict_new[$i]}")
done
if [ -n "$gum_bin" ]; then
selected="$(printf '%s\n' "${labels[@]}" | "$gum_bin" choose --no-limit --selected "$(printf '%s\n' "${labels[@]}" | paste -sd,)" --header "Config differs from .env.example — selected keys take the new value, unselected keep the server's current value:")"
else
echo "Config differs from .env.example. Enter space-separated numbers to KEEP the server's current value (all others take the new value), or press enter to take every new value:"
for i in "${!conflict_keys[@]}"; do
echo " $((i+1))) ${labels[$i]}"
done
read -r -p "> " keep_nums
selected=""
for i in "${!conflict_keys[@]}"; do
case " $keep_nums " in
*" $((i+1)) "*) ;;
*) selected="${selected}${labels[$i]}"$'\n' ;;
esac
done
fi
for i in "${!conflict_keys[@]}"; do
key="${conflict_keys[$i]}"; new="${conflict_new[$i]}"
if printf '%s\n' "$selected" | grep -qxF "${labels[$i]}"; then
sed -i "s|^${key}=.*|${key}=${new}|" .env
echo "${key}: ${conflict_old[$i]} -> ${new}"
else
echo "${key}: kept ${conflict_old[$i]} (server value)"
fi
done
fi
fi
set_if_blank() {
local key="$1" value="$2"
if grep -qE "^${key}=.*[^[:space:]]" .env; then
echo "${key}: already set, skipping."
elif grep -qE "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
echo "${key}: set."
else
echo "${key}=${value}" >> .env
echo "${key}: added (was missing from .env)."
fi
}
echo "==> filling in missing secrets"
# see docs/operations.md#secrets
set_if_blank OMNIROUTE_INITIAL_PASSWORD "$(openssl rand -hex 16)"
set_if_blank OMNIROUTE_JWT_SECRET "$(openssl rand -base64 48)"
set_if_blank OMNIROUTE_API_KEY_SECRET "$(openssl rand -hex 32)"
set_if_blank OMNIROUTE_STORAGE_ENCRYPTION_KEY "$(openssl rand -hex 32)"
set_if_blank OMNIROUTE_MACHINE_ID_SALT "$(openssl rand -hex 16)"
set_if_blank OMNIROUTE_CLI_SALT "$(openssl rand -hex 16)"
set_if_blank OMNIROUTE_WS_BRIDGE_SECRET "$(openssl rand -hex 32)"
echo "==> resolving SEARXNG_LAN_IP"
# see docs/gateway.md#web-search
searxng_ip="$(getent hosts search.home 2>/dev/null | awk '{print $1}' | head -1)"
if [ -n "$searxng_ip" ]; then
set_if_blank SEARXNG_LAN_IP "$searxng_ip"
else
echo "SEARXNG_LAN_IP: couldn't resolve search.home from this host, set it manually if still blank."
fi
echo "==> resolving host video/render GIDs (shared by every GPU service)"
# see docs/architecture.md#gpu-access
video_gid="$(getent group video 2>/dev/null | cut -d: -f3)"
render_gid="$(getent group render 2>/dev/null | cut -d: -f3)"
if [ -n "$video_gid" ]; then
set_if_blank HOST_VIDEO_GID "$video_gid"
else
echo "HOST_VIDEO_GID: no 'video' group on this host, set it manually if still blank."
fi
if [ -n "$render_gid" ]; then
set_if_blank HOST_RENDER_GID "$render_gid"
else
echo "HOST_RENDER_GID: no 'render' group on this host, set it manually if still blank."
fi
echo "==> validating compose config"
docker compose config -q
echo "==> pulling images"
docker compose pull --ignore-buildable
echo "==> rebuilding local-build services"
docker compose build --pull
echo "==> ensuring models are downloaded (skips already-present files)"
docker compose --profile tools run --rm downloader
docker compose --profile tools run --rm downloader-classifier
echo "==> bringing up omniroute"
docker compose up -d --wait omniroute
echo "==> recreating changed services"
docker compose up -d --remove-orphans
echo "==> status"
docker compose ps