Files
LLM-Server/scripts/update.sh
T
haylan 9def240a8e feat: update.sh force-syncs tracked config from .env.example
Follows directly from the previous commit's caveat: this PR's own fix
(LLAMA_FAST_PARALLEL=2 -> 1) wouldn't have taken effect on the server
without a manual .env edit, because set_if_blank never touches an
already-set value — by design, for secrets, but the same logic was
silently protecting stale copies of ordinary tunable config too.

Every KEY=VALUE line in .env.example with a real (non-blank) default
is now force-synced into .env on every run. Secrets and host-resolved
values are unaffected — .env.example already leaves those blank on
purpose, so the sync loop naturally skips them and they keep going
through set_if_blank as before.

Trade-off, called out in both the script's header and the sync loop's
own comment: there's no such thing as a persistent server-only
override for these keys anymore — a hand-edited value not reflected
in git gets reverted on the next run. That's the intended behavior.

Verified against a simulated stale .env matching the real scenario
from this PR: LLAMA_FAST_PARALLEL correctly overwritten 2 -> 1, an
OMNIROUTE secret left untouched. bash -n and docker compose config -q
both pass.

Refs #5
2026-09-06 22:10:51 +02:00

155 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# The one command to run after any change to this repo (compose file, .env,
# or a git pull) to bring the running stack in sync. Ensures secrets/keys
# exist, pulls, validates, rebuilds/re-pulls images, and recreates only what
# changed — safe to run any time, including with nothing to do.
#
# Tunable config values (LLAMA_*, ports, timeouts — anything with a real
# default in .env.example) are force-synced from .env.example every run,
# not just filled in when missing — see the sync loop below. Only actual
# secrets and host-resolved values (blank in .env.example) survive a
# server-side edit; there's no persistent server-only override for
# anything else.
#
# omniroute's own routing/provider config (llama-server, search) lives in
# its dashboard, not a checked-in file like the old litellm-config.yaml —
# see issue #31 and docs/proxy-key-onboarding.md.
#
# ponytail: no rollback/backup logic — this is a single-user homelab box,
# not a fleet. If a bad config lands, `git revert` + re-run is the recovery
# path, not this script.
set -euo pipefail
cd "$(dirname "$0")/.."
# Must run before anything else touches a file this script itself reads
# (docker-compose.yml, .env.example, this script's own remaining lines) —
# a self-updating script isn't guaranteed atomic against its own file
# changing mid-run, so pulling later can execute a mix of old and new
# script/compose content in one pass. Bit us for real: old GID-resolution
# code ran, then this pulled in new var names docker-compose.yml now
# requires, and nothing re-ran the (now-current) resolution step for
# them — see issue #5's thread.
echo "==> git pull"
git pull --ff-only
[ -f .env ] || cp .env.example .env
echo "==> syncing tracked config values from .env.example"
# Every KEY=VALUE line in .env.example that has a real default (not
# blank) is ordinary tunable config, not a secret or host-specific
# value — .env.example itself already draws that line: secrets
# (OMNIROUTE_*_SECRET/_KEY/_SALT/_PASSWORD) and host-resolved values
# (SEARXNG_LAN_IP, COMFYUI_PUID/PGID, HOST_VIDEO_GID/RENDER_GID) are
# all left blank there on purpose, so this loop naturally skips them —
# they keep going through set_if_blank below instead. This force-
# overwrites .env with whatever's in git on every run, so a value
# hand-edited on the server (not in .env.example) gets silently
# reverted on the next update.sh — that's the point (git pull #48 bit
# us exactly because a stale .env value survived a code change and
# broke llama-server-fast, see #5/#49), but it means there's no such
# thing as a persistent server-only override for these keys anymore.
while IFS='=' read -r key value; do
[ -n "$value" ] || continue
if grep -qE "^${key}=" .env; then
sed -i "s|^${key}=.*|${key}=${value}|" .env
else
echo "${key}=${value}" >> .env
fi
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=.+' .env.example)
# Handles all three cases: the KEY=value line is missing entirely (.env
# predates that var being added to .env.example — sed can't fix what isn't
# there, so this appends it), present but blank, or already set.
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"
# Random values — safe to re-run, never overwrites what's already set.
# OMNIROUTE_STORAGE_ENCRYPTION_KEY especially: never change it after first
# run, existing encrypted data becomes unreadable if you do (same caveat as
# LiteLLM's old LITELLM_SALT_KEY).
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"
# search.home is a LAN mDNS/local-DNS name — resolvable from this host, just
# not from inside the omniroute container (see docs/research/litellm-searxng-search.md,
# still the relevant background even though omniroute replaced litellm — see issue #31).
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 ComfyUI host UID"
# yurisasc/comfyui-rocm7.1 wants these as env vars, not just group_add in
# compose — resolve from this host, same pattern as SEARXNG_LAN_IP.
set_if_blank COMFYUI_PUID "$(id -u)"
set_if_blank COMFYUI_PGID "$(id -g)"
echo "==> resolving host video/render GIDs (shared by every GPU service)"
# Numeric GIDs, not names, in docker-compose.yml's group_add: — Docker
# resolves a *named* group_add entry against the container's own /etc/group,
# not the host's, and fails unpredictably (worse with multiple GPU services
# starting concurrently and racing on the same lookup) — see
# docs/research/rocm-gpu-pin-and-render-group.md and issue #5.
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-fast
docker compose --profile tools run --rm downloader-comfyui
echo "==> bringing up omniroute"
docker compose up -d --wait omniroute
# ponytail: no scripted key-minting yet, unlike the old LiteLLM /key/generate
# flow — omniroute's POST /api/keys needs a dashboard login session
# (ManagementSessionAuth), not a static bearer key, and that flow hasn't
# been verified against a live instance (see issue #37). No in-stack
# workload needs a key right now (nothing left calls the gateway besides
# coding CLIs, which mint their own by hand per docs/proxy-key-onboarding.md)
# — revisit this script once that flow is automatable.
echo "==> recreating changed services"
docker compose up -d --remove-orphans
echo "==> status"
docker compose ps