#!/usr/bin/env bash # --------------------------------------------------------------------------- # Install the user-level Pi configuration from runtime/ into ~/.pi/agent, # rendering models.json from its template. # # Dry run by default. # # Usage: # scripts/deploy-runtime.sh # scripts/deploy-runtime.sh --apply # --------------------------------------------------------------------------- # shellcheck source=lib/common.sh . "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh" APPLY=0 [ "${1:-}" = "--apply" ] && APPLY=1 SRC="$REPO_ROOT/runtime/agent" DST="$HOME/.pi/agent" SECRETS="$REPO_ROOT/secrets/zenmux.env" head1 "deploy runtime -> $DST" [ "$APPLY" -eq 1 ] || info "${C_DIM}(dry run; pass --apply to write)${C_OFF}" # --------------------------------------------------------------------------- # models.json: render the template # --------------------------------------------------------------------------- head1 "models.json" if [ ! -f "$SECRETS" ]; then die "missing $SECRETS cp secrets/zenmux.env.example secrets/zenmux.env && chmod 600 secrets/zenmux.env then fill in ZENMUX_API_KEY" fi PERM="$(stat -c '%a' "$SECRETS")" [ "$PERM" = "600" ] || warn "$SECRETS has mode $PERM; expected 600" # shellcheck disable=SC1090 set -a; . "$SECRETS"; set +a [ -n "${ZENMUX_API_KEY:-}" ] || die "ZENMUX_API_KEY is empty in $SECRETS" case "$ZENMUX_API_KEY" in ''|'<'*'>'|'${'*) die "ZENMUX_API_KEY in $SECRETS is still a placeholder" ;; esac RENDERED="$(python3 - "$SRC/models.json.template" <<'PY' import json, os, pathlib, re, sys text = pathlib.Path(sys.argv[1]).read_text() def sub(match): name = match.group(1) value = os.environ.get(name) if not value: sys.exit(f"unresolved placeholder ${{{name}}}") return json.dumps(value)[1:-1] # escape for a JSON string context text = re.sub(r'\$\{([A-Z_][A-Z0-9_]*)\}', sub, text) json.loads(text) # fail early on malformed output sys.stdout.write(text) PY )" || die "cannot render models.json.template" if [ -f "$DST/models.json" ] && [ "$RENDERED" = "$(cat "$DST/models.json")" ]; then ok "models.json already current" else info " write models.json ${C_DIM}(rendered, mode 600)${C_OFF}" if [ "$APPLY" -eq 1 ]; then install -d -m 700 "$DST" printf '%s' "$RENDERED" > "$DST/models.json" chmod 600 "$DST/models.json" fi fi unset ZENMUX_API_KEY # --------------------------------------------------------------------------- # Everything else is copied verbatim # --------------------------------------------------------------------------- head1 "settings, extensions, prompts" CHANGES=0 while IFS= read -r rel; do src="$SRC/$rel" dst="$DST/$rel" if [ -f "$dst" ] && cmp -s "$src" "$dst"; then continue; fi CHANGES=$((CHANGES + 1)) [ -f "$dst" ] && info " update $rel" || info " create $rel" if [ "$APPLY" -eq 1 ]; then install -d -m 700 "$(dirname "$dst")" install -m 600 "$src" "$dst" fi done < <(cd "$SRC" && find settings.json extensions prompts -type f 2>/dev/null | sort) [ "$CHANGES" -eq 0 ] && ok "already current" # --------------------------------------------------------------------------- # Report host-side files this repository deliberately does not own # --------------------------------------------------------------------------- head1 "not managed here" for f in auth.json trust.json models-store.json; do [ -e "$DST/$f" ] && info " ${C_DIM}$f (host state)${C_OFF}" done if [ -e "$DST/extensions/herdr-agent-state.ts" ]; then info " ${C_DIM}extensions/herdr-agent-state.ts (installed by herdr)${C_OFF}" fi printf '\n' if [ "$APPLY" -eq 1 ]; then ok "applied" info "" info "Restart any Pi gateway that should pick up new provider settings:" info " systemctl --user restart curator.service pi-memo-telegram.service" else info "${C_DIM}dry run complete; re-run with --apply${C_OFF}" fi