Idunn/deploy/legacy/idunn-yggdrasil
Meta Vens 889e23629c Retire the legacy bifrost-persona-feedback deploy route
The operator ruled "delete the Bifrost lane". This route was that lane's live
trigger: /srv/odin/deploy-manifests/bifrost-persona-feedback runs
`docker compose up persona-feedback`, and the provider actually running on
Yggdrasil is the native bifrost-persona-feedback.service. Firing the route
would start a container next to it.

Following deploy/legacy's own order, the grant goes first, then the dispatch:
- sudoers: drop `deploy bifrost-persona-feedback *`.
- idunn-yggdrasil: drop the target's policy case and its dispatch token, plus
  the Bifrost release-authority check that only it used (requires_bifrost_authority
  and the `idunn validate-release-authority` call). A deploy request that claims
  a Bifrost authority is still refused. The manifest environment keeps
  IDUNN_REQUIRES_BIFROST_AUTHORITY=false, because the installed Epiphany manifest
  checks for it.
- README: record the retirement and the counts. Also record that on 2026-09-30
  the installed actuator matched this file, but the sudoers grant was not
  installed: the idunn user cannot sudo.

Rebinding persona-feedback to a current Idunn binding is a separate follow-up.

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

347 lines
12 KiB
Shell

#!/bin/sh
set -eu
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
LC_ALL=C
export LC_ALL
if [ "$(id -u)" -ne 0 ]; then
echo "idunn-yggdrasil must run through its narrow sudo boundary" >&2
exit 1
fi
action="${1:-}"
target="${2:-}"
compose=/srv/compose/yggdrasil-apps.yaml
brake_store=/var/lib/gamecult/idunn-authority/deployment-brake.cc
brake_lock=/var/lib/gamecult/idunn-authority/deployment-brake.cc.lock
brake_anchor=/etc/gamecult/idunn/deployment-brake-operator-anchor.cc
generation_lock=/var/lib/gamecult/idunn-authority/idunn-generation.lock
idunn_provision=/usr/local/bin/idunn-provision
require_root_owned_nonwritable_component() {
component="$1"
kind="$2"
if [ -L "$component" ]; then
echo "privileged actuator path component must not be a symlink: $component" >&2
exit 1
fi
case "$kind" in
file) [ -f "$component" ] || { echo "privileged actuator path component is not a regular file: $component" >&2; exit 1; } ;;
directory) [ -d "$component" ] || { echo "privileged actuator path component is not a directory: $component" >&2; exit 1; } ;;
*) echo "internal privileged actuator path-kind error" >&2; exit 1 ;;
esac
owner="$(stat -c '%u' -- "$component")"
mode="$(stat -c '%a' -- "$component")"
[ "$owner" = 0 ] || { echo "privileged actuator path component is not root-owned: $component" >&2; exit 1; }
case "$mode" in
''|*[!0-7]*) echo "privileged actuator path component has an unreadable mode: $component" >&2; exit 1 ;;
esac
group_mode=$(( (mode / 10) % 10 ))
other_mode=$(( mode % 10 ))
if [ $((group_mode & 2)) -ne 0 ] || [ $((other_mode & 2)) -ne 0 ]; then
echo "privileged actuator path component is group/world-writable: $component" >&2
exit 1
fi
}
require_root_owned_regular_path() {
path="$1"
case "$path" in
/*) ;;
*) echo "privileged actuator path must be absolute: $path" >&2; exit 1 ;;
esac
ancestor="$(dirname -- "$path")"
while :; do
require_root_owned_nonwritable_component "$ancestor" directory
[ "$ancestor" = / ] && break
ancestor="$(dirname -- "$ancestor")"
done
require_root_owned_nonwritable_component "$path" file
}
require_root_owned_resolved_executable() {
entrypoint="$1"
ancestor="$(dirname -- "$entrypoint")"
while :; do
require_root_owned_nonwritable_component "$ancestor" directory
[ "$ancestor" = / ] && break
ancestor="$(dirname -- "$ancestor")"
done
[ -e "$entrypoint" ] || { echo "privileged executable entrypoint is missing: $entrypoint" >&2; exit 1; }
[ "$(stat -c '%u' -- "$entrypoint")" = 0 ] || { echo "privileged executable entrypoint is not root-owned: $entrypoint" >&2; exit 1; }
resolved="$(readlink -f -- "$entrypoint")"
[ -n "$resolved" ] || { echo "privileged executable entrypoint cannot be resolved: $entrypoint" >&2; exit 1; }
require_root_owned_regular_path "$resolved"
[ -x "$resolved" ] || { echo "privileged executable is not executable: $resolved" >&2; exit 1; }
}
acquire_idunn_generation_lock() {
# Every target consequence observes one installed Idunn generation. The
# self-bootstrap owns the exclusive side of this lock while replacing and
# accepting that generation; target manifests hold the shared side from
# admission through completion. This lock does not decide brake posture,
# target lifecycle, or release authority.
require_root_owned_regular_path "$generation_lock"
exec 8<"$generation_lock"
/usr/bin/flock --shared 8
}
deployment_target_policy() {
manifest_takes_action=false
minimum_source_revision_required=false
case "$target" in
odin)
daemon_id=yggdrasil-odin
repository_full_name=GameCult/Odin
upstream_ref=refs/heads/codex/ygg-idunn-independent-bootstrap
manifest_takes_action=true
;;
codex-connector)
daemon_id=yggdrasil-codex-connector
repository_full_name=GameCult/CodexConnector
upstream_ref=refs/heads/codex/ghostlight-release-binding
minimum_source_revision_required=true
manifest_takes_action=true
;;
ghostlight)
daemon_id=yggdrasil-ghostlight
repository_full_name=GameCult/Ghostlight
upstream_ref=refs/heads/codex/ghostlight-dungeon-mvp
minimum_source_revision_required=true
manifest_takes_action=true
;;
voidbot)
daemon_id=yggdrasil-voidbot
repository_full_name=GameCult/VoidBot
upstream_ref=refs/heads/main
manifest_takes_action=true
;;
heimdall)
daemon_id=yggdrasil-heimdall
repository_full_name=GameCult/Heimdall
upstream_ref=refs/heads/main
;;
epiphany-capstone-17)
daemon_id=yggdrasil-epiphany-capstone-17
repository_full_name=GameCult/Epiphany
upstream_ref=refs/heads/codex/epiphany-shakedown-live
manifest_takes_action=true
;;
epiphany)
daemon_id=yggdrasil-epiphany
repository_full_name=GameCult/Epiphany
upstream_ref=refs/heads/codex/epiphany-shakedown-live
manifest_takes_action=true
;;
repixelizer)
daemon_id=yggdrasil-repixelizer
repository_full_name=GameCult/repixelizer
upstream_ref=refs/heads/main
;;
streampixels)
daemon_id=yggdrasil-streampixels
repository_full_name=GameCult/StreamPixels
upstream_ref=refs/heads/main
;;
gjallar)
daemon_id=yggdrasil-gjallar
repository_full_name=GameCult/Gjallar
upstream_ref=refs/heads/codex/yggdrasil-aggregate-daemon
;;
*) echo "deployment target has no root actuator policy: $target" >&2; exit 1 ;;
esac
}
require_signed_deployment_grant() {
source_commit="$3"
deployment_request_id="$4"
release_authority_id="$5"
release_authority_sha256="$6"
deployment_target_policy
case "$source_commit" in
*[!0-9a-f]*|'') echo "invalid or missing source commit" >&2; exit 1 ;;
esac
[ "${#source_commit}" -eq 40 ] || { echo "source commit must be 40 lowercase hex characters" >&2; exit 1; }
case "$deployment_request_id" in
*[!A-Za-z0-9._:/+-]*|'') echo "invalid or missing Idunn deployment request id" >&2; exit 1 ;;
esac
[ "${#deployment_request_id}" -le 256 ] || { echo "Idunn deployment request id is oversized" >&2; exit 1; }
case "$deployment_request_id" in
"deploy:$daemon_id:"?*|"manual:redeploy:$daemon_id:"?*) ;;
*) echo "signed deployment request is not for root target $target" >&2; exit 1 ;;
esac
# No target carries Bifrost release authority: its only one, bifrost-persona-feedback,
# was retired on 2026-09-30. A request that claims one is refused.
[ -z "$release_authority_id" ] || { echo "source-observed release cannot claim Bifrost authority id" >&2; exit 1; }
[ -z "$release_authority_sha256" ] || { echo "source-observed release cannot claim Bifrost authority digest" >&2; exit 1; }
require_root_owned_resolved_executable "$idunn_provision"
require_root_owned_regular_path "$brake_store"
require_root_owned_regular_path "$brake_lock"
require_root_owned_regular_path "$brake_anchor"
# This validation is the deployment-admission point. The signed expiry is a
# start-before deadline: it denies a later transaction, but does not become a
# timer-shaped veto over one already admitted. The root actuator remains the
# sole shared-lock holder through the synchronous consequence, so an explicit
# engage/release transition cannot race or rewrite that transaction.
exec 9<"$brake_lock"
/usr/bin/flock --shared 9
now_unix_millis="$(/usr/bin/date +%s%3N)"
case "$now_unix_millis" in
''|*[!0-9]*) echo "root actuator clock did not produce Unix milliseconds" >&2; exit 1 ;;
esac
"$idunn_provision" deployment-brake-status \
--store "$brake_store" \
--operator-anchor "$brake_anchor" \
--runtime-id yggdrasil \
--release-id "$source_commit" \
--deployment-id "$deployment_request_id" \
--now-unix-millis "$now_unix_millis"
if [ "$minimum_source_revision_required" = true ]; then
require_root_owned_resolved_executable /usr/local/bin/idunn
/usr/local/bin/idunn validate-minimum-source-revision \
--daemon "$daemon_id" \
--source-revision "$source_commit"
fi
}
manifest_group_exists() {
kill -s 0 "-$manifest_pid" 2>/dev/null
}
signal_manifest_group() {
kill -s "$1" "-$manifest_pid" 2>/dev/null || true
}
forward_manifest_signal() {
manifest_wait_interrupted=true
signal_manifest_group "$1"
}
run_manifest_while_holding_authority_locks() {
# Stay alive as the sole lock owner. The child must not inherit descriptors
# 8 or 9: flock locks attach to shared open-file descriptions, so an
# inherited descriptor could retain or unlock the parent's generation or
# deployment transaction. The owned process group also prevents ordinary
# descendants from outliving interruption.
require_root_owned_resolved_executable /usr/bin/setsid
/usr/bin/setsid "$@" 8<&- 9<&- &
manifest_pid=$!
manifest_wait_interrupted=false
trap 'forward_manifest_signal HUP' HUP
trap 'forward_manifest_signal INT' INT
trap 'forward_manifest_signal TERM' TERM
while :; do
manifest_wait_interrupted=false
if wait "$manifest_pid"; then
manifest_status=0
else
manifest_status=$?
fi
if [ "$manifest_wait_interrupted" = true ] && manifest_group_exists; then
continue
fi
break
done
trap - HUP INT TERM
manifest_descendants_leaked=false
if manifest_group_exists; then
manifest_descendants_leaked=true
signal_manifest_group TERM
for _ in 1 2 3 4 5; do
manifest_group_exists || break
sleep 1
done
fi
if manifest_group_exists; then
signal_manifest_group KILL
for _ in 1 2 3 4 5; do
manifest_group_exists || break
sleep 1
done
fi
if manifest_group_exists; then
echo "deployment manifest process group survived SIGKILL" >&2
return 1
fi
if [ "$manifest_descendants_leaked" = true ] && [ "$manifest_status" -eq 0 ]; then
echo "deployment manifest left a descendant process behind" >&2
return 1
fi
return "$manifest_status"
}
case "$action" in
deploy)
[ "$#" -eq 6 ] || { echo "deploy requires exact target, source, request, and release-authority arguments" >&2; exit 2; }
;;
restart)
[ "$#" -eq 2 ] || { echo "restart accepts only one exact target" >&2; exit 2; }
;;
*)
echo "unsupported Idunn Yggdrasil actuator action: $action" >&2
exit 2
;;
esac
acquire_idunn_generation_lock
case "$action:$target" in
restart:heimdall)
require_root_owned_regular_path "$compose"
exec docker compose -f "$compose" restart heimdall
;;
restart:repixelizer)
require_root_owned_regular_path "$compose"
exec docker compose -f "$compose" restart repixelizer
;;
restart:streampixels)
require_root_owned_regular_path "$compose"
docker compose -f "$compose" restart streampixels-service streampixels-web
;;
restart:gjallar)
exec systemctl restart gjallar-yggdrasil.service
;;
restart:voidbot|restart:epiphany|restart:odin|restart:codex-connector|restart:ghostlight)
manifest="/srv/odin/deploy-manifests/$target"
require_root_owned_regular_path "$manifest"
[ -x "$manifest" ] || { echo "root actuator manifest is not executable: $manifest" >&2; exit 1; }
export IDUNN_ACTUATOR=1
export IDUNN_COMMAND_AUTHORITY=idunn-daemon
run_manifest_while_holding_authority_locks "$manifest" restart
exit $?
;;
deploy:voidbot|deploy:epiphany|deploy:epiphany-capstone-17|deploy:odin|deploy:codex-connector|deploy:ghostlight|deploy:heimdall|deploy:repixelizer|deploy:streampixels|deploy:gjallar)
manifest="/srv/odin/deploy-manifests/$target"
require_root_owned_regular_path "$manifest"
[ -x "$manifest" ] || { echo "root actuator manifest is not executable: $manifest" >&2; exit 1; }
require_signed_deployment_grant "$@"
export IDUNN_ACTUATOR=1
export IDUNN_COMMAND_AUTHORITY=idunn-daemon
export IDUNN_SOURCE_COMMIT="$source_commit"
export IDUNN_REPOSITORY_FULL_NAME="$repository_full_name"
export IDUNN_UPSTREAM_REF="$upstream_ref"
export BIFROST_RELEASE_AUTHORITY_ID="$release_authority_id"
export BIFROST_RELEASE_AUTHORITY_SHA256="$release_authority_sha256"
export IDUNN_DEPLOYMENT_REQUEST_ID="$deployment_request_id"
# Installed manifests (Epiphany's) still require this to read false.
export IDUNN_REQUIRES_BIFROST_AUTHORITY=false
if [ "$manifest_takes_action" = true ]; then
run_manifest_while_holding_authority_locks "$manifest" "$action"
exit $?
fi
run_manifest_while_holding_authority_locks "$manifest"
exit $?
;;
*)
echo "unsupported Idunn Yggdrasil actuator request: $action $target" >&2
exit 2
;;
esac