bridge the host pi config, with the credential by env only
The image ships pi with no provider and no preferences, so a sandboxed run
meant configuring the model twice. ai-sandbox now reads the host
~/.pi/agent/{models,settings}.json and rewrites them instead of copying them:
every provider apiKey becomes "$AI_API_TOKEN" (pi resolves an env reference
itself, so no value is written to disk), '!command' header values are dropped
rather than executed, settings that point into $HOME are dropped because that
path is not mounted, and auth.json / models-store.json / sessions / extensions
are never read.
The rewritten pair is seeded into /home/sandbox/.pi/agent at container start,
owned by uid 1000 and mode 600, and is treated as managed: a host change is
re-seeded, an edit made inside the sandbox is kept and reported, a deletion
takes the host version again.
The token follows the existing environment rules -- one variable, passed by
name through the 0600 env file, announced when auto-passed. A bridged run
whose token variable is unset or empty is refused instead of starting a pi
that cannot reach its model, and the bridge never widens the allowlist: it
warns with the exact --allow to add.
Docs and examples now use a self-hosted gateway (https://aipi.reeseapps.com)
instead of vendor API endpoints, since that is the only thing this tool is
used against. 391 assertions pass (make lint, ./tests/run.sh --e2e).
LLM-Generated: true
This commit is contained in:
+264
-7
@@ -11,7 +11,7 @@
|
||||
# Meant to be aliased to an agent harness:
|
||||
#
|
||||
# alias pi='ai-sandbox'
|
||||
# cd ~/Projects/foo && pi --allow api.anthropic.com --env ANTHROPIC_API_KEY
|
||||
# cd ~/Projects/foo && pi --allow aipi.reeseapps.com --env AI_API_TOKEN
|
||||
#
|
||||
set -Eeuo pipefail
|
||||
|
||||
@@ -145,7 +145,7 @@ USAGE
|
||||
behave like the harness itself:
|
||||
|
||||
alias pi='$PROG'
|
||||
pi --allow api.anthropic.com --env ANTHROPIC_API_KEY --model sonnet
|
||||
pi --allow aipi.reeseapps.com --env AI_API_TOKEN --model turbo
|
||||
# ^ sandbox flags ^ arguments for pi
|
||||
|
||||
pi src/main.py # runs: pi src/main.py
|
||||
@@ -193,6 +193,17 @@ IMAGE
|
||||
Each distinct set gets its own cached image tag.
|
||||
--harness NAME Default in-container command: pi | codex | claude | none.
|
||||
|
||||
PI CONFIG
|
||||
Your ~/.pi/agent/{models,settings}.json are rewritten and seeded into the sandbox:
|
||||
every provider apiKey becomes an environment reference, so no credential is written
|
||||
into the container, and host commands ('!...') and host paths are dropped instead of
|
||||
followed. auth.json and the credential store are never bridged.
|
||||
--pi-token-env NAME Variable the bridged config should read the key from
|
||||
(default AI_API_TOKEN). Passed by name if it is set.
|
||||
--no-pi-token-env Do not pass it automatically; then pass it with --env or
|
||||
the run is refused rather than starting a pi with no key.
|
||||
--no-pi-config Do not bridge at all; pi starts with its own defaults.
|
||||
|
||||
LIMITS
|
||||
--cpus N (4) --memory SIZE (4g) --pids N (512) --tmpfs-size SIZE (1g)
|
||||
|
||||
@@ -272,7 +283,8 @@ cfg_read_kv() { # `key = value` file
|
||||
fi
|
||||
case "$key" in
|
||||
net|harness|workdir|cpus|memory|pids|tmpfs_size|base_image|node_version|pi_version|\
|
||||
project_dest|selinux|home_cache|gate_addr|gate_port|extra_args|base_ttl_days|base_auto_update) CFG["$key"]="$val" ;;
|
||||
project_dest|selinux|home_cache|gate_addr|gate_port|extra_args|base_ttl_days|base_auto_update|\
|
||||
pi_config|pi_token_env|pi_token_auto|pi_agent_dir) CFG["$key"]="$val" ;;
|
||||
*) warn "$file: unknown key: $key" ;;
|
||||
esac
|
||||
done < "$file"
|
||||
@@ -310,6 +322,7 @@ OPT_NAME="" OPT_PROXY="" OPT_GATE="" OPT_BASE_IMAGE=""
|
||||
OPT_NO_CWD=0 OPT_SHELL=0 OPT_DRY=0 OPT_PRINT=0 OPT_NO_BUILD=0 OPT_KEEP=0 OPT_READONLY=0
|
||||
OPT_HOME_CACHE="" OPT_STRICT="${AISB_STRICT_ARGS:-0}" OPT_UNENFORCED=0 OPT_KEEP_ROOT=0
|
||||
OPT_TTY="" OPT_INTERACTIVE="" OPT_PRINT_EP=0 OPT_PRINT_CF=0 OPT_PASSTHROUGH=0
|
||||
OPT_PI_CONFIG="" OPT_PI_TOKEN_AUTO="" OPT_PI_TOKEN_ENV=""
|
||||
declare -a OPT_allow=() OPT_env=() OPT_mount=() OPT_install=() OPT_label=() OPT_allow_files=()
|
||||
COMMAND=()
|
||||
|
||||
@@ -319,7 +332,7 @@ KNOWN_OPTS=(
|
||||
net allow allow-file proxy no-gate env env-file mount no-cwd workdir home-cache ephemeral-home
|
||||
harness install base-image cpus memory pids tmpfs-size name label keep read-only shell
|
||||
interactive no-tty dry-run print-policy no-build allow-unenforced strict-args insecure-keep-root
|
||||
print-entrypoint print-containerfile help version
|
||||
print-entrypoint print-containerfile pi-token-env no-pi-config no-pi-token-env help version
|
||||
)
|
||||
suggest() {
|
||||
local given="$1" o hit=0 hits=""
|
||||
@@ -339,6 +352,10 @@ parse_args() {
|
||||
--net) need "$1" "${2:-}"; OPT_NET="$2"; shift 2 ;;
|
||||
--net=*) OPT_NET="${1#*=}"; shift ;;
|
||||
--harness) need "$1" "${2:-}"; OPT_HARNESS="$2"; shift 2 ;;
|
||||
--no-pi-config) OPT_PI_CONFIG=0; shift ;;
|
||||
--no-pi-token-env) OPT_PI_TOKEN_AUTO=0; shift ;;
|
||||
--pi-token-env) need "$1" "${2:-}"; OPT_PI_TOKEN_ENV="$2"; shift 2 ;;
|
||||
--pi-token-env=*) OPT_PI_TOKEN_ENV="${1#*=}"; shift ;;
|
||||
--harness=*) OPT_HARNESS="${1#*=}"; shift ;;
|
||||
--allow) need "$1" "${2:-}"; OPT_allow+=("$2"); shift 2 ;;
|
||||
--allow=*) OPT_allow+=("${1#*=}"); shift ;;
|
||||
@@ -416,6 +433,16 @@ resolve_opts() {
|
||||
POL[pids]="${OPT_PIDS:-${AISB_PIDS:-$(cfgv pids 512)}}"
|
||||
POL[tmpfs]="${OPT_TMPFS:-${AISB_TMPFS_SIZE:-$(cfgv tmpfs_size 1g)}}"
|
||||
POL[base_image]="${OPT_BASE_IMAGE:-${AISB_BASE_IMAGE:-$(cfgv base_image docker.io/library/ubuntu:24.04)}}"
|
||||
# --- pi configuration bridge (see pi_config_bridge) -------------------------
|
||||
POL[pi_agent_dir]="${AISB_PI_AGENT_DIR:-$(cfgv pi_agent_dir "${HOME%/}/.pi/agent")}"
|
||||
POL[pi_config]="${OPT_PI_CONFIG:-${AISB_PI_CONFIG:-$(cfgv pi_config 1)}}"
|
||||
case "${POL[pi_config]}" in 0|false|no) POL[pi_config]=0 ;; *) POL[pi_config]=1 ;; esac
|
||||
POL[pi_token_env]="${OPT_PI_TOKEN_ENV:-${AISB_PI_TOKEN_ENV:-$(cfgv pi_token_env AI_API_TOKEN)}}"
|
||||
[[ "${POL[pi_token_env]}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] \
|
||||
|| die "pi token variable name is not a valid identifier: '${POL[pi_token_env]}'"
|
||||
POL[pi_token_auto]="${OPT_PI_TOKEN_AUTO:-${AISB_PI_TOKEN_AUTO:-$(cfgv pi_token_auto 1)}}"
|
||||
case "${POL[pi_token_auto]}" in 0|false|no) POL[pi_token_auto]=0 ;; *) POL[pi_token_auto]=1 ;; esac
|
||||
|
||||
POL[base_ttl]="${AISB_BASE_TTL_DAYS:-$(cfgv base_ttl_days 7)}"
|
||||
[[ "${POL[base_ttl]}" =~ ^-?[0-9]+$ ]] || { warn "base_ttl_days must be an integer, using 7"; POL[base_ttl]=7; }
|
||||
POL[base_auto]="${AISB_BASE_AUTO_UPDATE:-$(cfgv base_auto_update 1)}"
|
||||
@@ -477,7 +504,7 @@ validate_opts() {
|
||||
fi
|
||||
if [[ "${POL[net]}" == allow && ${#P_allow[@]} -eq 0 && -z "${POL[proxy]}" ]]; then
|
||||
die "no egress allowlist configured. Refusing to start wide open.
|
||||
add one: $PROG --allow api.openai.com --env OPENAI_API_KEY
|
||||
add one: $PROG --allow aipi.reeseapps.com --env AI_API_TOKEN
|
||||
or go dark: $PROG --net none
|
||||
or opt out: $PROG --net full"
|
||||
fi
|
||||
@@ -740,6 +767,43 @@ if [[ "$KEEP_ROOT" == 1 ]]; then
|
||||
fi
|
||||
|
||||
command -v setpriv >/dev/null || fail "setpriv is missing from the image; rebuild it" 70
|
||||
|
||||
# --- pi configuration bridge -------------------------------------------------
|
||||
# ai-sandbox may have generated a models.json / settings.json for pi from the host's
|
||||
# agent directory (with the credential replaced by an environment reference). They are
|
||||
# seeded into the sandbox user's home, because pi needs that directory writable: it
|
||||
# keeps sessions, extensions and /settings edits there.
|
||||
#
|
||||
# Seeding runs as the sandbox user, not as container root: HOME belongs to that uid and
|
||||
# the entrypoint has no CAP_DAC_OVERRIDE, so root could not write there anyway. It is
|
||||
# also the honest identity -- these are the user's own files.
|
||||
#
|
||||
# A file is only replaced when it is missing or still identical to what we seeded last
|
||||
# time, so an edit made inside the sandbox survives; a change on the host wins next run.
|
||||
if [[ -d "$POLICY_DIR/pi" ]]; then
|
||||
setpriv --reuid="$UID_TARGET" --regid="$GID_TARGET" --clear-groups -- /bin/bash -c '
|
||||
dst="$HOME/.pi/agent"
|
||||
mkdir -p "$dst" || { echo "[aisb] pi config: cannot create $dst" >&2; exit 0; }
|
||||
for f in models.json settings.json; do
|
||||
src="$1/pi/$f"
|
||||
[ -f "$src" ] || continue
|
||||
tgt="$dst/$f"; mark="$dst/.aisb-$f.seeded"
|
||||
want="$(sha256sum "$src" | cut -c1-16)"
|
||||
cur=""; [ -f "$tgt" ] && cur="$(sha256sum "$tgt" | cut -c1-16)"
|
||||
prev=""; [ -f "$mark" ] && prev="$(cat "$mark" 2>/dev/null)"
|
||||
if [ -z "$cur" ]; then
|
||||
install -m 600 "$src" "$tgt" && printf %s "$want" > "$mark"
|
||||
echo "[aisb] pi config: seeded $f"
|
||||
elif [ "$cur" = "$prev" ]; then
|
||||
install -m 600 "$src" "$tgt" && printf %s "$want" > "$mark"
|
||||
echo "[aisb] pi config: refreshed $f from the host config"
|
||||
else
|
||||
echo "[aisb] pi config: left $f alone (changed inside the sandbox; delete it to take the host version again)"
|
||||
fi
|
||||
done
|
||||
' aisb-seed "$POLICY_DIR" || log "pi config: seeding failed"
|
||||
fi
|
||||
|
||||
exec setpriv --reuid="$UID_TARGET" --regid="$GID_TARGET" --clear-groups \
|
||||
--inh-caps=-all --bounding-set=-all --no-new-privs -- "$@"
|
||||
AISB_ENTRY
|
||||
@@ -1080,6 +1144,165 @@ cmd_build() {
|
||||
if (( want_gate )); then gref="$(ensure_image gate)"; log "gate image: $gref"; fi
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# pi configuration bridge
|
||||
# ===========================================================================
|
||||
# pi reads <agent-dir>/models.json and <agent-dir>/settings.json, the agent directory
|
||||
# defaulting to ~/.pi/agent. Inside the sandbox that home is a volume, so without a
|
||||
# bridge you get a virgin pi with no provider and no preferences.
|
||||
#
|
||||
# Copying the host files would be worse than useless: models.json may hold a literal
|
||||
# key, or a "!pass show ..." command that can only run on the host and would leak the
|
||||
# secret into a container that is meant to be able to talk to nobody but the gate. So
|
||||
# the bridge rewrites instead of copying:
|
||||
#
|
||||
# * every provider apiKey becomes "$AI_API_TOKEN" (name settable) -- pi itself
|
||||
# resolves that interpolation at request time, so no value is ever written to disk
|
||||
# * header values that were "!command" are dropped, never executed
|
||||
# * settings keys pointing at the host home are dropped rather than left dangling
|
||||
# * auth.json, models-store.json, sessions, extensions and everything else stay on
|
||||
# the host -- they are credentials or host-side executables, not preferences
|
||||
#
|
||||
# The rewritten pair lands in the session dir (which is mounted at /run/aisb) and the
|
||||
# entrypoint seeds it into the sandbox user's home. Only the variable *name* crosses
|
||||
# the boundary in the container's environment; the value travels through the 0600
|
||||
# env file like any other --env.
|
||||
|
||||
declare -a PI_PROVIDER_HOSTS=()
|
||||
PI_GEN_DIR=""
|
||||
|
||||
pi_agent_dir() { printf '%s' "${POL[pi_agent_dir]:-${AISB_PI_AGENT_DIR:-${HOME%/}/.pi/agent}}"; }
|
||||
|
||||
# Is pi itself the thing we are about to launch? Only then is a missing credential a
|
||||
# reason to refuse: --shell, --harness make, or a command after -- all run without it.
|
||||
# codex/claude are excluded because models.json is pi's file, not theirs.
|
||||
pi_harness_in_use() {
|
||||
(( OPT_SHELL )) && return 1
|
||||
(( ${#COMMAND[@]} )) && return 1
|
||||
case "${POL[harness]:-pi}" in
|
||||
pi|pi\ *) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# baseUrl hostnames the bridged config points at, for the egress hint below.
|
||||
pi_provider_hosts_of() { # $1=models.json
|
||||
jq -r '[(.providers // {})[]? | (.baseUrl? // empty),
|
||||
((.models // [])[]? | .baseUrl? // empty)] | .[]' "$1" 2>/dev/null \
|
||||
| sed -E 's#^[A-Za-z][A-Za-z0-9+.-]*://##; s#[/?].*$##; s#:[0-9]+$##' | sort -u
|
||||
}
|
||||
|
||||
pi_config_bridge() {
|
||||
PI_GEN_DIR=""
|
||||
[[ "${POL[pi_config]}" == 1 ]] || return 0
|
||||
local src dst tok="${POL[pi_token_env]}"
|
||||
src="$(pi_agent_dir)"
|
||||
if [[ ! -d "$src" ]]; then
|
||||
(( OPT_PRINT || OPT_DRY )) || log "pi config: no agent directory at $src -- pi starts with its own defaults"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v jq > /dev/null 2>&1; then
|
||||
warn "pi config: jq is not installed, so the pi config was not bridged (or use --no-pi-config to say so deliberately)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
dst="$SESSION_MOUNT/pi"
|
||||
mkdir -p "$dst"; chmod 755 "$dst"
|
||||
PI_GEN_DIR="$dst"
|
||||
|
||||
# --- models.json: same providers and models, credential from the environment
|
||||
if [[ -r "$src/models.json" ]]; then
|
||||
if ! jq -e . "$src/models.json" > /dev/null 2>&1; then
|
||||
warn "pi config: $src/models.json is not valid JSON -- pi starts without it"
|
||||
else
|
||||
jq --arg env "$tok" '
|
||||
(.providers // {}) |= with_entries(
|
||||
if (.value | type) == "object" and (.value | has("apiKey"))
|
||||
then .value.apiKey = ("$" + $env) else . end)
|
||||
| (.providers // {}) |= with_entries(
|
||||
if (.value | type) == "object" and (.value | has("headers"))
|
||||
then .value.headers = (.value.headers | with_entries(
|
||||
select(((.value | type) == "string" and (.value | test("^!"))) | not)))
|
||||
else . end)
|
||||
' "$src/models.json" > "$dst/models.json" || die "pi config: could not rewrite $src/models.json"
|
||||
chmod 644 "$dst/models.json"
|
||||
|
||||
local rewrote dropped
|
||||
rewrote="$(jq -r '[(.providers // {}) | to_entries[] | select(.value | has("apiKey")) | .key] | join(", ")' "$src/models.json")"
|
||||
dropped="$(jq -r '[(.providers // {})[]? | .headers? // {} | to_entries[]
|
||||
| select((.value | type) == "string" and (.value | test("^!"))) | .key] | join(", ")' "$src/models.json")"
|
||||
[[ -n "$rewrote" ]] && log "pi config: $rewrote -> apiKey \$$tok"
|
||||
[[ -n "$dropped" ]] && warn "pi config: dropped header(s) $dropped -- a '!command' runs on the host, not in here"
|
||||
|
||||
mapfile -t PI_PROVIDER_HOSTS < <(pi_provider_hosts_of "$src/models.json")
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- settings.json: preferences travel, host paths do not
|
||||
if [[ -r "$src/settings.json" ]]; then
|
||||
if ! jq -e . "$src/settings.json" > /dev/null 2>&1; then
|
||||
warn "pi config: $src/settings.json is not valid JSON -- pi starts without it"
|
||||
else
|
||||
local homep="${HOME%/}" dropped
|
||||
jq --arg home "$homep" '
|
||||
with_entries(select(((.value | type) == "string" and (.value | contains($home))) | not))
|
||||
' "$src/settings.json" > "$dst/settings.json" || die "pi config: could not rewrite $src/settings.json"
|
||||
chmod 644 "$dst/settings.json"
|
||||
dropped="$(jq -r --arg home "$homep" '[to_entries[]
|
||||
| select((.value | type) == "string" and (.value | contains($home))) | .key] | join(", ")' "$src/settings.json")"
|
||||
[[ -n "$dropped" ]] && warn "pi config: dropped settings $dropped -- they point inside the host home, which is not mounted"
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -f "$dst/models.json" || -f "$dst/settings.json" ]] || { rmdir "$dst" 2>/dev/null || true; PI_GEN_DIR=""; return 0; }
|
||||
|
||||
# --- the credential: exactly one variable, named by the config we just wrote
|
||||
local spec in_list=0
|
||||
for spec in ${P_env[@]+"${P_env[@]}"}; do
|
||||
[[ "$spec" == "$tok" || "$spec" == "$tok="* ]] && in_list=1
|
||||
done
|
||||
if (( in_list == 0 )) && (( POL[pi_token_auto] )) && [[ -n "${!tok:-}" ]]; then
|
||||
P_env+=("$tok")
|
||||
log "pi config: passing \$$tok from the host because the bridged config asks for it (--no-pi-token-env to stop)"
|
||||
in_list=1
|
||||
fi
|
||||
# A bridged provider with no credential in reach is a run where pi starts and then
|
||||
# says nothing, so it is refused rather than attempted -- except in preview, which
|
||||
# reports and carries on so a policy can be inspected before the secret exists.
|
||||
if (( in_list == 0 )) && pi_harness_in_use && [[ -f "$dst/models.json" ]]; then # unset or empty: both useless to pi
|
||||
if (( OPT_PRINT || OPT_DRY )); then
|
||||
warn "pi config: \$$tok will not reach the container -- pi would have no credential"
|
||||
else
|
||||
die "pi config asks for \$$tok, but it is not going to reach the container$([[ -n "${!tok:-}" ]] && printf ' (it is set on the host and auto-passing is off)' || printf ' (it is unset or empty in the host environment)').
|
||||
export $tok=... and re-run
|
||||
or add to $(cfg_or_home): env = $tok
|
||||
or pass it explicitly: --env $tok
|
||||
or stop bridging: --no-pi-config"
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
cfg_or_home() { printf '%s/config' "$AISB_CONFIG_DIR"; }
|
||||
|
||||
# The one failure mode that looks like a broken sandbox rather than a missing
|
||||
# allowlist entry: pi starts, says nothing, and the user blames the model.
|
||||
pi_egress_hint() {
|
||||
[[ "${POL[net]}" == allow ]] || return 0
|
||||
pi_harness_in_use || return 0
|
||||
((${#PI_PROVIDER_HOSTS[@]})) || return 0
|
||||
local h a not_allowed=""
|
||||
for h in "${PI_PROVIDER_HOSTS[@]}"; do
|
||||
local hit=0
|
||||
for a in ${P_allow[@]+"${P_allow[@]}"}; do
|
||||
[[ "${a%%:*}" == "$h" || "${a%%:*}" == "*.$h" || "${a%%:*}" == "*" ]] && hit=1
|
||||
done
|
||||
(( hit )) || not_allowed="${not_allowed:+$not_allowed }$h"
|
||||
done
|
||||
[[ -n "$not_allowed" ]] && warn "pi config: the model endpoint is not in the allowlist ($not_allowed). pi will start and be unable to answer -- add --allow ${not_allowed%% *} or put it in .ai-sandbox/allow"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# egress gate
|
||||
# ===========================================================================
|
||||
@@ -1221,7 +1444,7 @@ default_command() {
|
||||
# nothing given at all: just start the harness (or a shell)
|
||||
if ((${#hb[@]})); then COMMAND=("${hb[@]}"); else COMMAND=(bash -li); fi
|
||||
elif [[ "${OPT_PASSTHROUGH:-0}" == 1 && ${#hb[@]} -gt 0 ]]; then
|
||||
# arguments meant for the harness, e.g. `pi --model sonnet`
|
||||
# arguments meant for the harness, e.g. `pi --model turbo`
|
||||
COMMAND=("${hb[@]}" "${COMMAND[@]}")
|
||||
fi
|
||||
# otherwise -- was used: the command replaces the harness
|
||||
@@ -1287,6 +1510,10 @@ build_argv() {
|
||||
fi
|
||||
[[ -s "$ENV_FILE" ]] && ARGV+=(--env-file "$ENV_FILE")
|
||||
|
||||
# The pi bridge rides the policy mount: $SESSION/pi shows up as /run/aisb/pi, where
|
||||
# the entrypoint picks it up. No extra mount, and it inherits the read-only policy.
|
||||
[[ -n "${PI_GEN_DIR:-}" && -d "${PI_GEN_DIR}" ]] && ARGV+=(--label aisb.pi-config=bridged)
|
||||
|
||||
[[ "${OPT_NO_CWD:-0}" == 1 ]] || ARGV+=(-v "${POL[cwd]}:${POL[workdir]}${SELOPT}")
|
||||
ARGV+=(-v "${SESSION_MOUNT}:/run/aisb:ro${SELOPT_SEP}")
|
||||
if [[ "${POL[home_cache]}" == 1 ]]; then
|
||||
@@ -1340,6 +1567,13 @@ print_argv() {
|
||||
print_policy() {
|
||||
printf 'image=%s\n' "${POL[image_ref]}"
|
||||
printf 'gate_image=%s\n' "$(gate_image_ref)"
|
||||
if [[ -n "${PI_GEN_DIR:-}" ]]; then
|
||||
printf 'pi_config=%s\n' "bridged from $(pi_agent_dir)"
|
||||
local _ptv="${POL[pi_token_env]}"
|
||||
printf 'pi_token_env=%s (%s)\n' "$_ptv" \
|
||||
"$([[ -n "${!_ptv:-}" ]] && printf set || { [[ -v "$_ptv" ]] && printf 'set but empty' || printf 'NOT SET'; })"
|
||||
((${#PI_PROVIDER_HOSTS[@]})) && printf 'pi_endpoints=%s\n' "${PI_PROVIDER_HOSTS[*]}"
|
||||
fi
|
||||
printf 'base=%s\n' "${POL[base_image]}"
|
||||
printf 'base_digest=%s\n' "${POL[base_digest]:-unpinned}"
|
||||
printf 'base_age_days=%s\n' "$(base_age_days "${POL[base_image]}")"
|
||||
@@ -1395,8 +1629,10 @@ cmd_run() {
|
||||
fi
|
||||
resolve_proxy_endpoints
|
||||
|
||||
pi_config_bridge
|
||||
build_env_file
|
||||
write_policy_files
|
||||
pi_egress_hint
|
||||
build_argv
|
||||
|
||||
if (( OPT_PRINT )); then print_policy; exit 0; fi
|
||||
@@ -1500,6 +1736,25 @@ cmd_doctor() {
|
||||
|
||||
chk INFO "policy: net=${POL[net]} harness=${POL[harness]} limits=cpus:${POL[cpus]},memory:${POL[memory]},pids:${POL[pids]}"
|
||||
chk INFO "dirs: config=$AISB_CONFIG_DIR state=$AISB_STATE_DIR cache=$AISB_CACHE_DIR"
|
||||
|
||||
# pi bridge, reported read-only: doctor must not generate a session to say this.
|
||||
local padir="${POL[pi_agent_dir]}" ptok="${POL[pi_token_env]}"
|
||||
if [[ "${POL[pi_config]}" != 1 ]]; then
|
||||
chk INFO "pi config: bridging is off (pi_config=0 or --no-pi-config)"
|
||||
elif [[ ! -d "$padir" ]]; then
|
||||
chk INFO "pi config: no agent directory at $padir -- pi starts with its own defaults"
|
||||
else
|
||||
local -a ph=()
|
||||
if command -v jq > /dev/null 2>&1 && [[ -r "$padir/models.json" ]]; then
|
||||
mapfile -t ph < <(pi_provider_hosts_of "$padir/models.json")
|
||||
fi
|
||||
chk INFO "pi config: bridged from $padir (endpoints: ${ph[*]:-none})"
|
||||
if [[ -n "${!ptok:-}" ]]; then
|
||||
chk OK "pi credential: \$$ptok is set and will be passed by name"
|
||||
else
|
||||
chk WARN "pi credential: \$$ptok is unset or empty -- a bridged pi run is refused (export it, add it to $(cfg_or_home), or use --no-pi-config)"
|
||||
fi
|
||||
fi
|
||||
if ((${#P_allow[@]})); then
|
||||
chk INFO "allowlist from config/env: ${P_allow[*]}"
|
||||
else
|
||||
@@ -1688,16 +1943,18 @@ cmd_update() {
|
||||
}
|
||||
|
||||
cmd_inspect() {
|
||||
mkdir -p "$AISB_STATE_DIR"
|
||||
mkdir -p "$AISB_STATE_DIR" "$AISB_CACHE_DIR"
|
||||
load_config
|
||||
parse_args "$@"
|
||||
session_init
|
||||
trap teardown EXIT
|
||||
resolve_opts
|
||||
set_selinux "${POL[selinux]}"
|
||||
POL[image_ref]="$(image_ref)"
|
||||
classify_allows
|
||||
GATE_PORT="${POL[gate_port]}"
|
||||
resolve_proxy_endpoints
|
||||
pi_config_bridge
|
||||
build_env_file
|
||||
print_policy
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user