Files
stack/scripts/instance.sh
T
jason.woltje 00d4cc1f69 stack: several Mosaic Stack instances on one host (row 190, #1688)
Squash of Darkwing's 176a6bf4b..df65a3d4c on origin/darkwing/row190-r3,
the 25 manifest paths, build-r3.patch efe898c2, manifest cc0ea9e8
(Filbert approve round 3, comment 29713). Design revision 3 in
agents/darkwing/work/queue-190/DESIGN.md. Gate on base d0bbc5c1c with
the patch: 15 package suites 2664 pass 0 fail, 10 scripts/test-*.sh
suites 429 pass 0 fail (plus 156 node tests in the queue suite).
Refactor moved to 99a406ed3 since, a window record touching no manifest
path. Mutants 20 of 20 killed (BUILD.md, round 3). No live change: the
stack runs from copy 4429a104f until a re-pin.
2026-10-20 17:49:06 -05:00

143 lines
6.7 KiB
Bash

# Sourced, not run: stack instance selection for the shell entry points (row 190).
#
# instance_args "$@" take `--instance NAME` out of the arguments. Sets
# INSTANCE_FLAG (NAME, or empty) and INSTANCE_ARGS (the
# rest, in order). A second --instance or a missing
# NAME exits 4.
# instance_select run `mosaic-config.mjs instance [--instance NAME]`,
# which maps NAME to ~/.config/mosaic-NAME/config.json
# and checks the data root's instance.json claim, then
# set the names below. A refusal exits with the
# loader's code (3 claim or missing config, 4 name or
# mismatch) before the caller reads or writes anything.
#
# With no flag, every name is today's and MOSAIC_CONFIG is left as the caller
# had it. With --instance NAME (other than dev), MOSAIC_CONFIG is exported as
# NAME's config, so everything the entry point runs reads that config.
#
# BUS_TEMPLATE [email protected] | [email protected]
# BUS_GLOB 'mosaic-bus@*.service' | 'mosaic-bus-NAME@*.service'
# BUS_DROPIN_GLOBS unit-path globs whose .conf files can override the bus
# ExecStart: mosaic-bus@*.service.d for the default;
# mosaic-bus-NAME@*.service.d and the shared prefix
# mosaic-bus-.service.d for NAME; then mosaic-.service.d
# and service.d for both
# CONSOLE_UNIT mosaic-webui.service | mosaic-webui-NAME.service
# DISCORD_TEMPLATE [email protected] | [email protected]
# WRAPPER_NAME mosaic | mosaic-NAME
# SESSION_ENV %h/.config/mosaic-dev/secrets/claude-session.env
# | <config dir>/secrets/claude-session.env
# INSTANCE_FLAGS () or (--instance NAME), for the entry points this one runs
# INSTANCE_RESERVED ports a named instance's Console never takes: 7330 (the
# operator's bare Console), 7332 (the tunnel) and 7333
# (the default's tester); empty for the default
# INSTANCE_SED sed arguments that fill @CONFIG_ENV@ and @SESSION_ENV@
# in a unit template: the default deletes the
# @CONFIG_ENV@ line, so its render is byte-identical to
# the template before row 190; NAME gets a comment and
# Environment=MOSAIC_CONFIG=<config>, " [NAME]" after
# the Description, and the Console comment's "sets no
# MOSAIC_*" says it sets MOSAIC_CONFIG (Sage, N3)
#
# Also set: MOSAIC_INSTANCE (dev or NAME), MOSAIC_INSTANCE_CONFIG,
# MOSAIC_INSTANCE_CONFIG_DIR, MOSAIC_INSTANCE_DATA_ROOT (empty when no flag
# was given and the config is missing or invalid; the caller's own load
# refuses that as before) and MOSAIC_UNIT_SUFFIX (empty or -NAME).
INSTANCE_FLAG=""
INSTANCE_ARGS=()
INSTANCE_FLAGS=()
INSTANCE_RESERVED=()
instance_args() {
local seen=0
INSTANCE_FLAG=""
INSTANCE_ARGS=()
while [ $# -gt 0 ]; do
if [ "$1" = "--instance" ]; then
if [ "$seen" = 1 ] || [ $# -lt 2 ]; then
echo "instance: usage: --instance NAME, given once" >&2
exit 4
fi
INSTANCE_FLAG="$2"
seen=1
shift 2
else
INSTANCE_ARGS+=("$1")
shift
fi
done
}
instance_select() {
local here out
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -n "$INSTANCE_FLAG" ]; then
out="$(node "$here/mosaic-config.mjs" instance --instance "$INSTANCE_FLAG")" || exit $?
else
out="$(node "$here/mosaic-config.mjs" instance)" || exit $?
fi
eval "$out"
local s="$MOSAIC_UNIT_SUFFIX"
BUS_TEMPLATE="mosaic-bus$s@.service"
BUS_GLOB="mosaic-bus$s@*.service"
CONSOLE_UNIT="mosaic-webui$s.service"
DISCORD_TEMPLATE="mosaic-discord$s@.service"
WRAPPER_NAME="mosaic$s"
if [ -z "$s" ]; then
BUS_DROPIN_GLOBS=('mosaic-bus@*.service.d' 'mosaic-.service.d' 'service.d')
SESSION_ENV='%h/.config/mosaic-dev/secrets/claude-session.env'
INSTANCE_SED=(-e '/^@CONFIG_ENV@$/d' -e "s|@SESSION_ENV@|$SESSION_ENV|g")
return 0
fi
# systemd expands % and splits Environment= on spaces, and the sed
# expressions use |, so a named instance's paths keep to a plain set.
case "$MOSAIC_INSTANCE_CONFIG" in
*[!A-Za-z0-9/._+-]*)
echo "instance: config path contains a character a unit cannot carry: $MOSAIC_INSTANCE_CONFIG" >&2
exit 4 ;;
esac
export MOSAIC_CONFIG="${MOSAIC_CONFIG:-$MOSAIC_INSTANCE_CONFIG}"
INSTANCE_FLAGS=(--instance "$MOSAIC_INSTANCE")
INSTANCE_RESERVED=(7330 7332 7333)
BUS_DROPIN_GLOBS=("mosaic-bus$s@*.service.d" 'mosaic-bus-.service.d' 'mosaic-.service.d' 'service.d')
SESSION_ENV="$MOSAIC_INSTANCE_CONFIG_DIR/secrets/claude-session.env"
INSTANCE_SED=(
-e "s|^@CONFIG_ENV@\$|# Stack instance $MOSAIC_INSTANCE (row 190): the system config its processes read.\\nEnvironment=MOSAIC_CONFIG=$MOSAIC_INSTANCE_CONFIG|"
-e "s|@SESSION_ENV@|$SESSION_ENV|g"
-e "s|^\\(Description=.*\\)\$|\\1 [$MOSAIC_INSTANCE]|"
-e 's|The unit sets no MOSAIC_\*, CLAUDE\*, CODEX\* or PI_\* variable\.$|The unit sets MOSAIC_CONFIG and no other MOSAIC_*, CLAUDE*, CODEX* or PI_* variable.|'
)
}
# instance_port_holder DIR PORT: the installed Console unit in DIR, other than
# this instance's, whose ExecStart takes PORT as --port or --tester-port.
# A unit with no --port holds 7332, serve's default. Prints the unit and
# returns 0, or prints nothing and returns 1. Unit files are host
# configuration, so reading another instance's is not reading its state.
instance_port_holder() {
local dir="$1" want="$2" file unit line tok next port
want="$((10#$want))"
for file in "$dir"/mosaic-webui.service "$dir"/mosaic-webui-*.service; do
[ -f "$file" ] || continue
unit="${file##*/}"
[ "$unit" != "$CONSOLE_UNIT" ] || continue
line="$(sed -n 's/^ExecStart=//p' "$file" | tail -n 1)"
port=7332 next=""
local -a ports=() toks=()
read -r -a toks <<< "$line"
for tok in ${toks[@]+"${toks[@]}"}; do
case "$next" in
--port) [[ "$tok" =~ ^[0-9]{1,5}$ ]] && port="$((10#$tok))" ;;
--tester-port) [[ "$tok" =~ ^[0-9]{1,5}$ ]] && ports+=("$((10#$tok))") ;;
esac
next="$tok"
done
ports+=("$port")
for port in "${ports[@]}"; do
if [ "$port" = "$want" ]; then echo "$unit"; return 0; fi
done
done
return 1
}