@bongos/core 1.20.14 → 1.20.16
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 +26 -26
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/autobongos-windows-host.md +19 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/autobongos-service.ps1 +25 -2
- package/scripts/gds/provision-config.js +10 -14
- package/scripts/gds/provision-disconnect.js +4 -6
- package/scripts/gds/provision-net.js +5 -8
- package/scripts/gds/provision-repo.js +12 -33
- package/scripts/gds/provision-teardown.js +10 -14
- package/scripts/gds/provision-units.js +3 -4
- package/scripts/gds/provision.js +94 -201
- package/src/module-api.js +1 -1
- package/tests/autobongos_service_ps1.mjs +52 -0
- package/tests/provision.mjs +15 -134
- package/tests/provision_account_identity.mjs +2 -5
- package/tests/provision_disconnect.mjs +1 -10
- package/tests/provision_restart.mjs +3 -12
- package/tests/provision_settings_apply.mjs +7 -19
- package/tests/provision_teardown_address.mjs +1 -19
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.20.
|
|
6
|
-
"core_contract": "1.20.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.20.16",
|
|
6
|
+
"core_contract": "1.20.16",
|
|
7
|
+
"source_commit": "594ee4d4c853130397cd87e0095a3b50e03ad4b0",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-30T12:
|
|
9
|
+
"built_at": "2026-09-30T12:51:52.406Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 556,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 3210,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "9a6832279c80fa1b768101f4b2c4c13e626f023be26da7232d0596ef21accfa9",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2772,7 +2772,7 @@
|
|
|
2772
2772
|
{
|
|
2773
2773
|
"path": "docs/module-api-changelog.md",
|
|
2774
2774
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2775
|
+
"sha256": "2664b90799e3d947cdce43f2eb6c05699da1aafad223fef1b6eafd463c4478e3"
|
|
2776
2776
|
},
|
|
2777
2777
|
{
|
|
2778
2778
|
"path": "docs/modules-contract.md",
|
|
@@ -2882,7 +2882,7 @@
|
|
|
2882
2882
|
{
|
|
2883
2883
|
"path": "docs/recipes/autobongos-windows-host.md",
|
|
2884
2884
|
"mode": "0000644",
|
|
2885
|
-
"sha256": "
|
|
2885
|
+
"sha256": "e70faa969725827307388ccb0620bde203500333ed299d860ca7a001cc8577a1"
|
|
2886
2886
|
},
|
|
2887
2887
|
{
|
|
2888
2888
|
"path": "docs/recipes/bongos-cli-release.md",
|
|
@@ -8862,12 +8862,12 @@
|
|
|
8862
8862
|
{
|
|
8863
8863
|
"path": "package-lock.json",
|
|
8864
8864
|
"mode": "0000644",
|
|
8865
|
-
"sha256": "
|
|
8865
|
+
"sha256": "979f117fe308e4c9d4c2b8d9765fc7a8314c490d8cb134215efd61cded7cf30e"
|
|
8866
8866
|
},
|
|
8867
8867
|
{
|
|
8868
8868
|
"path": "package.json",
|
|
8869
8869
|
"mode": "0000644",
|
|
8870
|
-
"sha256": "
|
|
8870
|
+
"sha256": "f5ff021f7aedefb0c74cd4e019815aaada727a00b4a1dc7609f369aa41186b85"
|
|
8871
8871
|
},
|
|
8872
8872
|
{
|
|
8873
8873
|
"path": "public-docs/index.html",
|
|
@@ -8887,7 +8887,7 @@
|
|
|
8887
8887
|
{
|
|
8888
8888
|
"path": "release-notes.json",
|
|
8889
8889
|
"mode": "0000644",
|
|
8890
|
-
"sha256": "
|
|
8890
|
+
"sha256": "95ae54225e9925f4d47f66179e6a081db5668a217c0000b94c06c992a2cae479"
|
|
8891
8891
|
},
|
|
8892
8892
|
{
|
|
8893
8893
|
"path": "scripts/bongos-mcp.js",
|
|
@@ -9057,7 +9057,7 @@
|
|
|
9057
9057
|
{
|
|
9058
9058
|
"path": "scripts/gds/autobongos-service.ps1",
|
|
9059
9059
|
"mode": "0000644",
|
|
9060
|
-
"sha256": "
|
|
9060
|
+
"sha256": "b1e038bb70ace1652b85a4d29f5a68761c6b432fd50fbfdafb5e1e38d93627de"
|
|
9061
9061
|
},
|
|
9062
9062
|
{
|
|
9063
9063
|
"path": "scripts/gds/autobongos-verify.js",
|
|
@@ -9842,7 +9842,7 @@
|
|
|
9842
9842
|
{
|
|
9843
9843
|
"path": "scripts/gds/provision-config.js",
|
|
9844
9844
|
"mode": "0000644",
|
|
9845
|
-
"sha256": "
|
|
9845
|
+
"sha256": "5d8c07ebba17c797d7c8506e199f408a9490418e74089f5441a6141d76400be5"
|
|
9846
9846
|
},
|
|
9847
9847
|
{
|
|
9848
9848
|
"path": "scripts/gds/provision-core-upgrade.js",
|
|
@@ -9852,12 +9852,12 @@
|
|
|
9852
9852
|
{
|
|
9853
9853
|
"path": "scripts/gds/provision-disconnect.js",
|
|
9854
9854
|
"mode": "0000644",
|
|
9855
|
-
"sha256": "
|
|
9855
|
+
"sha256": "43aabd985e5ec2b2eff12b5d624158c3240e7777887705228945fedf691d95c3"
|
|
9856
9856
|
},
|
|
9857
9857
|
{
|
|
9858
9858
|
"path": "scripts/gds/provision-net.js",
|
|
9859
9859
|
"mode": "0000644",
|
|
9860
|
-
"sha256": "
|
|
9860
|
+
"sha256": "cc3bd9146e6f513239f564df67fe3dd0a4b50e7889a759c7528c9a4d34e64ce4"
|
|
9861
9861
|
},
|
|
9862
9862
|
{
|
|
9863
9863
|
"path": "scripts/gds/provision-pin-key.js",
|
|
@@ -9887,22 +9887,22 @@
|
|
|
9887
9887
|
{
|
|
9888
9888
|
"path": "scripts/gds/provision-repo.js",
|
|
9889
9889
|
"mode": "0000644",
|
|
9890
|
-
"sha256": "
|
|
9890
|
+
"sha256": "d1fd247608db02805168cd306d27378cf2ac964bf8ac156019eb969cc7b410d4"
|
|
9891
9891
|
},
|
|
9892
9892
|
{
|
|
9893
9893
|
"path": "scripts/gds/provision-teardown.js",
|
|
9894
9894
|
"mode": "0000644",
|
|
9895
|
-
"sha256": "
|
|
9895
|
+
"sha256": "eca5ebb6a796e336b3e5c542de171923488a3f4edd24e2e5da07d0a53f239c23"
|
|
9896
9896
|
},
|
|
9897
9897
|
{
|
|
9898
9898
|
"path": "scripts/gds/provision-units.js",
|
|
9899
9899
|
"mode": "0000644",
|
|
9900
|
-
"sha256": "
|
|
9900
|
+
"sha256": "2fd3cd47bce98f32ec151557908fdde48f91d7a3d7a144f7e3e7f77638eb5165"
|
|
9901
9901
|
},
|
|
9902
9902
|
{
|
|
9903
9903
|
"path": "scripts/gds/provision.js",
|
|
9904
9904
|
"mode": "0000644",
|
|
9905
|
-
"sha256": "
|
|
9905
|
+
"sha256": "8f03f6c0e043ba7d80e6c4543cc0d6abc8b2ccb8d4a7e40e814a6a14d6859440"
|
|
9906
9906
|
},
|
|
9907
9907
|
{
|
|
9908
9908
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -11002,7 +11002,7 @@
|
|
|
11002
11002
|
{
|
|
11003
11003
|
"path": "src/module-api.js",
|
|
11004
11004
|
"mode": "0000644",
|
|
11005
|
-
"sha256": "
|
|
11005
|
+
"sha256": "0d0657d70e9b17cb8f11328fd85f36df2bb7d67c1f44cac8745364e6558bc453"
|
|
11006
11006
|
},
|
|
11007
11007
|
{
|
|
11008
11008
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11457,7 +11457,7 @@
|
|
|
11457
11457
|
{
|
|
11458
11458
|
"path": "tests/autobongos_service_ps1.mjs",
|
|
11459
11459
|
"mode": "0000644",
|
|
11460
|
-
"sha256": "
|
|
11460
|
+
"sha256": "34ca7d75bc83d9c4a77706dd06ecc170011ef52b7ee77eea5c3b77ac514389d0"
|
|
11461
11461
|
},
|
|
11462
11462
|
{
|
|
11463
11463
|
"path": "tests/autobongos_verify.mjs",
|
|
@@ -14512,12 +14512,12 @@
|
|
|
14512
14512
|
{
|
|
14513
14513
|
"path": "tests/provision.mjs",
|
|
14514
14514
|
"mode": "0000644",
|
|
14515
|
-
"sha256": "
|
|
14515
|
+
"sha256": "ad4c6e5d4dd342e8c2b183c6decd7a38c976b207b30e211042bed0a42f3f5f17"
|
|
14516
14516
|
},
|
|
14517
14517
|
{
|
|
14518
14518
|
"path": "tests/provision_account_identity.mjs",
|
|
14519
14519
|
"mode": "0000644",
|
|
14520
|
-
"sha256": "
|
|
14520
|
+
"sha256": "63a9052194dbeca658aab24b839f549ba3193b34f564001ad889dd5671166fd9"
|
|
14521
14521
|
},
|
|
14522
14522
|
{
|
|
14523
14523
|
"path": "tests/provision_byo_host.mjs",
|
|
@@ -14532,7 +14532,7 @@
|
|
|
14532
14532
|
{
|
|
14533
14533
|
"path": "tests/provision_disconnect.mjs",
|
|
14534
14534
|
"mode": "0000644",
|
|
14535
|
-
"sha256": "
|
|
14535
|
+
"sha256": "8c117a05b0c4af8e4f6a465cad02d208210148aaa7e26adfe9098967911f1be5"
|
|
14536
14536
|
},
|
|
14537
14537
|
{
|
|
14538
14538
|
"path": "tests/provision_render.mjs",
|
|
@@ -14547,17 +14547,17 @@
|
|
|
14547
14547
|
{
|
|
14548
14548
|
"path": "tests/provision_restart.mjs",
|
|
14549
14549
|
"mode": "0000644",
|
|
14550
|
-
"sha256": "
|
|
14550
|
+
"sha256": "caa345e6708d896b42c597a9f83919b4795222e61955c728975d56a873a04fb8"
|
|
14551
14551
|
},
|
|
14552
14552
|
{
|
|
14553
14553
|
"path": "tests/provision_settings_apply.mjs",
|
|
14554
14554
|
"mode": "0000644",
|
|
14555
|
-
"sha256": "
|
|
14555
|
+
"sha256": "b8721014fea6a0cb60335077fff10fe55ab0c745511ab680154cbc291e357f9c"
|
|
14556
14556
|
},
|
|
14557
14557
|
{
|
|
14558
14558
|
"path": "tests/provision_teardown_address.mjs",
|
|
14559
14559
|
"mode": "0000644",
|
|
14560
|
-
"sha256": "
|
|
14560
|
+
"sha256": "e835c2b0b75640dc96954815c1f13ecd6d8fddcb08ae64fb0b6d98818959eb0a"
|
|
14561
14561
|
},
|
|
14562
14562
|
{
|
|
14563
14563
|
"path": "tests/provisioning_app_status.mjs",
|
|
@@ -2685,5 +2685,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2685
2685
|
landed since 1.20.12 with no explicit bump. run 36710762884. (task 1002620)
|
|
2686
2686
|
1.20.14 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2687
2687
|
landed since 1.20.13 with no explicit bump. run 36712510525. (task 1002620)
|
|
2688
|
+
1.20.15 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2689
|
+
landed since 1.20.14 with no explicit bump. run 36713710510. (task 1002620)
|
|
2690
|
+
1.20.16 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2691
|
+
landed since 1.20.15 with no explicit bump. run 36717488992. (task 1002620)
|
|
2688
2692
|
---------------------------------------------------------------------------
|
|
2689
2693
|
```
|
|
@@ -18,7 +18,7 @@ Task [1003905](https://cloudbongos.com/builders#/task/1003905).
|
|
|
18
18
|
|
|
19
19
|
## What you need on the box first
|
|
20
20
|
|
|
21
|
-
Open PowerShell as your
|
|
21
|
+
Open PowerShell as your own user and check all three:
|
|
22
22
|
|
|
23
23
|
```powershell
|
|
24
24
|
node --version ; git --version ; claude --version
|
|
@@ -28,7 +28,7 @@ All three must print a version. The installer refuses to proceed otherwise —
|
|
|
28
28
|
a runner that starts without `claude` on its PATH fails every worker and looks
|
|
29
29
|
like a mysterious outage in the log rather than a missing program.
|
|
30
30
|
|
|
31
|
-
You also need, as **your own user** (not
|
|
31
|
+
You also need, as **your own user** (not a separate administrator account):
|
|
32
32
|
|
|
33
33
|
- a `git` credential that can fetch the repo,
|
|
34
34
|
- a signed-in Claude Code session,
|
|
@@ -53,13 +53,29 @@ machine can write, the worker running on it can write too.
|
|
|
53
53
|
|
|
54
54
|
## 2. Install the scheduled task
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
**This step, and only this step, needs an Administrator shell.** Right-click
|
|
57
|
+
PowerShell, choose **Run as administrator**, and sign in to the prompt as the
|
|
58
|
+
**same user** you checked the prerequisites with — then, from the checkout:
|
|
57
59
|
|
|
58
60
|
```powershell
|
|
59
61
|
powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 install
|
|
60
62
|
powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 start
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
Why admin, when the runner deliberately is not: two different things happen
|
|
66
|
+
here. **Registering** the task needs admin, because it uses the S4U logon type
|
|
67
|
+
(run whether or not anyone is signed in), and Windows only lets an elevated
|
|
68
|
+
shell register one — from a normal shell `install` stops and says so, rather
|
|
69
|
+
than failing with a bare *Access is denied*. **Running** the task does not: it
|
|
70
|
+
runs as you, unelevated (`-RunLevel Limited`), which is what lets it use your
|
|
71
|
+
git credential and Claude session. Elevating the same account keeps your
|
|
72
|
+
username, so the task is still registered to run as you.
|
|
73
|
+
|
|
74
|
+
Do **not** install it as SYSTEM, or from a different administrator account:
|
|
75
|
+
the task would run as that account, which has none of your credentials. If
|
|
76
|
+
your own account is not an administrator on this PC, make it one for the
|
|
77
|
+
install rather than elevating as someone else.
|
|
78
|
+
|
|
63
79
|
That registers a task called `Autobongos` that starts **at boot** (not at sign-in
|
|
64
80
|
— nobody should have to log in), restarts if it crashes, and never times out.
|
|
65
81
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.16",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.16",
|
|
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.20.
|
|
3
|
+
"version": "1.20.16",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -8140,5 +8140,17 @@
|
|
|
8140
8140
|
"id": "1004397",
|
|
8141
8141
|
"text": "The unattended runner now records which version of the code it is running from its very first check-in, instead of leaving that blank for its first minute, which is exactly when someone is trying to find out."
|
|
8142
8142
|
}
|
|
8143
|
+
],
|
|
8144
|
+
"1.20.15": [
|
|
8145
|
+
{
|
|
8146
|
+
"id": "1004177",
|
|
8147
|
+
"text": "Installing the Windows runner now tells you up front that it needs an administrator window, and why, instead of failing with a bare \"access denied\"; the setup guide no longer says the opposite."
|
|
8148
|
+
}
|
|
8149
|
+
],
|
|
8150
|
+
"1.20.16": [
|
|
8151
|
+
{
|
|
8152
|
+
"id": "1004185",
|
|
8153
|
+
"text": "Cleaned out leftover code for a hosting option we retired, with proof that every live project's setup is unchanged — no project sees any difference."
|
|
8154
|
+
}
|
|
8143
8155
|
]
|
|
8144
8156
|
}
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
# UNPRIVILEGED ON PURPOSE. It runs as the logged-in user, not SYSTEM: the runner
|
|
26
26
|
# needs that user's git credential, npm config and Claude session, and SYSTEM has
|
|
27
27
|
# none of them. It is also the smaller blast radius for a process that spawns
|
|
28
|
-
# workers with blanket bypassPermissions.
|
|
28
|
+
# workers with blanket bypassPermissions. (INSTALLING it is the one step that
|
|
29
|
+
# needs an elevated shell of that same user -- see Assert-Prereqs, task 1004177.)
|
|
29
30
|
|
|
30
31
|
[CmdletBinding()]
|
|
31
32
|
param(
|
|
@@ -49,10 +50,32 @@ $LogDir = Join-Path $env:LOCALAPPDATA 'cloudbongos'
|
|
|
49
50
|
$LogFile = Join-Path $LogDir 'autobongos.log'
|
|
50
51
|
$Wrapper = Join-Path $RepoRoot 'scripts\gds\autobongos-service.cmd'
|
|
51
52
|
|
|
53
|
+
# Is this shell elevated (Run as administrator)? Its own function so the tests
|
|
54
|
+
# can stand it in: the check is Windows-only, and CI runs on Linux.
|
|
55
|
+
function Test-Elevated {
|
|
56
|
+
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
57
|
+
return ([Security.Principal.WindowsPrincipal]$id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# REGISTERING vs RUNNING (task 1004177). The task RUNS as this user, unelevated
|
|
61
|
+
# (-RunLevel Limited): that is what reaches the user's git credential and Claude
|
|
62
|
+
# session, and it is load-bearing. But REGISTERING a task with an S4U principal
|
|
63
|
+
# needs an elevated shell -- unelevated, Register-ScheduledTask fails with a bare
|
|
64
|
+
# "Access is denied" (0x80070005) that says nothing about why. Elevating the SAME
|
|
65
|
+
# account keeps $env:USERNAME, so the principal below is still this user. So
|
|
66
|
+
# install -- and only install -- checks, and says so in words, before trying.
|
|
67
|
+
$ElevationNeeded = @(
|
|
68
|
+
'install needs an elevated shell: registering a task that runs whether or not you are signed in (the S4U logon type) requires Administrator.',
|
|
69
|
+
'Open PowerShell with "Run as administrator" AS THIS SAME USER and run install again. The task still RUNS as you, unelevated (-RunLevel Limited) -- only the registration needs admin.',
|
|
70
|
+
'Do NOT install it as SYSTEM or another admin account: the runner needs your git credential and Claude session, and SYSTEM has neither.'
|
|
71
|
+
) -join ' '
|
|
72
|
+
|
|
52
73
|
function Assert-Prereqs {
|
|
74
|
+
param([switch]$ForInstall)
|
|
53
75
|
if (-not (Test-Path (Join-Path $RepoRoot 'scripts\gds\autobongos-run.js'))) {
|
|
54
76
|
throw "No autobongos-run.js under $RepoRoot -- pass -RepoRoot <path to the checkout>."
|
|
55
77
|
}
|
|
78
|
+
if ($ForInstall -and -not (Test-Elevated)) { throw $ElevationNeeded }
|
|
56
79
|
foreach ($exe in @('node', 'git', 'claude')) {
|
|
57
80
|
if (-not (Get-Command $exe -ErrorAction SilentlyContinue)) {
|
|
58
81
|
# Named, not guessed at: a runner that starts without `claude` on PATH will
|
|
@@ -191,7 +214,7 @@ if ($MyInvocation.InvocationName -eq '.') { return }
|
|
|
191
214
|
|
|
192
215
|
switch ($Action) {
|
|
193
216
|
'install' {
|
|
194
|
-
Assert-Prereqs
|
|
217
|
+
Assert-Prereqs -ForInstall
|
|
195
218
|
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
|
196
219
|
|
|
197
220
|
# NOT $action: PowerShell variable names are CASE-INSENSITIVE, so $action IS the
|
|
@@ -77,12 +77,11 @@ const CONFIG = {
|
|
|
77
77
|
// systemd (ADR 0306). Unused on a systemd box. No sudo is applied to writes here: the
|
|
78
78
|
// planes that select supervisord run unprivileged and already own this dir.
|
|
79
79
|
supervisorConfDir: process.env.PROVISION_SUPERVISOR_CONF_DIR || '/etc/supervisor/conf.d',
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
// The DigitalOcean token. No runner leg spends it since the dedicated droplet path went
|
|
81
|
+
// (task 1004185); the read stays because it is what makes this file a registered
|
|
82
|
+
// credential reader (modules/government/protected-surfaces.json + the tripwire in
|
|
83
|
+
// tests/government_protected_surfaces.mjs). Dropping it is a governance change of its own.
|
|
84
84
|
doToken: process.env.DO_API_TOKEN || null,
|
|
85
|
-
sshKeyIds: (process.env.BOX_SSH_KEY_IDS || '').split(',').map((s) => s.trim()).filter(Boolean),
|
|
86
85
|
// The Cloudflare DNS hook (the instance's infra/box-dns-cloudflare.sh).
|
|
87
86
|
// P4 (#1945) formalizes the UPSERT: point the instance's domain at the target
|
|
88
87
|
// public IP (below), in the domain's OWN Cloudflare zone, proxied=false (see
|
|
@@ -90,8 +89,7 @@ const CONFIG = {
|
|
|
90
89
|
// still reachable by IP; TLS then can't be issued until DNS lands).
|
|
91
90
|
dnsHook: process.env.BOX_DNS_HOOK || null,
|
|
92
91
|
// This box's PUBLIC IP — the A-record target for a CO-TENANT instance's domain.
|
|
93
|
-
// Set in the control-plane /etc/<inst>/box.env (never the web tier).
|
|
94
|
-
// instance instead points its A record at the freshly-created droplet's IP. Under
|
|
92
|
+
// Set in the control-plane /etc/<inst>/box.env (never the web tier). Under
|
|
95
93
|
// remote-exec (PROVISION_REMOTE_HOST) this is the CO-HOSTING box's IP — where the
|
|
96
94
|
// co-tenant actually runs (ADR 0130).
|
|
97
95
|
publicIp: process.env.PROVISION_PUBLIC_IP || process.env.BOX_PUBLIC_IP || null,
|
|
@@ -159,8 +157,8 @@ const CONFIG = {
|
|
|
159
157
|
// allows 97 non-superuser backends, so nine busy projects exhaust it whatever the
|
|
160
158
|
// RAM headroom. Idle projects hold zero backends, so this bounds bursts, not the
|
|
161
159
|
// steady state: 3 × 15 slots + the control plane's own 10 stays under the 97. The
|
|
162
|
-
// control plane keeps pg's default (it is not provisioned by this runner),
|
|
163
|
-
//
|
|
160
|
+
// control plane keeps pg's default (it is not provisioned by this runner), so it is
|
|
161
|
+
// not written there.
|
|
164
162
|
instancePoolMax: numEnv('PROVISION_INSTANCE_PGPOOL_MAX', 3),
|
|
165
163
|
};
|
|
166
164
|
|
|
@@ -188,8 +186,6 @@ function loadDeps() {
|
|
|
188
186
|
const { pool } = require(path.join(REPO_ROOT, 'src/bongos/pool'));
|
|
189
187
|
// eslint-disable-next-line global-require
|
|
190
188
|
const provisioning = require(path.join(REPO_ROOT, 'modules/provisioning/provisioning'));
|
|
191
|
-
// eslint-disable-next-line global-require
|
|
192
|
-
const { DoApi } = require(path.join(REPO_ROOT, 'scripts/gds/do-api'));
|
|
193
189
|
// Cross-site identity (ADR 0141 §4, task #1002109). Resolve the hub-side module by a
|
|
194
190
|
// VARIABLE key, gated on isModuleEnabled — the module-loader's own dynamic-dispatch
|
|
195
191
|
// pattern (the run-unit-tests.js precedent), NOT a static 'modules/platform-identity/'
|
|
@@ -206,14 +202,14 @@ function loadDeps() {
|
|
|
206
202
|
try { platformIdentity = require(path.join(piBase, 'platform-identity')); hubKeys = require(path.join(piBase, 'hub-keys')); projectCatalog = require(path.join(piBase, 'project-catalog')); }
|
|
207
203
|
catch { platformIdentity = null; hubKeys = null; projectCatalog = null; }
|
|
208
204
|
}
|
|
209
|
-
_deps = { pool, provisioning,
|
|
205
|
+
_deps = { pool, provisioning, platformIdentity, hubKeys, projectCatalog };
|
|
210
206
|
return _deps;
|
|
211
207
|
}
|
|
212
208
|
|
|
213
209
|
// ---------------------------------------------------------------------------
|
|
214
210
|
// PURE step-command generators (exported for tests). Each returns the exact shell
|
|
215
|
-
// command a runbook step runs. Kept pure so
|
|
216
|
-
//
|
|
211
|
+
// command a runbook step runs. Kept pure so the runbook and its tests build from ONE
|
|
212
|
+
// source of truth.
|
|
217
213
|
// ---------------------------------------------------------------------------
|
|
218
214
|
|
|
219
215
|
// A slug is validated DNS/db-safe upstream (provisioning.isValidSlug), so it is
|
|
@@ -62,12 +62,10 @@ async function disconnectInstance(inst, deps) {
|
|
|
62
62
|
// A teardown returns ok or throws; a throw ends the run here, before anything is erased.
|
|
63
63
|
await teardownInstance(inst, deps);
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
catch (e) { return { ok: false, error: `erase step failed (${cmd.split(' ').slice(0, 3).join(' ')}): ${(e && e.message) || e}` }; }
|
|
70
|
-
}
|
|
65
|
+
const boxExec = inst.hosting_shape === 'cloud-host' ? (deps.controlExec || exec) : exec;
|
|
66
|
+
for (const cmd of eraseCommands(inst, { privileged })) {
|
|
67
|
+
try { boxExec(cmd); }
|
|
68
|
+
catch (e) { return { ok: false, error: `erase step failed (${cmd.split(' ').slice(0, 3).join(' ')}): ${(e && e.message) || e}` }; }
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
const origin = catalogOriginForInstance(inst);
|
|
@@ -33,8 +33,7 @@ function federationHubOrigin() {
|
|
|
33
33
|
// platform-identity module is present, a signing key is loadable, AND a hub origin
|
|
34
34
|
// resolves), the instance is a CO-TENANT (the mothership-hosted shape whose web.env is
|
|
35
35
|
// written locally — a `standalone` is owner-hosted and keeps its own GitHub app, the
|
|
36
|
-
// self-hosted path
|
|
37
|
-
// ADR 0133 manifest flow leaves — see placeManifestCreds), and it has a public domain
|
|
36
|
+
// self-hosted path), and it has a public domain
|
|
38
37
|
// (the origin the hub binds the CLI mint audience to — the #1002108 account-takeover
|
|
39
38
|
// fix). Returns { federate, reason } so the caller can log WHY it skipped. PURE-ish
|
|
40
39
|
// (reads env + the injected module handles; no DB, no I/O).
|
|
@@ -110,7 +109,7 @@ function zoneForDomain(domain) {
|
|
|
110
109
|
// The env a DNS UPSERT invocation of infra/box-dns-cloudflare.sh needs for a
|
|
111
110
|
// PROVISIONED host: the hostname, the target public IP (the A-record content), the
|
|
112
111
|
// zone the record lives in, and whether Cloudflare proxies it.
|
|
113
|
-
// • proxied=false (default) is LOAD-BEARING for a CO-TENANT
|
|
112
|
+
// • proxied=false (default) is LOAD-BEARING for a CO-TENANT host: it uses
|
|
114
113
|
// Caddy on-demand ACME (Let's Encrypt), whose challenge must reach Caddy directly —
|
|
115
114
|
// Cloudflare proxy ON would intercept :80/:443 at the edge and the challenge would
|
|
116
115
|
// silently fail (ADR 0003 / docs/recipes/ops-gotchas.md). Do NOT proxy those.
|
|
@@ -297,11 +296,9 @@ function ingressPlaneGap(exec, { caddyfile = CONFIG.caddyfile } = {}) {
|
|
|
297
296
|
}
|
|
298
297
|
// The preflight around ingressPlaneGap: the narration its sibling preflights already carry,
|
|
299
298
|
// including the dry-run line without which the plan prints step 6's Caddy write as though it
|
|
300
|
-
// will work. Returns the refusal, or null to proceed.
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
function ingressPreflight({ exec, apply, log, shape, probe = ingressPlaneGap, before = 'writing any Caddy config' }) {
|
|
304
|
-
if (shape === 'dedicated') return null;
|
|
299
|
+
// will work. Returns the refusal, or null to proceed. Lives here rather than inline at the two
|
|
300
|
+
// call sites to keep provision.js under its 1,500-line ratchet.
|
|
301
|
+
function ingressPreflight({ exec, apply, log, probe = ingressPlaneGap, before = 'writing any Caddy config' }) {
|
|
305
302
|
if (!apply) { log(` [preflight] would probe the plane for a reverse proxy before ${before}`); return null; }
|
|
306
303
|
const missing = probe(exec);
|
|
307
304
|
if (missing) return `Ingress preflight failed: ${missing}`;
|
|
@@ -62,9 +62,8 @@ function migrateCmd(inst, { privileged = false } = {}) {
|
|
|
62
62
|
// The id/name/done_when come from instanceInitSpec so the starter version has ONE
|
|
63
63
|
// source of truth (the same spec the greenfield `bongos init` seed uses). SQL string
|
|
64
64
|
// literals are DOLLAR-QUOTED ($v$…$v$) so the whole -c argument is single-quoted —
|
|
65
|
-
// the one form that is safe UNCHANGED across
|
|
66
|
-
// over SSH (bash -s),
|
|
67
|
-
// with no nested-quote or $-expansion hazard.
|
|
65
|
+
// the one form that is safe UNCHANGED across both executors: inline (/bin/sh) and
|
|
66
|
+
// over SSH (bash -s), with no nested-quote or $-expansion hazard.
|
|
68
67
|
function seedFirstVersionCmd(inst, { privileged = false } = {}) {
|
|
69
68
|
const v = instanceInitSpec(inst).version;
|
|
70
69
|
const q = (s) => `$v$${s}$v$`; // dollar-quoted literal (no value contains a single quote or $)
|
|
@@ -299,10 +298,8 @@ function accountPlaneGap(exec, { privileged = false } = {}) {
|
|
|
299
298
|
|
|
300
299
|
// The preflight around accountPlaneGap: the narration its sibling preflights carry,
|
|
301
300
|
// including the dry-run line without which the plan prints step 1c's useradd as though it
|
|
302
|
-
// will work. Returns the refusal, or null to proceed.
|
|
303
|
-
|
|
304
|
-
function accountPreflight({ exec, apply, log, shape, privileged = false, probe = accountPlaneGap }) {
|
|
305
|
-
if (shape === 'dedicated') return null;
|
|
301
|
+
// will work. Returns the refusal, or null to proceed. Lives here beside the account it guards.
|
|
302
|
+
function accountPreflight({ exec, apply, log, privileged = false, probe = accountPlaneGap }) {
|
|
306
303
|
if (!apply) { log(' [preflight] would probe whether the runner can create this instance\'s own unix account'); return null; }
|
|
307
304
|
const missing = probe(exec, { privileged });
|
|
308
305
|
if (missing) return `Account preflight failed: ${missing}`;
|
|
@@ -327,38 +324,20 @@ function accountPreflight({ exec, apply, log, shape, privileged = false, probe =
|
|
|
327
324
|
//
|
|
328
325
|
// QUOTING, which is load-bearing in a way that is easy to undo by accident. Every literal
|
|
329
326
|
// is DOLLAR-QUOTED and every identifier goes through format(%I), so the rendered command
|
|
330
|
-
// contains no single quote and NO DOUBLE QUOTE
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
// seedFirstVersionCmd dollar-quotes for the same reason; a test asserts the absence.
|
|
335
|
-
// `peerOnly` is the DEDICATED shape's mode, and it exists because of where that
|
|
336
|
-
// command TRAVELS (task 1003369). A dedicated droplet's steps ride DigitalOcean
|
|
337
|
-
// cloud-init user-data, which DO RETAINS and serves back through its API, its console
|
|
338
|
-
// and the droplet's own metadata endpoint — so a password embedded there is a second,
|
|
339
|
-
// permanent copy of the credential in a place this task cannot lock down, which is
|
|
340
|
-
// strictly worse than what it protects against. A dedicated droplet is SINGLE-TENANT:
|
|
341
|
-
// there are no siblings on it, so the co-tenancy vector B3 describes does not exist,
|
|
342
|
-
// and the role can peer-auth to its own unix account. The role, its DB ownership and
|
|
343
|
-
// the PUBLIC revoke are still created — only the password is omitted, deliberately.
|
|
344
|
-
function dbRoleCmd(inst, { password = null, peerOnly = false, privileged = false } = {}) {
|
|
327
|
+
// contains no single quote and NO DOUBLE QUOTE, so it survives both executors unchanged —
|
|
328
|
+
// inline /bin/sh and `bash -s` over SSH. seedFirstVersionCmd dollar-quotes for the same
|
|
329
|
+
// reason; a test asserts the absence.
|
|
330
|
+
function dbRoleCmd(inst, { password = null, privileged = false } = {}) {
|
|
345
331
|
const role = instanceDbRole(inst), db = dbName(inst);
|
|
346
|
-
if (
|
|
347
|
-
if (password) throw new Error('dbRoleCmd: peerOnly takes no password — it exists to keep one out of cloud-init user-data');
|
|
348
|
-
} else if (!/^[A-Za-z0-9_-]+$/.test(String(password || ''))) {
|
|
332
|
+
if (!/^[A-Za-z0-9_-]+$/.test(String(password || ''))) {
|
|
349
333
|
throw new Error('dbRoleCmd: password must be a non-empty base64url string (generateDbPassword)');
|
|
350
334
|
}
|
|
351
|
-
// Two shapes of the same upsert. peerOnly omits the PASSWORD clause entirely rather
|
|
352
|
-
// than passing an empty one — `WITH LOGIN` leaves any existing password untouched.
|
|
353
|
-
const login = peerOnly
|
|
354
|
-
? { clause: 'WITH LOGIN', args: '' }
|
|
355
|
-
: { clause: 'WITH LOGIN PASSWORD %L', args: `, $p$${password}$p$` };
|
|
356
335
|
const sql =
|
|
357
336
|
`DO $do$ BEGIN ` +
|
|
358
337
|
`IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = $r$${role}$r$) THEN ` +
|
|
359
|
-
`EXECUTE format($f$ALTER ROLE %I $
|
|
338
|
+
`EXECUTE format($f$ALTER ROLE %I WITH LOGIN PASSWORD %L$f$, $r$${role}$r$, $p$${password}$p$); ` +
|
|
360
339
|
`ELSE ` +
|
|
361
|
-
`EXECUTE format($f$CREATE ROLE %I $
|
|
340
|
+
`EXECUTE format($f$CREATE ROLE %I WITH LOGIN PASSWORD %L$f$, $r$${role}$r$, $p$${password}$p$); ` +
|
|
362
341
|
`END IF; ` +
|
|
363
342
|
`EXECUTE format($f$ALTER DATABASE %I OWNER TO %I$f$, $d$${db}$d$, $r$${role}$r$); ` +
|
|
364
343
|
`EXECUTE format($f$REVOKE CONNECT ON DATABASE %I FROM PUBLIC$f$, $d$${db}$d$); ` +
|
|
@@ -1158,7 +1137,7 @@ async function refreshStandaloneCorePin(inst, deps) {
|
|
|
1158
1137
|
// bound to the instance's port + DB + brand, secrets from its own env file. SHAPE-AWARE:
|
|
1159
1138
|
// a STANDALONE instance runs the CORE PACKAGE's platform-server from its OWN repo dir
|
|
1160
1139
|
// (WorkingDirectory=standaloneRoot → resolveInstanceRoot() resolves to the instance via
|
|
1161
|
-
// cwd, deploy-instance.sh §4); co-tenant
|
|
1140
|
+
// cwd, deploy-instance.sh §4); co-tenant runs this control-plane checkout's src/.
|
|
1162
1141
|
|
|
1163
1142
|
module.exports = {
|
|
1164
1143
|
appGroupExpr,
|
|
@@ -24,24 +24,20 @@ async function teardownInstance(inst, deps) {
|
|
|
24
24
|
// Tear down on the box the instance actually RAN on: a standalone ran control-plane-
|
|
25
25
|
// local (task 2074), so disable/remove its unit HERE — not on the remote co-hosting
|
|
26
26
|
// box, where it was never installed (that would allowFail-noop and leave the real
|
|
27
|
-
// control-plane unit running). Co-tenant
|
|
27
|
+
// control-plane unit running). Co-tenant is unchanged.
|
|
28
28
|
const boxExec = inst.hosting_shape === 'cloud-host' ? controlExec : exec;
|
|
29
29
|
const sudoP = privileged ? 'sudo ' : ''; // systemctl / rm-under-/etc escalation (task 2066)
|
|
30
30
|
log(`teardown ${inst.slug} (status=${inst.status})`);
|
|
31
31
|
if (apply) await provisioning.setInstanceStatus(db, inst.id, 'tearing_down');
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (inst.db_name) log(` (left for the operator: dropdb ${dbName(inst)}, prune ${CONFIG.backupDir}/${dbName(inst)}-*.sql.gz)`);
|
|
42
|
-
} else if (apply && inst.droplet_id && deps.doApi) {
|
|
43
|
-
await deps.doApi.deleteDroplet(inst.droplet_id);
|
|
44
|
-
}
|
|
32
|
+
// Stop + remove the service AND the nightly backup units (task 2213; the dir + dumps stay — precious data, like the DB below). Whoever supervises the instance owns its removal (ADR 0306) — the systemd manager keeps the reset-failed step task 1003940 added, since a removed unit's FAILED state outlives its file and `systemctl --failed` listed torn-down ghosts for weeks.
|
|
33
|
+
(deps.processManager || resolveProcessManager()).removeService(inst, { exec: boxExec, sudoP });
|
|
34
|
+
// The site snippet standup step 6 wrote: while it stays, Caddy proxies the hostname to
|
|
35
|
+
// a port nothing listens on and every visitor gets a 502 (task 1002897). Not precious.
|
|
36
|
+
boxExec(`${sudoP}rm -f ${caddySnippetPath(inst)}`, { allowFail: true });
|
|
37
|
+
boxExec(`${sudoP}systemctl reload caddy`, { allowFail: true });
|
|
38
|
+
// Left for an operator (data is precious), naming ONLY what this row created —
|
|
39
|
+
// teardown now takes 'requested'/'error'. `port` + `domain` stay held: ADR 0181.
|
|
40
|
+
if (inst.db_name) log(` (left for the operator: dropdb ${dbName(inst)}, prune ${CONFIG.backupDir}/${dbName(inst)}-*.sql.gz)`);
|
|
45
41
|
parkAddress(inst, { controlExec, log, apply });
|
|
46
42
|
// The app's Render services are the OWNER's: forget them, never delete them (ADR 0327 §3, task 1004183).
|
|
47
43
|
await require('./provision-render.js').forgetRenderApp(inst, { db, log, apply });
|
|
@@ -87,7 +87,7 @@ function backupUnitName(inst) { return `${inst.slug}-db-backup`; }
|
|
|
87
87
|
function backupServicePath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.service`; }
|
|
88
88
|
function backupTimerPath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.timer`; }
|
|
89
89
|
// The consumer-layout-correct absolute path to db-backup-nightly.sh. Standalone runs
|
|
90
|
-
// it from the instance's own pinned core; co-tenant
|
|
90
|
+
// it from the instance's own pinned core; co-tenant from this checkout's
|
|
91
91
|
// scripts/gds/ — mirrors serviceUnit's execStart resolution so they can't diverge.
|
|
92
92
|
function backupScriptPath(inst) {
|
|
93
93
|
const rel = 'scripts/gds/db-backup-nightly.sh';
|
|
@@ -269,9 +269,8 @@ GITHUB_APP_PRIVATE_KEY=${String(appPem).replace(/\r?\n/g, '\\n')}
|
|
|
269
269
|
: '';
|
|
270
270
|
// The pool cap (task 1003928, ADR 0285 §3): a co-located instance shares the box's
|
|
271
271
|
// Postgres with every sibling and the control plane, so its pool may not take pg's
|
|
272
|
-
// default 10 of the server's 97. src/bongos/pool.js reads it bare.
|
|
273
|
-
|
|
274
|
-
const poolLine = inst.hosting_shape === 'dedicated' ? '' : `PGPOOL_MAX=${CONFIG.instancePoolMax}\n`;
|
|
272
|
+
// default 10 of the server's 97. src/bongos/pool.js reads it bare.
|
|
273
|
+
const poolLine = `PGPOOL_MAX=${CONFIG.instancePoolMax}\n`;
|
|
275
274
|
return `# /etc/${inst.slug}/web.env — per-instance secrets (chmod 600, OUTSIDE the repo).
|
|
276
275
|
${credComment}
|
|
277
276
|
${prefix}_GITHUB_CLIENT_ID=${clientId || ''}
|