discord: unit can run from a pinned copy; check --offline; status reads the effective tree (row 114, #1608)

Candidate manifest 68c60a79914692f3f6d4923f7bcff41de109ccca33bf6fba687fb88efa24bcac
from Rocko's round 1 packet (a94a943c9), on BASE 72c9ac36c. No
manifest path changed between BASE and the landing target. Filbert
approved round 1 in 28497 with no findings; the TOOLS.md paragraph
break is a doc-only follow-up (decision 137). Sage reran the full gate
at the landing target with the candidate applied and Docker live.
Nothing is installed or restarted; the live switch is Mos's step after
the SetSpark freeze lifts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-14 15:43:46 -05:00
co-authored by Claude Opus 5.5
parent a94a943c92
commit a1ddeabdfc
7 changed files with 207 additions and 13 deletions
+52 -8
View File
@@ -6,8 +6,8 @@
# instance per binding (mosaic-discord@<binding>). Nothing here reads the
# binding or the token. See packages/discord/README.md, "Service unit".
#
# render print the rendered unit on stdout
# install [--dir DIR] [--no-reload]
# render [--copy ID [--copies DIR]] print the rendered unit on stdout
# install [--copy ID [--copies DIR]] [--dir DIR] [--no-reload]
# write the unit (write to a temp file, then
# rename) and run `systemctl --user daemon-reload`
# uninstall [--dir DIR] [--no-reload]
@@ -19,20 +19,44 @@ set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEMPLATE="$REPO/packages/discord/systemd/[email protected]"
UNIT_NAME="[email protected]"
USAGE="usage: scripts/discord-service.sh render | install [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>"
USAGE="usage: scripts/discord-service.sh render [--copy ID [--copies DIR]] | install [--copy ID [--copies DIR]] [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>"
die() { echo "discord-service: $*" >&2; exit 1; }
usage() { echo "$USAGE" >&2; exit 4; }
COPY_ID=""
COPY_SET=0
COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies"
copy_root() {
[[ "$COPY_ID" =~ ^[0-9a-f]{40}$ ]] || die '--copy must be a full 40-character commit id'
case "$COPIES" in /*) ;; *) die 'copies directory must be absolute' ;; esac
case "$COPIES" in *[!A-Za-z0-9/._+-]*) die 'copies directory contains a character the unit cannot carry' ;; esac
local root="$COPIES/$COPY_ID"
[ -f "$root/packages/discord/src/cli.mjs" ] || die "copy lacks packages/discord/src/cli.mjs: $root"
[ -f "$root/node_modules/.bin/pi" ] || die "copy lacks node_modules/.bin/pi: $root"
echo "$root"
}
render() {
[ -f "$TEMPLATE" ] || die "template missing: $TEMPLATE"
local root=""
[ "$COPY_SET" = 0 ] || root="$(copy_root)" || return 1
local node_dir
node_dir="$(dirname "$(command -v node || true)")"
[ -n "$node_dir" ] && [ "$node_dir" != "." ] || die "node not found on PATH"
local path="/usr/local/bin:/usr/bin:/bin"
case ":$path:" in *":$node_dir:"*) ;; *) path="$node_dir:$path" ;; esac
case "$REPO" in *@*|*'|'*) die "repository path contains a character the template cannot carry: $REPO" ;; esac
sed -e "s|@REPO@|$REPO|g" -e "s|@PATH@|$path|g" "$TEMPLATE"
local unit
unit="$(sed -e "s|@REPO@|$REPO|g" -e "s|@PATH@|$path|g" "$TEMPLATE")" || return 1
if [ -n "$root" ]; then
local node
node="$(command -v node)"
case "$node" in *[!A-Za-z0-9/._+-]*) die 'node path contains a character the unit cannot carry' ;; esac
sed -e "s|^WorkingDirectory=.*$|WorkingDirectory=$root|" -e "s|^ExecStart=.*$|ExecStart=$node $root/packages/discord/src/cli.mjs run %i --supervised --repo $root|" <<< "$unit"
else
printf '%s\n' "$unit"
fi
}
# Parse --dir and --no-reload for install and uninstall.
@@ -41,8 +65,10 @@ RELOAD=1
parse_flags() {
while [ $# -gt 0 ]; do
case "$1" in
--dir) [ $# -ge 2 ] || usage; UNIT_DIR="$2"; shift 2 ;;
--no-reload) RELOAD=0; shift ;;
--copy) [ "$cmd" != uninstall ] && [ $# -ge 2 ] || usage; COPY_ID="$2"; COPY_SET=1; shift 2 ;;
--copies) [ "$cmd" != uninstall ] && [ $# -ge 2 ] || usage; COPIES="$2"; shift 2 ;;
--dir) [ "$cmd" != render ] && [ $# -ge 2 ] || usage; UNIT_DIR="$2"; shift 2 ;;
--no-reload) [ "$cmd" != render ] || usage; RELOAD=0; shift ;;
*) usage ;;
esac
done
@@ -50,10 +76,12 @@ parse_flags() {
install_unit() {
parse_flags "$@"
local rendered
rendered="$(render)" || exit 1
mkdir -p "$UNIT_DIR"
local target="$UNIT_DIR/$UNIT_NAME" tmp
tmp="$(mktemp "$UNIT_DIR/.$UNIT_NAME.XXXXXX")"
render > "$tmp"
printf '%s\n' "$rendered" > "$tmp"
chmod 0644 "$tmp"
if [ -f "$target" ] && cmp -s "$tmp" "$target"; then
rm -f "$tmp"
@@ -66,6 +94,11 @@ install_unit() {
systemctl --user daemon-reload || die "daemon-reload failed"
echo "daemon-reload done"
fi
if [ -n "$COPY_ID" ]; then
echo "copy: $COPY_ID"
else
echo "checkout: $REPO (use --copy ID to pin the connector)"
fi
cat <<MSG
next, for one binding:
systemctl --user enable --now mosaic-discord@<binding> start now and at login
@@ -106,6 +139,17 @@ status_unit() {
echo " enabled: $(systemctl --user is-enabled "$unit" 2>&1 || true)"
echo " active: $(systemctl --user is-active "$unit" 2>&1 || true)"
systemctl --user show "$unit" -p MainPID -p NRestarts -p ExecMainStartTimestamp -p Result 2>/dev/null | sed 's/^/ /'
local working id
working="$(systemctl --user show "$unit" -p WorkingDirectory --value 2>/dev/null)" || working=""
# Accept property-form output too, for systemctl fixtures and older wrappers.
working="${working#WorkingDirectory=}"
echo " tree: ${working:-absent (WorkingDirectory unavailable)}"
id="${working##*/}"
if [[ "$id" =~ ^[0-9a-f]{40}$ ]] && [ -f "$working/packages/discord/src/cli.mjs" ]; then
echo " copy: $id"
elif [ -n "$working" ]; then
echo " checkout: $working"
fi
local config="${XDG_CONFIG_HOME:-$HOME/.config}/mosaic-dev/config.json"
local data_root
data_root="$(node -e 'const c=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(String(c.dataRoot||""))' "$config" 2>/dev/null || true)"
@@ -130,7 +174,7 @@ status_unit() {
[ $# -ge 1 ] || usage
cmd="$1"; shift
case "$cmd" in
render) [ $# -eq 0 ] || usage; render ;;
render) parse_flags "$@"; render ;;
install) install_unit "$@" ;;
uninstall) uninstall_unit "$@" ;;
status) status_unit "$@" ;;
+56
View File
@@ -209,6 +209,62 @@ env -u USER scripts/discord-service.sh install --dir "$SANDBOX/units-nouser" --n
&& grep -qF "enable-linger $(id -un)" "$SANDBOX/install.nouser"
check "service install with USER unset finishes and names the account for lingering" $?
# --- pinned copy service, fixture paths only ---
COPY_ID="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
COPIES="$SANDBOX/copies"
COPY="$COPIES/$COPY_ID"
mkdir -p "$COPY/packages/discord/src" "$COPY/node_modules/.bin"
printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs"
printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
chmod 755 "$COPY/node_modules/.bin/pi"
scripts/discord-service.sh render --copy "$COPY_ID" --copies "$COPIES" >"$SANDBOX/unit.copy" 2>&1 \
&& grep -qxF "WorkingDirectory=$COPY" "$SANDBOX/unit.copy" \
&& grep -qxF "ExecStart=$(command -v node) $COPY/packages/discord/src/cli.mjs run %i --supervised --repo $COPY" "$SANDBOX/unit.copy" \
&& ! sed -n '/^WorkingDirectory=/p;/^ExecStart=/p' "$SANDBOX/unit.copy" | grep -qF "$REPO_DIR"
check "pinned render runs copy CLI with copy cwd/repo and no checkout executable" $?
scripts/discord-service.sh install --copy "$COPY_ID" --copies "$COPIES" --dir "$SANDBOX/pinned-units" --no-reload >"$SANDBOX/pinned-install" 2>&1 \
&& cmp -s "$SANDBOX/pinned-units/[email protected]" "$SANDBOX/unit.copy"
check "pinned install writes the rendered unit in a temporary unit directory" $?
for kind in short missing cli pi; do
id="$COPY_ID"
case "$kind" in short) id=short;; missing) id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;; cli) rm "$COPY/packages/discord/src/cli.mjs";; pi) printf '// fixture\n' > "$COPY/packages/discord/src/cli.mjs";rm "$COPY/node_modules/.bin/pi";; esac
scripts/discord-service.sh install --copy "$id" --copies "$COPIES" --dir "$SANDBOX/refused-$kind" --no-reload >"$SANDBOX/refused-$kind.log" 2>&1
rc=$?
[ "$rc" -eq 1 ] && [ ! -e "$SANDBOX/refused-$kind/[email protected]" ]
check "pinned install refuses $kind prerequisite and writes no unit" $?
done
printf '#!/bin/sh\nexit 0\n' > "$COPY/node_modules/.bin/pi"
mkdir -p "$SANDBOX/fake-bin"
cat >"$SANDBOX/fake-bin/systemctl" <<EOF_STATUS
#!/bin/sh
case "\$*" in *show*) echo 'WorkingDirectory=$COPY';; *)echo inactive;; esac
EOF_STATUS
chmod 755 "$SANDBOX/fake-bin/systemctl"
HOME="$SANDBOX/home" XDG_CONFIG_HOME="$SANDBOX/config-home" PATH="$SANDBOX/fake-bin:$PATH" scripts/discord-service.sh status fixture >"$SANDBOX/status.copy" 2>&1 \
&& grep -qF "copy: $COPY_ID" "$SANDBOX/status.copy" \
&& grep -qF "tree: $COPY" "$SANDBOX/status.copy"
check "status names the copy from fake systemctl effective cwd" $?
grep -qF "checkout: $REPO_DIR" "$SANDBOX/install.1" && grep -qF -- '--copy' "$SANDBOX/install.1"
check "checkout install explicitly recommends --copy" $?
# Offline wrapper entrypoint. Only a synthetic token/binding/config are used.
node - "$SANDBOX" <<'EOF_OFFLINE'
const fs=require('fs'),p=require('path'),root=process.argv[2];
const data=p.join(root,'offline-data');fs.mkdirSync(p.join(data,'discord'),{recursive:true});
const b=JSON.parse(fs.readFileSync('packages/discord/fixtures/binding.example.json','utf8'));
b.name='offline-fixture';b.tokenFile=p.join(root,'offline-token');b.context.files=['packages/discord/README.md'];delete b.tools;
fs.writeFileSync(b.tokenFile,'MTAw.abcdefghijklmnopqrstuvwxyz0123456789\n',{mode:0o600});
fs.writeFileSync(p.join(data,'discord',b.name+'.json'),JSON.stringify(b),{mode:0o600});
fs.writeFileSync(p.join(root,'offline-config'),JSON.stringify({dataRoot:data}));
fs.writeFileSync(p.join(root,'offline-trap.mjs'),`globalThis.fetch=()=>{throw Error('network forbidden in offline fixture')};globalThis.WebSocket=class{constructor(){throw Error('gateway forbidden in offline fixture')}};`);
EOF_OFFLINE
NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh check offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline.out" 2>&1 \
&& grep -qF 'offline check passed; no REST or gateway calls were made' "$SANDBOX/offline.out"
check "wrapper check --offline passes local fixture and makes no network call" $?
NODE_OPTIONS="--import=$SANDBOX/offline-trap.mjs" scripts/discord.sh run offline-fixture --config "$SANDBOX/offline-config" --offline >"$SANDBOX/offline-run.out" 2>&1
[ $? -eq 4 ]
check "wrapper run --offline refuses usage" $?
echo
echo "discord suite: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]