@bongos/core 1.19.938 → 1.19.939
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +21 -16
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/address-park-sweep.sh +16 -5
- package/scripts/gds/audit-authorship.sh +0 -5
- package/scripts/gds/db-backup-nightly.sh +2 -2
- package/scripts/gds/db-backup-offsite.sh +2 -2
- package/scripts/gds/stray-unit-sweep.sh +16 -3
- package/src/module-api.js +1 -1
- package/tests/sweep_app_account.mjs +112 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.939",
|
|
6
|
+
"core_contract": "1.19.939",
|
|
7
|
+
"source_commit": "5a36a537ba12fc2d9dc3ce29ec6e84fb102238e8",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-21T18:
|
|
9
|
+
"built_at": "2026-09-21T18:56:49.358Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 511,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2335,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2872,
|
|
20
|
+
"tree_sha256": "718dc449d9f22dd45347cce2f17dc24d6ca3d8e0cce9c9b1e7aad67a8683df47",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2677,7 +2677,7 @@
|
|
|
2677
2677
|
{
|
|
2678
2678
|
"path": "docs/module-api-changelog.md",
|
|
2679
2679
|
"mode": "0000644",
|
|
2680
|
-
"sha256": "
|
|
2680
|
+
"sha256": "4e9e33798c5671990f7d9d45e0695f43b8de1621cdaa316dd30d4e014f93df90"
|
|
2681
2681
|
},
|
|
2682
2682
|
{
|
|
2683
2683
|
"path": "docs/modules-contract.md",
|
|
@@ -8002,12 +8002,12 @@
|
|
|
8002
8002
|
{
|
|
8003
8003
|
"path": "package-lock.json",
|
|
8004
8004
|
"mode": "0000644",
|
|
8005
|
-
"sha256": "
|
|
8005
|
+
"sha256": "f9412625c08d05fd8718e0590544e5b4ac8d61ce85168c01c98973b0b0afbafd"
|
|
8006
8006
|
},
|
|
8007
8007
|
{
|
|
8008
8008
|
"path": "package.json",
|
|
8009
8009
|
"mode": "0000644",
|
|
8010
|
-
"sha256": "
|
|
8010
|
+
"sha256": "fcdd31f9e93e415ebe9384cfa896520ea80733044e833513f914c0f873180774"
|
|
8011
8011
|
},
|
|
8012
8012
|
{
|
|
8013
8013
|
"path": "public-docs/index.html",
|
|
@@ -8067,7 +8067,7 @@
|
|
|
8067
8067
|
{
|
|
8068
8068
|
"path": "scripts/gds/address-park-sweep.sh",
|
|
8069
8069
|
"mode": "0000644",
|
|
8070
|
-
"sha256": "
|
|
8070
|
+
"sha256": "b08a77da7a834dbe30fa9bd041ce05289f8abf48b3c44764ecbe2238cbc88e9c"
|
|
8071
8071
|
},
|
|
8072
8072
|
{
|
|
8073
8073
|
"path": "scripts/gds/adopt-detect.js",
|
|
@@ -8137,7 +8137,7 @@
|
|
|
8137
8137
|
{
|
|
8138
8138
|
"path": "scripts/gds/audit-authorship.sh",
|
|
8139
8139
|
"mode": "0000644",
|
|
8140
|
-
"sha256": "
|
|
8140
|
+
"sha256": "95607e7f1383148ecb21d448196872f752988d5c70169f5470a12eafc7e2643e"
|
|
8141
8141
|
},
|
|
8142
8142
|
{
|
|
8143
8143
|
"path": "scripts/gds/audit-deps.js",
|
|
@@ -8427,12 +8427,12 @@
|
|
|
8427
8427
|
{
|
|
8428
8428
|
"path": "scripts/gds/db-backup-nightly.sh",
|
|
8429
8429
|
"mode": "0000644",
|
|
8430
|
-
"sha256": "
|
|
8430
|
+
"sha256": "6123be86860d43d95d5d44966eb3e49caf9c5dc05a2a9ec02c67f53f527919dd"
|
|
8431
8431
|
},
|
|
8432
8432
|
{
|
|
8433
8433
|
"path": "scripts/gds/db-backup-offsite.sh",
|
|
8434
8434
|
"mode": "0000644",
|
|
8435
|
-
"sha256": "
|
|
8435
|
+
"sha256": "f530f46117bb052f8294e29c95970f6325e1e39f7ee27c96b92c2d672537b37d"
|
|
8436
8436
|
},
|
|
8437
8437
|
{
|
|
8438
8438
|
"path": "scripts/gds/demote.js",
|
|
@@ -9512,7 +9512,7 @@
|
|
|
9512
9512
|
{
|
|
9513
9513
|
"path": "scripts/gds/stray-unit-sweep.sh",
|
|
9514
9514
|
"mode": "0000644",
|
|
9515
|
-
"sha256": "
|
|
9515
|
+
"sha256": "f90a8ae14f3715f1454431d49be858b605bb3030a2ff3359f5ba2d41ebfd7a3f"
|
|
9516
9516
|
},
|
|
9517
9517
|
{
|
|
9518
9518
|
"path": "scripts/gds/surface.js",
|
|
@@ -10007,7 +10007,7 @@
|
|
|
10007
10007
|
{
|
|
10008
10008
|
"path": "src/module-api.js",
|
|
10009
10009
|
"mode": "0000644",
|
|
10010
|
-
"sha256": "
|
|
10010
|
+
"sha256": "7e4584b3285ed7261fa69727d9100dc10856000a7d8f75c8e9f1f47c26eb2f93"
|
|
10011
10011
|
},
|
|
10012
10012
|
{
|
|
10013
10013
|
"path": "src/module-loader/catalog.js",
|
|
@@ -14009,6 +14009,11 @@
|
|
|
14009
14009
|
"mode": "0000644",
|
|
14010
14010
|
"sha256": "24257ca4ad1a7bcefb735ee1b02609469035730e0723645f71e10eb71525bb72"
|
|
14011
14011
|
},
|
|
14012
|
+
{
|
|
14013
|
+
"path": "tests/sweep_app_account.mjs",
|
|
14014
|
+
"mode": "0000644",
|
|
14015
|
+
"sha256": "46fef77aa82d7f5295ed119b2d5ca77aeb29f5f267a3b82235260a94ee5bd41f"
|
|
14016
|
+
},
|
|
14012
14017
|
{
|
|
14013
14018
|
"path": "tests/task_classifier_vocab.mjs",
|
|
14014
14019
|
"mode": "0000644",
|
|
@@ -2350,5 +2350,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2350
2350
|
landed since 1.19.936 with no explicit bump. run 35566446478. (task 1002620)
|
|
2351
2351
|
1.19.938 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2352
2352
|
landed since 1.19.937 with no explicit bump. run 35638291152. (task 1002620)
|
|
2353
|
+
1.19.939 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2354
|
+
landed since 1.19.938 with no explicit bump. run 35641614084. (task 1002620)
|
|
2353
2355
|
---------------------------------------------------------------------------
|
|
2354
2356
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.939",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.939",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.939",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# Run on the control plane as a sudoer:
|
|
17
17
|
# bash scripts/gds/address-park-sweep.sh # survey only — read-only
|
|
18
18
|
# bash scripts/gds/address-park-sweep.sh --apply # remove the snippets, reload Caddy, park the hostnames
|
|
19
|
+
# Run from anywhere but an instance's own vendored core, set INSTANCE_DIR to that instance.
|
|
19
20
|
#
|
|
20
21
|
# Left in place on purpose, even with --apply: each project's database, dumps and checkout,
|
|
21
22
|
# and the row's domain + port (ADR 0181 — a revive re-uses that address).
|
|
@@ -23,15 +24,25 @@ set -uo pipefail
|
|
|
23
24
|
|
|
24
25
|
APPLY=0; [ "${1:-}" = "--apply" ] && APPLY=1
|
|
25
26
|
DB="${PGDATABASE:-cloudbongos}"
|
|
26
|
-
|
|
27
|
+
# The instance checkout and the account that owns it (task 1004135) — never a named person.
|
|
28
|
+
# Run from an instance's vendored core, the checkout is three levels up (the rule
|
|
29
|
+
# selfInstanceRoot() in provision-repo.js uses); from anywhere else it must be named. The
|
|
30
|
+
# account is that checkout's owner unless PROVISION_APP_USER pins it; root holds no role here.
|
|
31
|
+
CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
32
|
+
case "$CORE_DIR" in */node_modules/@bongos/core) INSTANCE_DIR="${INSTANCE_DIR:-${CORE_DIR%/node_modules/@bongos/core}}" ;; esac
|
|
33
|
+
[ -n "${INSTANCE_DIR:-}" ] || { echo "set INSTANCE_DIR to the control plane's instance checkout — this sweep is not running from one"; exit 1; }
|
|
34
|
+
APP_USER="${PROVISION_APP_USER:-$(stat -c %U "$INSTANCE_DIR" 2>/dev/null)}"
|
|
35
|
+
if [ "$APP_USER" = root ] || ! [[ "$APP_USER" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then
|
|
36
|
+
echo "cannot use '$APP_USER' as the app account — set PROVISION_APP_USER to the unprivileged account that owns $INSTANCE_DIR"; exit 1
|
|
37
|
+
fi
|
|
27
38
|
SITES="${PROVISION_CADDY_SNIPPET_DIR:-/etc/caddy/sites}"
|
|
28
39
|
BOX_ENV="${BOX_ENV:-/etc/cloudbongos/box.env}"
|
|
29
40
|
|
|
30
|
-
rows=$(sudo -n -u
|
|
31
|
-
|| { echo "cannot read provisioning_instances from $DB as
|
|
41
|
+
rows=$(sudo -n -u "$APP_USER" psql -d "$DB" -tAF '|' -c "select slug, coalesce(domain, '') from provisioning_instances where status='torn_down' order by slug" 2>/dev/null) \
|
|
42
|
+
|| { echo "cannot read provisioning_instances from $DB as $APP_USER"; exit 1; }
|
|
32
43
|
|
|
33
44
|
# slug|domain|zone|reserved|blackhole per row, plus an apex line — from the installed core.
|
|
34
|
-
plan=$(sudo -n -u
|
|
45
|
+
plan=$(sudo -n -u "$APP_USER" env ROWS="$rows" bash -c 'cd "$1" && exec node -' _ "$INSTANCE_DIR" <<'JS'
|
|
35
46
|
const core = './node_modules/@bongos/core';
|
|
36
47
|
const { isReservedHostname } = require(`${core}/modules/provisioning/provisioning.js`);
|
|
37
48
|
const { dnsReleaseEnv } = require(`${core}/scripts/gds/provision-net.js`);
|
|
@@ -66,7 +77,7 @@ while IFS='|' read -r kind slug domain zone reserved blackhole; do
|
|
|
66
77
|
printf ' %-22s %s resolves to %s — park at %s (zone %s)\n' "$slug" "$domain" "$now" "$blackhole" "$zone"
|
|
67
78
|
left+=("$slug:dns")
|
|
68
79
|
[ "$APPLY" = 1 ] || continue
|
|
69
|
-
sudo -n -u
|
|
80
|
+
sudo -n -u "$APP_USER" bash -c 'set -a; . "$1"; set +a; BOX_HOSTNAME="$2" BOX_IP="$3" CLOUDFLARE_ZONE="$4" BOX_PROXIED=0 exec "$BOX_DNS_HOOK"' \
|
|
70
81
|
_ "$BOX_ENV" "$domain" "$blackhole" "$zone" </dev/null || echo " ⚠ could not park $domain — re-run to retry"
|
|
71
82
|
done <<< "$plan"
|
|
72
83
|
|
|
@@ -293,11 +293,6 @@ grep -vE "$OPS_ALLOWLIST_PATTERN" "$TMP_HITS" \
|
|
|
293
293
|
# ----------------------------------------------------------------------------
|
|
294
294
|
UNIX_USER_PATTERN='((^|[^A-Za-z0-9_.-])(-o|-g|-u)[ =]lars\b)|(\b(User|Group)=lars\b)|(\bchown( -R)? lars\b)|([A-Za-z_]*[Uu][Ss][Ee][Rr][A-Za-z_]*\s*[:=]\s*["'"'"']lars["'"'"'])'
|
|
295
295
|
UNIX_ALLOWLIST=(
|
|
296
|
-
# One-off backfill sweeps (tasks 1003939, 1003947), run by hand on the plane
|
|
297
|
-
# whose rows predate the fix; they name that plane's account on purpose.
|
|
298
|
-
'scripts/gds/address-park-sweep.sh'
|
|
299
|
-
'scripts/gds/stray-unit-sweep.sh'
|
|
300
|
-
|
|
301
296
|
# This script's own pattern.
|
|
302
297
|
'scripts/gds/audit-authorship.sh'
|
|
303
298
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Nightly pg_dump of the GDS Postgres DB.
|
|
3
3
|
#
|
|
4
|
-
# Runs as
|
|
4
|
+
# Runs as the unit's User= (the plane's app account) via the gds-db-backup.timer systemd unit (see
|
|
5
5
|
# scripts/gds/gds-db-backup.timer + .service in this repo). Output lands
|
|
6
6
|
# in /var/backups/gds/ as <db>-YYYY-MM-DDTHHMMSSZ.sql.gz (UTC timestamp), and
|
|
7
7
|
# files older than 30 days are pruned.
|
|
@@ -33,7 +33,7 @@ set -euo pipefail
|
|
|
33
33
|
|
|
34
34
|
# ---- configuration ----------------------------------------------------------
|
|
35
35
|
|
|
36
|
-
# Destination directory for dumps. Owned by
|
|
36
|
+
# Destination directory for dumps. Owned by that account and its primary group, mode 0750.
|
|
37
37
|
BACKUP_DIR="${GDS_BACKUP_DIR:-/var/backups/gds}"
|
|
38
38
|
|
|
39
39
|
# Database to dump: explicit GDS_BACKUP_DB wins, else the instance's own branding
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
#
|
|
10
10
|
# Vendor decision: DigitalOcean Spaces over Backblaze B2 — see ADR 0025.
|
|
11
11
|
#
|
|
12
|
-
# Runs as
|
|
12
|
+
# Runs as the unit's User= (the plane's app account) via the gds-db-offsite.timer systemd unit (see
|
|
13
13
|
# scripts/gds/gds-db-offsite.service + .timer). Weekly cadence.
|
|
14
14
|
#
|
|
15
15
|
# Authentication: S3-style credentials for the Space live OUTSIDE the repo
|
|
@@ -121,7 +121,7 @@ for v in SPACES_KEY SPACES_SECRET SPACES_BUCKET SPACES_REGION SPACES_ENDPOINT; d
|
|
|
121
121
|
done
|
|
122
122
|
if [ "${#missing[@]}" -gt 0 ]; then
|
|
123
123
|
echo "ERROR: missing Spaces credentials: ${missing[*]}" >&2
|
|
124
|
-
echo " Set them in $ENV_FILE (chmod 600, owned by
|
|
124
|
+
echo " Set them in $ENV_FILE (chmod 600, owned by $(id -un)). See ADR 0022 + 0025." >&2
|
|
125
125
|
exit 2
|
|
126
126
|
fi
|
|
127
127
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# Run on the control plane as a sudoer:
|
|
14
14
|
# bash scripts/gds/stray-unit-sweep.sh # survey only — read-only
|
|
15
15
|
# bash scripts/gds/stray-unit-sweep.sh --apply # disable --now, remove the unit files, reload, reset-failed
|
|
16
|
+
# Run from anywhere but an instance's own vendored core, set INSTANCE_DIR to that instance.
|
|
16
17
|
#
|
|
17
18
|
# Left in place on purpose, even with --apply: the project's database, its dumps under
|
|
18
19
|
# /var/backups/gds, any checkout, and its Caddy block — data is precious, and the address is
|
|
@@ -23,10 +24,22 @@ APPLY=0; [ "${1:-}" = "--apply" ] && APPLY=1
|
|
|
23
24
|
DB="${PGDATABASE:-cloudbongos}"
|
|
24
25
|
ETC=/etc/systemd/system
|
|
25
26
|
|
|
26
|
-
# The
|
|
27
|
+
# The app account (task 1004135) — never a named person: PROVISION_APP_USER, else the owner
|
|
28
|
+
# of the instance checkout, found the way address-park-sweep.sh finds it (three levels up
|
|
29
|
+
# from an instance's vendored core; from anywhere else INSTANCE_DIR names it). A clone in an
|
|
30
|
+
# operator's own home must not decide it. root is refused: it holds no Postgres role here.
|
|
31
|
+
CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
32
|
+
case "$CORE_DIR" in */node_modules/@bongos/core) INSTANCE_DIR="${INSTANCE_DIR:-${CORE_DIR%/node_modules/@bongos/core}}" ;; esac
|
|
33
|
+
[ -n "${PROVISION_APP_USER:-}${INSTANCE_DIR:-}" ] || { echo "set INSTANCE_DIR to the control plane's instance checkout (or PROVISION_APP_USER) — this sweep is not running from one"; exit 1; }
|
|
34
|
+
APP_USER="${PROVISION_APP_USER:-$(stat -c %U "$INSTANCE_DIR" 2>/dev/null)}"
|
|
35
|
+
if [ "$APP_USER" = root ] || ! [[ "$APP_USER" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then
|
|
36
|
+
echo "cannot use '$APP_USER' as the app account — set PROVISION_APP_USER to the unprivileged account that owns ${INSTANCE_DIR:-the instance checkout}"; exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# The torn-down slugs, from the ledger, read as the app account (peer auth — the runner's
|
|
27
40
|
# own identity; nothing here needs a superuser).
|
|
28
|
-
slugs=$(sudo -n -u
|
|
29
|
-
|| { echo "cannot read provisioning_instances from $DB as
|
|
41
|
+
slugs=$(sudo -n -u "$APP_USER" psql -d "$DB" -tAc "select slug from provisioning_instances where status='torn_down' order by slug" 2>/dev/null) \
|
|
42
|
+
|| { echo "cannot read provisioning_instances from $DB as $APP_USER"; exit 1; }
|
|
30
43
|
|
|
31
44
|
found=(); cleared=()
|
|
32
45
|
echo "== torn-down rows that still have systemd units =="
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.939'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// tests/sweep_app_account.mjs — the two backfill sweeps act as the plane's app account,
|
|
2
|
+
// never a named person (task 1004135). Both used to `sudo -u <founder>`, so a second
|
|
3
|
+
// operator who ran them got `sudo: unknown user`.
|
|
4
|
+
//
|
|
5
|
+
// Behavioural: each sweep runs under bash with stub `sudo` / `stat` / `systemctl` first on
|
|
6
|
+
// PATH. The sudo stub records its argv and answers nothing (an empty ledger), so the
|
|
7
|
+
// assertion is on the account the script actually asked for. Nothing touches a box.
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { test } from 'node:test';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const REPO = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
const skip = spawnSync('bash', ['-c', 'true']).error ? 'bash not on PATH' : false;
|
|
18
|
+
const slash = (p) => p.replace(/\\/g, '/');
|
|
19
|
+
|
|
20
|
+
function stubs() {
|
|
21
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sweep-stubs-'));
|
|
22
|
+
const sudoLog = slash(path.join(dir, 'sudo.log'));
|
|
23
|
+
const statLog = slash(path.join(dir, 'stat.log'));
|
|
24
|
+
const bin = (name, body) => fs.writeFileSync(path.join(dir, name), `#!/usr/bin/env bash\n${body}\n`, { mode: 0o755 });
|
|
25
|
+
bin('sudo', `echo "$*" >> "${sudoLog}"; exit 0`);
|
|
26
|
+
bin('stat', `echo "$*" >> "${statLog}"; echo checkout-owner`);
|
|
27
|
+
bin('systemctl', 'exit 0');
|
|
28
|
+
const lines = (f) => (fs.existsSync(f) ? fs.readFileSync(f, 'utf8').split('\n').filter(Boolean) : []);
|
|
29
|
+
return { dir, sudo: () => lines(sudoLog), stat: () => lines(statLog) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function run(scriptPath, s, env = {}) {
|
|
33
|
+
const base = { ...process.env };
|
|
34
|
+
delete base.PROVISION_APP_USER;
|
|
35
|
+
delete base.INSTANCE_DIR;
|
|
36
|
+
return spawnSync('bash', [slash(scriptPath)], {
|
|
37
|
+
cwd: REPO, encoding: 'utf8',
|
|
38
|
+
env: { ...base, ...env, PATH: `${s.dir}${path.delimiter}${process.env.PATH}` },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const STRAY = path.join(REPO, 'scripts', 'gds', 'stray-unit-sweep.sh');
|
|
43
|
+
const PARK = path.join(REPO, 'scripts', 'gds', 'address-park-sweep.sh');
|
|
44
|
+
|
|
45
|
+
test('stray-unit-sweep: PROVISION_APP_USER is the account the ledger is read as', { skip }, () => {
|
|
46
|
+
const s = stubs();
|
|
47
|
+
run(STRAY, s, { PROVISION_APP_USER: 'appsvc' });
|
|
48
|
+
assert.match(s.sudo()[0] || '', /^-n -u appsvc psql /);
|
|
49
|
+
assert.equal(s.stat().length, 0, 'a pin is not second-guessed');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('stray-unit-sweep: unpinned outside a vendored core, it refuses rather than trust whoever owns this clone', { skip }, () => {
|
|
53
|
+
const s = stubs();
|
|
54
|
+
const r = run(STRAY, s);
|
|
55
|
+
assert.equal(r.status, 1);
|
|
56
|
+
assert.match(r.stdout, /set INSTANCE_DIR/);
|
|
57
|
+
assert.deepEqual(s.stat(), [], 'the clone\'s owner was never asked');
|
|
58
|
+
assert.deepEqual(s.sudo(), []);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('stray-unit-sweep: INSTANCE_DIR names the checkout whose owner acts', { skip }, () => {
|
|
62
|
+
const s = stubs();
|
|
63
|
+
const inst = fs.mkdtempSync(path.join(os.tmpdir(), 'sweep-inst-'));
|
|
64
|
+
run(STRAY, s, { INSTANCE_DIR: slash(inst) });
|
|
65
|
+
assert.match(s.stat()[0] || '', new RegExp(`^-c %U .*${path.basename(inst)}$`));
|
|
66
|
+
assert.match(s.sudo()[0] || '', /^-n -u checkout-owner psql /);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('stray-unit-sweep: root is refused before anything runs', { skip }, () => {
|
|
70
|
+
const s = stubs();
|
|
71
|
+
const r = run(STRAY, s, { PROVISION_APP_USER: 'root' });
|
|
72
|
+
assert.equal(r.status, 1);
|
|
73
|
+
assert.match(r.stdout, /cannot use 'root' as the app account — set PROVISION_APP_USER/);
|
|
74
|
+
assert.deepEqual(s.sudo(), [], 'sudo never asked');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('address-park-sweep: outside a vendored core, INSTANCE_DIR must be named — no founder-home default', { skip }, () => {
|
|
78
|
+
const s = stubs();
|
|
79
|
+
const r = run(PARK, s);
|
|
80
|
+
assert.equal(r.status, 1);
|
|
81
|
+
assert.match(r.stdout, /set INSTANCE_DIR/);
|
|
82
|
+
assert.deepEqual(s.sudo(), []);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('address-park-sweep: root is refused before anything runs', { skip }, () => {
|
|
86
|
+
const s = stubs();
|
|
87
|
+
const r = run(PARK, s, { INSTANCE_DIR: slash(os.tmpdir()), PROVISION_APP_USER: 'root' });
|
|
88
|
+
assert.equal(r.status, 1);
|
|
89
|
+
assert.match(r.stdout, /cannot use 'root' as the app account — set PROVISION_APP_USER/);
|
|
90
|
+
assert.deepEqual(s.sudo(), [], 'sudo never asked');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('address-park-sweep: every sudo hop uses the pinned account', { skip }, () => {
|
|
94
|
+
const s = stubs();
|
|
95
|
+
run(PARK, s, { INSTANCE_DIR: slash(os.tmpdir()), PROVISION_APP_USER: 'appsvc' });
|
|
96
|
+
const hops = s.sudo().filter((l) => / -u /.test(l));
|
|
97
|
+
assert.ok(hops.length >= 2, `the ledger read and the core load both ran — got ${JSON.stringify(s.sudo())}`);
|
|
98
|
+
for (const h of hops) assert.match(h, /^-n -u appsvc /);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
for (const [name, src] of [['address-park-sweep', PARK], ['stray-unit-sweep', STRAY]]) {
|
|
102
|
+
test(`${name}: run from an instance's vendored core, the instance is three levels up and its owner acts`, { skip }, () => {
|
|
103
|
+
const s = stubs();
|
|
104
|
+
const inst = fs.mkdtempSync(path.join(os.tmpdir(), 'sweep-inst-'));
|
|
105
|
+
const gds = path.join(inst, 'node_modules', '@bongos', 'core', 'scripts', 'gds');
|
|
106
|
+
fs.mkdirSync(gds, { recursive: true });
|
|
107
|
+
fs.copyFileSync(src, path.join(gds, `${name}.sh`));
|
|
108
|
+
run(path.join(gds, `${name}.sh`), s);
|
|
109
|
+
assert.match(s.stat()[0] || '', new RegExp(`^-c %U .*${path.basename(inst)}$`), 'the instance root, not the package');
|
|
110
|
+
assert.match(s.sudo()[0] || '', /^-n -u checkout-owner psql /);
|
|
111
|
+
});
|
|
112
|
+
}
|