Merge origin/main; keep PM as plugin dependency owner

Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
This commit is contained in:
ethernet
2026-09-17 13:52:05 -04:00
2440 changed files with 120380 additions and 16107 deletions

View File

@@ -505,6 +505,77 @@ elif ! grep -q '^API_SERVER_KEY=..*' "$HERMES_HOME/.env" 2>/dev/null; then
fi
fi
# --- Sync deploy-injected Nous routing overrides into every profile .env ---
# Under multiplex, hermes_cli.auth_nous reads HERMES_PORTAL_BASE_URL (or its
# NOUS_PORTAL_BASE_URL alias) and NOUS_INFERENCE_BASE_URL through the profile
# secret scope (agent.secret_scope.get_secret, #108319 / #111809), built from
# <profile>/.env with no os.environ fallback — a value that lives only in the
# container env is invisible on every routed turn, the Portal URL heals to
# production and a non-production login is quarantined. The deploy therefore
# carries the value into $HERMES_HOME/.env and every profiles/*/.env: the
# container wins over a stale line, an already-correct line is left alone, and
# lines written here carry a marker so a boot WITHOUT the variable removes them
# again (a hand-set line is never touched). Known gap: a profile created while the container
# runs is synced on the next boot. Interim until the managed scope
# (/etc/hermes/.env) composition reverted by #111600 is restored.
_ROUTING_MARK='# stage2-managed'
# rewrite_env_var FILE NAME DROP_PATTERN [LINE]: drop the lines matching DROP_PATTERN (a BRE),
# append LINE when given. Rewritten through the existing inode (owner and mode kept — sed -i would
# re-create the file). `grep -v` exits 1 when nothing remains (fine) and 2 when the file could not
# be read (then a rewrite would wipe every other secret — refuse). A read-only volume degrades to a
# warning, never a boot abort.
rewrite_env_var() {
_rc=0
_rest=$(grep -v -- "$3" "$1" 2>/dev/null) || _rc=$?
if [ "$_rc" -gt 1 ]; then
echo "[stage2] Warning: could not read $1 — leaving $2 untouched"
return 1
fi
if [ $# -ge 4 ]; then
_rest="${_rest:+$_rest
}$4"
fi
if printf '%s' "${_rest:+$_rest
}" 2>/dev/null > "$1"; then
return 0
fi
echo "[stage2] Warning: could not write $2 to $1 (read-only volume?) — routed turns will fall back to the production Portal"
return 1
}
sync_routing_overrides() {
_file="$1"
if refuse_symlinked_path "sync" "$_file"; then
return 0
fi
for _name in HERMES_PORTAL_BASE_URL NOUS_PORTAL_BASE_URL NOUS_INFERENCE_BASE_URL; do
eval "_value=\${$_name:-}"
_managed="^$_name=.* $_ROUTING_MARK\$"
if [ -z "$_value" ]; then
if grep -q -- "$_managed" "$_file" 2>/dev/null && rewrite_env_var "$_file" "$_name" "$_managed"; then
echo "[stage2] Removed $_name from $_file (no longer set in the container environment)"
fi
continue
fi
_line="$_name=$_value $_ROUTING_MARK"
if grep -qxF -- "$_line" "$_file" 2>/dev/null; then
continue
fi
if [ ! -f "$_file" ] && ! (umask 077 && as_hermes touch "$_file") 2>/dev/null; then
echo "[stage2] Warning: could not create $_file — the Nous routing overrides will not reach this profile's secret scope"
return 0
fi
if rewrite_env_var "$_file" "$_name" "^$_name=" "$_line"; then
echo "[stage2] Synced $_name from the container environment into $_file"
fi
done
}
sync_routing_overrides "$HERMES_HOME/.env"
for _profile_dir in "$HERMES_HOME"/profiles/*/; do
[ -d "$_profile_dir" ] || continue
sync_routing_overrides "${_profile_dir}.env"
done
unset _profile_dir _file _name _value _managed _line _rest _rc
# .env holds API keys and secrets — restrict to owner-only access. Applied
# unconditionally (not only on first-seed) so a host-mounted .env that was
# created with a permissive umask gets tightened on every container start.