selector: --org and --business in stack-upgrade's args-file scopes

read_args takes --org as --business in the unit flags before -- and in the
serve flags after it, with per-scope duplicate and conflict refusal, and
refuses a file whose serve flags name no selector or another business, as
case2-unit.sh would at the render after A2 stopped the units. The serve
flags are walked with case2-unit.sh's grammar so a value is never read as a
selector. The file is not rewritten; only --copy's value changes, as before.
A checkout without scripts/selector.sh refuses checkout-unreadable.
This commit is contained in:
jason.woltje committed 2026-11-08 22:05:44 -06:00
1 parent 226be2e935
commit 9e458878df
2 files changed
+141 -10

No files matched your search

+36 -7
View File
@@ -12,10 +12,13 @@
# Root runs it from a root-owned checkout of the reviewed commit, never from a copy, because the
# stack's user can write the copies. The unit values come from /etc/mosaic-stack/<instance>.args,
# which I2's install writes: one case2-unit.sh argument per line, the serve flags after `--`. Only
# --copy changes, and the file changes only after confirm passes. The stack's user runs its steps
# from its own clone, <home>/src/mosaic-stack, through sudo. Root reads none of that user's output
# but exit codes and the rehearsal's BACKUP_STORE line (sage-1653), which it copies into its step
# log after a match against a fixed pattern and decides nothing on.
# --copy changes, and the file changes only after confirm passes. --org is the same option as
# --business in each of the file's two scopes, case2-unit.sh's own flags before `--` and the serve
# flags after it: each scope takes each spelling once, a second spelling only with the same value,
# and both scopes must name the business, the same one (rename design r2). The stack's user runs
# its steps from its own clone, <home>/src/mosaic-stack, through sudo. Root reads none of that user's
# output but exit codes and the rehearsal's BACKUP_STORE line (sage-1653), which it copies into its
# step log after a match against a fixed pattern and decides nothing on.
#
# --no-rehearsal is for a stack whose log records no backup. The rehearsal runs either way: exit 3
# from it (no-backup) goes on only with the flag, and exit 0 with the flag refuses
@@ -119,6 +122,12 @@ logl "start rc=0 argv:$(printf ' %q' "$0" "${ARGV[@]}")"
# --- The args file and the stack's user ---
# The selector scopes: selector.sh defines functions only, from this root-owned checkout, as RENDER is.
. "$REPO/scripts/selector.sh" || refuse checkout-unreadable "cannot read $REPO/scripts/selector.sh"
args_business_ok() { [[ "$1" =~ ^[a-z0-9][a-z0-9-]*$ ]]; }
args_outer_refused() { refuse args-unreadable "$ETC/$INSTANCE.args before --: $2"; }
args_serve_refused() { refuse args-unreadable "$ETC/$INSTANCE.args after --: $2"; }
# I2's file: one case2-unit.sh argument per line, owned by root with no group or other write. Sets
# ARGS (the lines), S_USER, S_HOME, BUSINESS, NODE, ARGS_COPY, ARGS_PROFILE and ARGS_PROXY, and the paths derived
# from them.
@@ -126,8 +135,12 @@ logl "start rc=0 argv:$(printf ' %q' "$0" "${ARGV[@]}")"
# --profile wins, as there), and the current one refuses it. A2 would stop both units, and A4 and
# render-old would both fail on the same args, so a library file holding --send after -- refuses
# here, before anything stops. The file is never rewritten; the operator removes the line.
# Rename r2: case2-unit.sh also refuses a file whose serve flags name no selector or another
# business, so that refuses here too. The serve flags are read with case2-unit.sh's grammar, which
# says which of them take a value, so a value is never taken for a selector; a token that grammar
# lacks refuses, as the render would.
read_args() {
local f="$ETC/$INSTANCE.args" st i copies=0 v profile=library
local f="$ETC/$INSTANCE.args" st i sep copies=0 v profile=library served
[ -f "$f" ] && [ ! -L "$f" ] || refuse args-unreadable "no regular file $f"
st="$(stat -c '%u %a' "$f")" || refuse args-unreadable "cannot stat $f"
[ "${st% *}" = "$(id -u)" ] && (( (8#${st#* } & 8#022) == 0 )) || refuse args-unreadable "$f must be $(id -un)'s with no group or other write (it is uid ${st% *}, mode ${st#* })"
@@ -138,10 +151,13 @@ read_args() {
--) break ;;
--pm) ;;
--proxy) ARGS_PROXY=1 ;;
--user|--home|--business|--copy|--node|--profile|--claude)
--business|--org)
selector_take outer id args_outer_refused args_business_ok "${ARGS[i]}" "${ARGS[i + 1]:-}"
BUSINESS="${ARGS[i + 1]}"; i=$((i + 1)) ;;
--user|--home|--copy|--node|--profile|--claude)
v="${ARGS[i + 1]:-}"
case "${ARGS[i]}" in
--user) S_USER="$v" ;; --home) S_HOME="$v" ;; --business) BUSINESS="$v" ;; --node) NODE="$v" ;;
--user) S_USER="$v" ;; --home) S_HOME="$v" ;; --node) NODE="$v" ;;
--copy) ARGS_COPY="$v"; copies=$((copies + 1)) ;;
--profile) profile="$v" ;;
esac
@@ -149,6 +165,7 @@ read_args() {
*) refuse args-unreadable "$f has ${ARGS[i]} before --, which case2-unit.sh does not take" ;;
esac
done
sep=$i
[ "$copies" = 1 ] || refuse args-unreadable "$f needs exactly one --copy before --, not $copies"
ARGS_PROFILE="$profile"
if [ "$profile" = library ]; then
@@ -160,6 +177,18 @@ read_args() {
[[ "$S_USER" =~ ^[a-z_][a-z0-9_-]*$ ]] && [[ "$S_HOME" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$BUSINESS" =~ ^[a-z0-9][a-z0-9-]*$ ]] &&
[[ "$NODE" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$ARGS_COPY" =~ ^[0-9a-f]{40}$ ]] ||
refuse args-unreadable "$f lacks a plain --user, --home, --business, --node or --copy"
for ((i = sep + 1; i < ${#ARGS[@]}; i++)); do
case "${ARGS[i]}" in
--business|--org)
selector_take serve id args_serve_refused args_business_ok "${ARGS[i]}" "${ARGS[i + 1]:-}"; i=$((i + 1)) ;;
--port|--public-host|--auth-header|--allow-user|--proof-header|--proof-file) i=$((i + 1)) ;;
--tester-socket|--sso|--send) ;;
*) refuse args-unreadable "$f has ${ARGS[i]} after --, which case2-unit.sh does not take" ;;
esac
done
served="$(selector_value serve)"
[ -n "$served" ] || refuse args-unreadable "$f needs --business or --org after --, as case2-unit.sh does"
[ "$served" = "$BUSINESS" ] || refuse args-unreadable "$f names business $BUSINESS before -- and $served after it"
COPIES_DIR="$S_HOME/.local/share/mosaic-stack/copies"
CLONE="$S_HOME/src/mosaic-stack"
SR="$CLONE/scripts/stack-release.sh"