# 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 mosaic-bus@.service | mosaic-bus-NAME@.service # 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 mosaic-discord@.service | mosaic-discord-NAME@.service # WRAPPER_NAME mosaic | mosaic-NAME # SESSION_ENV %h/.config/mosaic-dev/secrets/claude-session.env # | /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=, " [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 }