@bongos/core 1.19.1049 → 1.19.1050
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 +17 -52
- package/docs/file-map.md +1 -9
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/gds/publish-manifest.js +0 -1
- package/scripts/setup-builder.sh +3 -3
- package/src/bongos/module-scope-map.js +2 -15
- package/src/module-api.js +1 -1
- package/.devcontainer/Dockerfile +0 -69
- package/.devcontainer/README.md +0 -73
- package/.devcontainer/devcontainer-lock.json +0 -19
- package/.devcontainer/devcontainer.json +0 -93
- package/.devcontainer/healthcheck.sh +0 -84
- package/.devcontainer/init-firewall.sh +0 -234
- package/.devcontainer/post-create.sh +0 -149
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.1050",
|
|
6
|
+
"core_contract": "1.19.1050",
|
|
7
|
+
"source_commit": "58c76064d5864e4579213b609e0113be3685e0ac",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-27T13:
|
|
9
|
+
"built_at": "2026-09-27T13:58:16.724Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 542,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2507,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 3076,
|
|
20
|
+
"tree_sha256": "d89a3236ad7eedff9ab8575517a3ff1e564ae1bbac3b72a37cace58ee3ac8f0a",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -259,41 +259,6 @@
|
|
|
259
259
|
"mode": "0000644",
|
|
260
260
|
"sha256": "6548fe1de89c6909def02b76f022cae739c5b129d179a63538b9a1ab252e5030"
|
|
261
261
|
},
|
|
262
|
-
{
|
|
263
|
-
"path": ".devcontainer/Dockerfile",
|
|
264
|
-
"mode": "0000644",
|
|
265
|
-
"sha256": "8e2a619a3d72bfcb91440fd5773622fefb773b5effea1e6c7f7d6c2e01f14817"
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
"path": ".devcontainer/README.md",
|
|
269
|
-
"mode": "0000644",
|
|
270
|
-
"sha256": "33038ae214c708ce3e5c2539c5e672e82da76b882df93d6d906a041fc22253bb"
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
"path": ".devcontainer/devcontainer-lock.json",
|
|
274
|
-
"mode": "0000644",
|
|
275
|
-
"sha256": "ef6b1d2b6d7fdaea6d1a6b0d91378fa18e2f9f49bdf8ffbf541f8659d68574dc"
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
"path": ".devcontainer/devcontainer.json",
|
|
279
|
-
"mode": "0000644",
|
|
280
|
-
"sha256": "4ea21a7f615b5f9cb3a657f6c053d3680aed99c9c33cc70cbac398068fd6c309"
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
"path": ".devcontainer/healthcheck.sh",
|
|
284
|
-
"mode": "0000644",
|
|
285
|
-
"sha256": "5b04ae8e3436e659339eb0f820623844d22ccdbd07d6e9775aa6df17f6d1dd30"
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
"path": ".devcontainer/init-firewall.sh",
|
|
289
|
-
"mode": "0000644",
|
|
290
|
-
"sha256": "bcff88c5ca2835456f43992b6d9506e62ffb1475e20d7b15819f4572f5abb5ee"
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
"path": ".devcontainer/post-create.sh",
|
|
294
|
-
"mode": "0000644",
|
|
295
|
-
"sha256": "ba1f564c1d91473e545a1e33051a4c1952db037e5e607b1d219bde97280174b7"
|
|
296
|
-
},
|
|
297
262
|
{
|
|
298
263
|
"path": ".dockerignore",
|
|
299
264
|
"mode": "0000644",
|
|
@@ -2762,7 +2727,7 @@
|
|
|
2762
2727
|
{
|
|
2763
2728
|
"path": "docs/file-map.md",
|
|
2764
2729
|
"mode": "0000644",
|
|
2765
|
-
"sha256": "
|
|
2730
|
+
"sha256": "64d65a849596466c7fbafba381668129c2e305e88af805dc5eed06a5b06600bb"
|
|
2766
2731
|
},
|
|
2767
2732
|
{
|
|
2768
2733
|
"path": "docs/handoff-template.md",
|
|
@@ -2772,7 +2737,7 @@
|
|
|
2772
2737
|
{
|
|
2773
2738
|
"path": "docs/module-api-changelog.md",
|
|
2774
2739
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2740
|
+
"sha256": "cdd113e011377d05ef9d923798dea05b1c6ee4936ca4e026fd402395ed0b1223"
|
|
2776
2741
|
},
|
|
2777
2742
|
{
|
|
2778
2743
|
"path": "docs/modules-contract.md",
|
|
@@ -8497,12 +8462,12 @@
|
|
|
8497
8462
|
{
|
|
8498
8463
|
"path": "package-lock.json",
|
|
8499
8464
|
"mode": "0000644",
|
|
8500
|
-
"sha256": "
|
|
8465
|
+
"sha256": "d776fedff4ca4e2858592181e4d5afc71ea7cb5aef553466f831117d0dd2a435"
|
|
8501
8466
|
},
|
|
8502
8467
|
{
|
|
8503
8468
|
"path": "package.json",
|
|
8504
8469
|
"mode": "0000644",
|
|
8505
|
-
"sha256": "
|
|
8470
|
+
"sha256": "0bc6e929e86642d34b2a0c1655ca4926adbbb2089d7ed683bb5792c06dee5fc4"
|
|
8506
8471
|
},
|
|
8507
8472
|
{
|
|
8508
8473
|
"path": "public-docs/index.html",
|
|
@@ -8522,7 +8487,7 @@
|
|
|
8522
8487
|
{
|
|
8523
8488
|
"path": "release-notes.json",
|
|
8524
8489
|
"mode": "0000644",
|
|
8525
|
-
"sha256": "
|
|
8490
|
+
"sha256": "40a0e88364bef2a5e7fadeaa8f48b8ac8fa9b49a5e113e0c28ff1065fd116515"
|
|
8526
8491
|
},
|
|
8527
8492
|
{
|
|
8528
8493
|
"path": "scripts/bongos-mcp.js",
|
|
@@ -9532,7 +9497,7 @@
|
|
|
9532
9497
|
{
|
|
9533
9498
|
"path": "scripts/gds/publish-manifest.js",
|
|
9534
9499
|
"mode": "0000644",
|
|
9535
|
-
"sha256": "
|
|
9500
|
+
"sha256": "17cb93e59d7f05319d8245fcbe9e55d5821c559680ce65f28fef763afece3b4c"
|
|
9536
9501
|
},
|
|
9537
9502
|
{
|
|
9538
9503
|
"path": "scripts/gds/publish-watch.js",
|
|
@@ -10217,7 +10182,7 @@
|
|
|
10217
10182
|
{
|
|
10218
10183
|
"path": "scripts/setup-builder.sh",
|
|
10219
10184
|
"mode": "0000644",
|
|
10220
|
-
"sha256": "
|
|
10185
|
+
"sha256": "f449553cc1965f6801e7a72a1dc90724ad7556eb047834c73c77ae9ab7ae2f76"
|
|
10221
10186
|
},
|
|
10222
10187
|
{
|
|
10223
10188
|
"path": "scripts/status-mirror-sync.js",
|
|
@@ -10387,7 +10352,7 @@
|
|
|
10387
10352
|
{
|
|
10388
10353
|
"path": "src/bongos/module-scope-map.js",
|
|
10389
10354
|
"mode": "0000644",
|
|
10390
|
-
"sha256": "
|
|
10355
|
+
"sha256": "8f15e21380fe09d31b1a19d98415e502c81e41e566eda2fc9ec2496d8044eae9"
|
|
10391
10356
|
},
|
|
10392
10357
|
{
|
|
10393
10358
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -10592,7 +10557,7 @@
|
|
|
10592
10557
|
{
|
|
10593
10558
|
"path": "src/module-api.js",
|
|
10594
10559
|
"mode": "0000644",
|
|
10595
|
-
"sha256": "
|
|
10560
|
+
"sha256": "b90170c38222c5a35bb87452ad97fa2f1289ad89d30338b4843ac4daced0cecd"
|
|
10596
10561
|
},
|
|
10597
10562
|
{
|
|
10598
10563
|
"path": "src/module-loader/catalog.js",
|
package/docs/file-map.md
CHANGED
|
@@ -17,14 +17,6 @@
|
|
|
17
17
|
├── CODEOWNERS ← owner review on the merge-gate surfaces (real since task 1002611, ADR 0159); inert until branch protection is on (scripts/gds/gating.js) — keep aligned with gate-review.js HARD_FLOOR_GLOBS
|
|
18
18
|
├── server.js ← game boot: Express + Colyseus + the shared internal surface (mountInternalSurfaces), binds 127.0.0.1:3000
|
|
19
19
|
│
|
|
20
|
-
├── .devcontainer/ ← the Example Dev Box, defined as code (ADR 0031 §3) — builds identically on a DO box + a laptop
|
|
21
|
-
│ ├── devcontainer.json ← box spec: features (Node 22, gh, Claude Code), NET_ADMIN caps, persisted volumes, lifecycle hooks
|
|
22
|
-
│ ├── Dockerfile ← Ubuntu 24.04 base + Python 3.12 venv + firewall/art system deps + build-time integrity assert
|
|
23
|
-
│ ├── init-firewall.sh ← postStart default-deny outbound firewall, tight allow-list (ADR 0031 §6)
|
|
24
|
-
│ ├── post-create.sh ← first-run: folds in setup-builder.sh --no-auth + art deps + full-stack verify
|
|
25
|
-
│ ├── healthcheck.sh ← image-level toolchain integrity check (the image half of doctor-as-health-check)
|
|
26
|
-
│ └── README.md ← box docs, ADR mapping, deliberate reconciliations, out-of-scope tasks
|
|
27
|
-
│
|
|
28
20
|
├── limitations/ ← versioning-methodology artifacts
|
|
29
21
|
│ ├── README.md
|
|
30
22
|
│ ├── v1-shipped.md ← V1 game archive (shipped 2026-05-12)
|
|
@@ -150,7 +142,7 @@
|
|
|
150
142
|
│ ├── migrate.sh ← idempotent migration runner; applies core `migrations/*.sql` always + `modules/<key>/migrations/*.sql` only when the module is enabled (ADR 0083 §module-owned-migrations); stem-keyed so relocating a migration between dirs never re-runs it on prod
|
|
151
143
|
│ ├── verify-gds-only-schema.sh ← task 1191: throwaway-DB proof that a Bongos-only migration run yields the build-system schema with NO game tables (needs local Postgres; not the DB-free unit gate)
|
|
152
144
|
│ ├── setup-builder.js ← one-command new-builder bootstrap (toolchain + npm ci + .env.local + Bongos auth); cross-platform node entry (#673)
|
|
153
|
-
│ ├── setup-builder.sh ← thin bash shim → setup-builder.js (Mac/Linux convenience +
|
|
145
|
+
│ ├── setup-builder.sh ← thin bash shim → setup-builder.js (Mac/Linux convenience + README refs)
|
|
154
146
|
│ ├── deploy/ ← deploy scripts that run ON the droplet
|
|
155
147
|
│ │ ├── deploy-prod.sh ← #1026/ADR 0056: version-controlled MIRROR of the droplet's hand-maintained ~/deploy.sh (byte-faithful). NOT auto-installed — the ci deploy key is forced-command-locked (ADR 0042/0043); reinstall by hand on change. Localhost /healthz retried 15×@1s so a slow bind no longer false-fails a deploy.
|
|
156
148
|
│ │ ├── deploy-staging.sh ← #261: staging analogue of ~/deploy.sh; scp'd to ~/deploy-staging.sh + run by deploy-staging.yml on push to `staging` (same healthz retry)
|
|
@@ -2585,5 +2585,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2585
2585
|
landed since 1.19.1047 with no explicit bump. run 36285856717. (task 1002620)
|
|
2586
2586
|
1.19.1049 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2587
2587
|
landed since 1.19.1048 with no explicit bump. run 36323359891. (task 1002620)
|
|
2588
|
+
1.19.1050 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2589
|
+
landed since 1.19.1049 with no explicit bump. run 36324195800. (task 1002620)
|
|
2588
2590
|
---------------------------------------------------------------------------
|
|
2589
2591
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1050",
|
|
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.1050",
|
|
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.1050",
|
|
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
|
@@ -7595,5 +7595,11 @@
|
|
|
7595
7595
|
"id": "1003969",
|
|
7596
7596
|
"text": "cloudbongos.com now checks for a new version every 15 minutes instead of once a day, so merged work goes live within about a quarter of an hour."
|
|
7597
7597
|
}
|
|
7598
|
+
],
|
|
7599
|
+
"1.19.1050": [
|
|
7600
|
+
{
|
|
7601
|
+
"id": "1003896",
|
|
7602
|
+
"text": "Removed the leftover dev-box container setup and the automated check that built it, so nothing in the project still describes or builds the retired dev box environment."
|
|
7603
|
+
}
|
|
7598
7604
|
]
|
|
7599
7605
|
}
|
package/scripts/setup-builder.sh
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
#
|
|
4
4
|
# The bootstrap logic now lives in scripts/setup-builder.js (Node), so it runs
|
|
5
5
|
# on Windows/macOS/Linux alike. This shim stays so existing references keep
|
|
6
|
-
# working on Mac/Linux — README
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# working on Mac/Linux — README and the onboarding primer invoke
|
|
7
|
+
# `./scripts/setup-builder.sh` / `bash scripts/setup-builder.sh ...`. It simply
|
|
8
|
+
# forwards all arguments to the Node script.
|
|
9
9
|
#
|
|
10
10
|
# On Windows (no Git Bash), run the Node entry directly instead:
|
|
11
11
|
# node scripts/setup-builder.js [--skip-keys] [--no-auth] [--help]
|
|
@@ -404,27 +404,14 @@ const MODULE_GLOBS = {
|
|
|
404
404
|
// The SELF-HOST IMAGE (V4.R63, task 1202, ADR 0062): `docker compose up` brings
|
|
405
405
|
// up Postgres + a vanilla, neutral-branded instance with every feature module
|
|
406
406
|
// off. Assigned here by task 1004146 — same class as the .github/ and .husky/
|
|
407
|
-
// entries above
|
|
408
|
-
// defines WHAT RUNS. Not
|
|
407
|
+
// entries above: repo-root build config that defines WHAT RUNS. Not
|
|
409
408
|
// `provisioning`, which owns the control plane that stands up instances, not an
|
|
410
|
-
// image a stranger runs.
|
|
411
|
-
// inbound references coming from .devcontainer/ and the devcontainer workflow.
|
|
409
|
+
// image a stranger runs.
|
|
412
410
|
'Dockerfile', 'docker-compose.yml', 'docker-entrypoint.sh', '.dockerignore',
|
|
413
411
|
// Repo-level dotfiles, the class .gitattributes and .npmrc already sit in.
|
|
414
412
|
// .env.local.example is the local-secrets template, next to the .gitleaks.toml
|
|
415
413
|
// scanner config above (task 1004146).
|
|
416
414
|
'.gitignore', '.env.local.example',
|
|
417
|
-
// .devcontainer/ is the same class as the entries above it — repo-level
|
|
418
|
-
// developer-environment config, not a feature's territory (task 1004029).
|
|
419
|
-
// It belonged to NO module and was not in SHARED_GLOBS, so isCovered() said
|
|
420
|
-
// false for it under EVERY possible scope_modules value: no goal could
|
|
421
|
-
// authorize a task that touched it, which is what stalled task 1003896
|
|
422
|
-
// (delete .devcontainer/). Given to devsecops rather than added to
|
|
423
|
-
// SHARED_GLOBS because it has a real owner — SHARED_GLOBS is for roots owned
|
|
424
|
-
// by nobody, and every addition there widens the wall for every goal at once.
|
|
425
|
-
// devsecops is PROTECTED, so this path is now Archon-only to scope; that is
|
|
426
|
-
// the correct floor for a file that defines what a builder's environment runs.
|
|
427
|
-
'.devcontainer/',
|
|
428
415
|
'package.json', 'package-lock.json',
|
|
429
416
|
'scripts/gds/fitness.js', 'scripts/gds/docs-entropy.js',
|
|
430
417
|
'scripts/gds/redteam-patrol.js', 'scripts/gds/audit-gate.js',
|
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.1050'; // 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');
|
package/.devcontainer/Dockerfile
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Example Dev Box — the OTB builder environment as code (ADR 0031 §3).
|
|
2
|
-
# Base Ubuntu 24.04 ships Python 3.12 (matches CI's art-pipeline cert) + a non-root
|
|
3
|
-
# `vscode` sudo user. Node 22 / gh / Claude Code arrive as version-pinned features
|
|
4
|
-
# (devcontainer.json); this Dockerfile covers the OS bits features don't: firewall
|
|
5
|
-
# tooling + the art pipeline's system/Python deps. Builds identically on a DO box
|
|
6
|
-
# and a laptop (x64/arm64). Full design + reconciliations: ./README.md.
|
|
7
|
-
|
|
8
|
-
# Pinned to the multi-arch (amd64+arm64) image-index digest, not just the mutable
|
|
9
|
-
# tag, so an upstream tag mutation / supply-chain swap can't silently replace the
|
|
10
|
-
# box foundation. devcontainer-lock.json pins the features; this pins the base.
|
|
11
|
-
# Bump the digest deliberately when intentionally moving to a newer base.
|
|
12
|
-
FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04@sha256:d94c97dd9cacf183d0a6fd12a8e87b526e9e928307674ae9c94139139c0c6eae
|
|
13
|
-
|
|
14
|
-
ARG TZ="Etc/UTC"
|
|
15
|
-
ENV TZ="${TZ}"
|
|
16
|
-
ENV DEVCONTAINER=true
|
|
17
|
-
# Persisted shell history (see the volume mount in devcontainer.json).
|
|
18
|
-
ENV HISTFILE=/commandhistory/.bash_history
|
|
19
|
-
|
|
20
|
-
# --- OS packages --------------------------------------------------------------
|
|
21
|
-
# Firewall tooling (iptables/ipset/iproute2 + dnsutils for `dig`), jq for the
|
|
22
|
-
# GitHub-meta parse, the Python 3.12 toolchain, and the sharp from-source net.
|
|
23
|
-
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
24
|
-
&& apt-get install -y --no-install-recommends \
|
|
25
|
-
iptables ipset iproute2 dnsutils \
|
|
26
|
-
jq curl ca-certificates \
|
|
27
|
-
python3 python3-pip python3-venv python3-dev \
|
|
28
|
-
build-essential pkg-config \
|
|
29
|
-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
30
|
-
|
|
31
|
-
# --- Python venv for the art pipeline (PEP 668 safe) --------------------------
|
|
32
|
-
# Ubuntu 24.04 marks the system Python "externally managed", so a global
|
|
33
|
-
# `pip install` is refused. A dedicated venv on PATH gives the art pipeline a
|
|
34
|
-
# clean, writable 3.12 interpreter where `python3` "just works" — matching the
|
|
35
|
-
# isolated interpreter CI gets from actions/setup-python. Deps themselves are
|
|
36
|
-
# installed from the repo's pinned art/pipeline/requirements.txt at first-run
|
|
37
|
-
# (post-create.sh), so the set stays manifest-driven and reproducible.
|
|
38
|
-
ENV OTB_VENV=/opt/otb-venv
|
|
39
|
-
RUN python3 -m venv "${OTB_VENV}" \
|
|
40
|
-
&& "${OTB_VENV}/bin/pip" install --no-cache-dir --upgrade pip \
|
|
41
|
-
&& chown -R vscode:vscode "${OTB_VENV}"
|
|
42
|
-
# Prepend the venv to PATH for every shell + lifecycle command.
|
|
43
|
-
ENV PATH="${OTB_VENV}/bin:${PATH}"
|
|
44
|
-
|
|
45
|
-
# --- Firewall + health scripts ------------------------------------------------
|
|
46
|
-
# NB the install path: the Claude Code feature ALSO ships its own
|
|
47
|
-
# /usr/local/bin/init-firewall.sh, and features install AFTER this Dockerfile —
|
|
48
|
-
# so installing ours at that path would be silently overwritten by the feature's
|
|
49
|
-
# (which lacks our GDS/Gemini/PyPI allow-list). We install ours as
|
|
50
|
-
# otb-init-firewall.sh and point postStart at it; the feature's copy sits unused.
|
|
51
|
-
COPY init-firewall.sh /usr/local/bin/otb-init-firewall.sh
|
|
52
|
-
COPY healthcheck.sh /usr/local/bin/healthcheck.sh
|
|
53
|
-
RUN chmod +x /usr/local/bin/otb-init-firewall.sh /usr/local/bin/healthcheck.sh
|
|
54
|
-
|
|
55
|
-
# Persisted-history dir owned by the runtime user.
|
|
56
|
-
RUN mkdir -p /commandhistory && chown -R vscode:vscode /commandhistory
|
|
57
|
-
|
|
58
|
-
# --- Build-time integrity assertion -------------------------------------------
|
|
59
|
-
# Fail the BUILD (not some later mystery at runtime) if the toolchain the box
|
|
60
|
-
# promises isn't actually present. This is half of "doctor.js as health check":
|
|
61
|
-
# the image-level half, asserting the box was built right. The builder-level
|
|
62
|
-
# half (session/auth/git) runs as scripts/gds/doctor.js at attach time.
|
|
63
|
-
RUN /usr/local/bin/healthcheck.sh --build || \
|
|
64
|
-
(echo "BUILD FAILED: toolchain integrity check did not pass" && exit 1)
|
|
65
|
-
|
|
66
|
-
# Runtime health check for the DO-box-as-service case (#598): a parked/recreated
|
|
67
|
-
# box can be probed for "is the toolchain intact" without a GDS session.
|
|
68
|
-
HEALTHCHECK --interval=1m --timeout=15s --start-period=30s --retries=3 \
|
|
69
|
-
CMD /usr/local/bin/healthcheck.sh || exit 1
|
package/.devcontainer/README.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# The Example Dev Box (`.devcontainer/`)
|
|
2
|
-
|
|
3
|
-
This folder is **the box defined as code** — [ADR 0031 §3](../docs/adr/0031-cloud-dev-environments-for-builders.md). It is the single environment every OTB builder runs Claude Code's *worker* in, whether they reach it from Claude Desktop → SSH, from the web → Remote Control, or locally via Docker Desktop. The same spec builds and runs identically on a DigitalOcean box and on a laptop.
|
|
4
|
-
|
|
5
|
-
> Recall the mental model ([ADR 0031 "mental model"](../docs/adr/0031-cloud-dev-environments-for-builders.md), and [the onboarding primer](../docs/onboarding/primer.md)): the **screen** is your device, the **brain** is Anthropic's servers, and this is the **worker** — where the repo lives and the work happens.
|
|
6
|
-
|
|
7
|
-
## What's here
|
|
8
|
-
|
|
9
|
-
| File | Role |
|
|
10
|
-
|---|---|
|
|
11
|
-
| `devcontainer.json` | The box spec: base build, version-pinned features (Node 22, GitHub CLI, Claude Code), firewall capabilities, persisted volumes, lifecycle hooks. |
|
|
12
|
-
| `Dockerfile` | Ubuntu 24.04 base + the OS-level bits features don't cover: firewall tooling, the art-pipeline system deps, and a PEP-668-safe Python venv. |
|
|
13
|
-
| `init-firewall.sh` | The **default-deny outbound firewall** (ADR 0031 §6). Runs at every container start. Allow-lists only the hosts the toolchain needs. |
|
|
14
|
-
| `post-create.sh` | First-run provisioning: folds in `setup-builder.sh`, installs art deps, verifies the stack. |
|
|
15
|
-
| `healthcheck.sh` | Image-level toolchain integrity check — the "is the box built right?" half of doctor-as-health-check. |
|
|
16
|
-
|
|
17
|
-
## The toolchain it guarantees
|
|
18
|
-
|
|
19
|
-
- **Node 22** (`package.json` requires `>=22`) — via the `node` devcontainer feature.
|
|
20
|
-
- **Python 3.12** — native to the Ubuntu 24.04 base, installed into a venv at `/opt/otb-venv`.
|
|
21
|
-
- **sharp ^0.34** — the game/art native dep; prebuilt binaries on linux x64 **and** arm64, so no compile.
|
|
22
|
-
- **The five art-pipeline libs** — Pillow, numpy, scipy, scikit-learn, certifi (from the pinned `art/pipeline/requirements.txt`).
|
|
23
|
-
- **Claude Code + GitHub CLI + git** — via features.
|
|
24
|
-
|
|
25
|
-
## Lifecycle
|
|
26
|
-
|
|
27
|
-
1. **build** — Dockerfile lays down the OS deps + venv, then asserts the toolchain (`healthcheck.sh --build`) so a broken box fails the build, not a later mystery.
|
|
28
|
-
2. **postCreate** (once) — `post-create.sh`: brings up the **firewall first** (so the dependency installs below run behind default-deny — no open-egress window during provisioning), then `setup-builder.sh --no-auth --skip-keys` (toolchain check + `npm ci` + `.env.local`), then `pip install` the art deps into the venv, then verify sharp + the art libs load.
|
|
29
|
-
3. **postStart** (every start) — `sudo otb-init-firewall.sh` re-applies the default-deny egress firewall (iptables state resets each container start).
|
|
30
|
-
4. **postAttach** (every attach) — `node scripts/gds/doctor.js` runs the *builder* health check (session/auth/git/env). Non-fatal, so a fresh, not-yet-authed box still attaches.
|
|
31
|
-
|
|
32
|
-
On first connect you finish two runtime steps the box can't do for you: authenticate the GDS CLI (`node scripts/gds/setup.js` or `/builder-setup`) and, if you run the art pipeline, drop a Google AI Studio key in `~/.config/otb/env` (a persisted volume — survives rebuilds).
|
|
33
|
-
|
|
34
|
-
## The firewall allow-list
|
|
35
|
-
|
|
36
|
-
Default-deny outbound. Only these egress targets are permitted (keep it tight — every addition is attack surface):
|
|
37
|
-
|
|
38
|
-
| Target | Why |
|
|
39
|
-
|---|---|
|
|
40
|
-
| Anthropic API + telemetry | the brain (tokens), Claude Code |
|
|
41
|
-
| GitHub (published meta ranges) | `git` push/pull, `gh` |
|
|
42
|
-
| npm registry | `npm ci` |
|
|
43
|
-
| `example.com` + `staging.` | the GDS API (`/api/gds/*` CLI) |
|
|
44
|
-
| `generativelanguage.googleapis.com` | the pixel-art pipeline (Gemini) |
|
|
45
|
-
| `pypi.org` + `files.pythonhosted.org` | `pip install` the art deps |
|
|
46
|
-
|
|
47
|
-
The firewall comes up **before the dependency installs** (in postCreate) and is **re-applied on every start** (postStart), so there is no open-egress window during provisioning — `npm ci` and `pip` run behind the allow-list (npm/PyPI/GitHub are on it). It **fails closed** by default: if it can't apply rules or verification fails, container start fails. The one documented escape hatch is `FIREWALL_OPTIONAL=1`, for a laptop Docker host that won't grant `NET_ADMIN` — there the builder is already root of their own machine, so egress lockdown is advisory (ADR 0031 §6 "honest limit"). Using it emits a loud, greppable `SECURITY AUDIT` line in the container logs. The DO box (#598) leaves it enforcing.
|
|
48
|
-
|
|
49
|
-
## Running it
|
|
50
|
-
|
|
51
|
-
- **Laptop (Docker Desktop):** open the repo in VS Code → "Reopen in Container", or `devcontainer up --workspace-folder .`. Apple-silicon (arm64) and Intel (x64) both work — sharp ships prebuilt binaries for each, and `node_modules` is a container-isolated volume so the host's macOS binaries never collide with the container's Linux ones.
|
|
52
|
-
- **DigitalOcean box:** the per-builder provisioning (#598) builds this same spec on the box and exposes it over SSH / `claude rc`. Nothing in this folder is DO-specific — that's the portability the ADR commits to.
|
|
53
|
-
|
|
54
|
-
### Known macOS-laptop caveat (does not affect the DO box)
|
|
55
|
-
|
|
56
|
-
On a macOS Docker Desktop bind mount, VirtioFS can intermittently return `EDEADLK` ("Resource deadlock avoided") when reading repo files, especially while the host is heavily accessing the same folder. The symptom seen in testing was the postCreate **art-deps `pip install` failing** to read `art/pipeline/requirements.txt` (the box otherwise built fine — Node, Python, `sharp`, the firewall, and the runtime health check all passed). post-create.sh retries and is non-fatal, so the box still provisions. If art deps don't install: rebuild the container, or switch **Docker Desktop → Settings → General → file sharing to "gRPC FUSE"**. The DO box uses a native filesystem (a real clone, no bind mount), so this never occurs there.
|
|
57
|
-
|
|
58
|
-
## Deliberate reconciliations (where this differs from a naïve reading of the ADR)
|
|
59
|
-
|
|
60
|
-
- **Python 3.12, not "3.10".** ADR 0031 §3 says "Python 3.10"; that's a floor. CI (`portability-smoke.yml`) certifies the art pipeline on **3.12**, so the box uses 3.12 to *reproduce* CI rather than approximate it. 3.12 satisfies the `>=3.10` requirement in `setup-builder.sh`.
|
|
61
|
-
- **`sharp` "build deps" without system libvips.** The ADR says "sharp build deps." sharp ^0.34 uses prebuilt binaries with libvips **bundled**, so no compile happens on x64/arm64. We install `build-essential` + `pkg-config` as the from-source safety net the ADR intends, but deliberately do **not** install system `libvips-dev` — a mismatched system libvips is a known footgun; sharp prefers its own.
|
|
62
|
-
- **Allow-list is wider than the ADR's "Anthropic, GitHub, npm".** That parenthetical describes the *Anthropic feature's* defaults. Our box legitimately needs the GDS API + Gemini + PyPI or the CLI and art pipeline break behind the firewall. The list stays minimal and each entry is justified above.
|
|
63
|
-
- **Base = Ubuntu 24.04 devcontainers image** (not Anthropic's reference `node:20`) — for the Node 22 + Python 3.12 parity above and a ready-made non-root `vscode` sudo user. The Claude Code *feature* is still Anthropic's, satisfying "the Anthropic Claude Code container feature."
|
|
64
|
-
|
|
65
|
-
## Out of scope (separate ADR-0031 tasks)
|
|
66
|
-
|
|
67
|
-
This task is the **foundation only**. It does not provision boxes, lock builders to one box, gate by rank, or wire billing:
|
|
68
|
-
|
|
69
|
-
- **#597** — move prod deploy to GitHub Actions (no builder box holds the droplet key).
|
|
70
|
-
- **#598** — per-builder DO box provisioning + auto-suspend (depends on this).
|
|
71
|
-
- **#599** — Desktop managed-settings + the always-on `claude rc` service.
|
|
72
|
-
- **#600** — rank-gated source access via the box.
|
|
73
|
-
- **#602** — cost passthrough (org-funded starter → self-fund).
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"features": {
|
|
3
|
-
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {
|
|
4
|
-
"version": "1.0.5",
|
|
5
|
-
"resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a",
|
|
6
|
-
"integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a"
|
|
7
|
-
},
|
|
8
|
-
"ghcr.io/devcontainers/features/github-cli:1": {
|
|
9
|
-
"version": "1.1.0",
|
|
10
|
-
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
|
|
11
|
-
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
|
|
12
|
-
},
|
|
13
|
-
"ghcr.io/devcontainers/features/node:1": {
|
|
14
|
-
"version": "1.7.1",
|
|
15
|
-
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
|
|
16
|
-
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Example Dev Box — the OTB builder environment as code (ADR 0031 §3).
|
|
3
|
-
//
|
|
4
|
-
// This is the single box spec every builder runs, reached via Claude Desktop
|
|
5
|
-
// → SSH or the web → Remote Control (ADR 0031 §1). It builds identically on a
|
|
6
|
-
// DigitalOcean box and on a laptop via Docker Desktop. The pre-distributed
|
|
7
|
-
// Desktop "managed settings" that lock a non-technical builder to exactly this
|
|
8
|
-
// box are a separate task (#599); this file is the box itself.
|
|
9
|
-
"name": "Example Dev Box",
|
|
10
|
-
|
|
11
|
-
"build": {
|
|
12
|
-
"dockerfile": "Dockerfile",
|
|
13
|
-
// Context is this .devcontainer/ folder (the default). The Dockerfile only
|
|
14
|
-
// COPYs the two scripts that live here; nothing from the repo root is needed
|
|
15
|
-
// at build time (npm ci / pip install run at postCreate against the mounted
|
|
16
|
-
// workspace), so the COPY paths stay relative to this folder.
|
|
17
|
-
"context": ".",
|
|
18
|
-
"args": {
|
|
19
|
-
// Honor the host timezone when set, else UTC.
|
|
20
|
-
"TZ": "${localEnv:TZ:Etc/UTC}"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
// Node 22, the GitHub CLI, and Claude Code arrive as version-pinned features
|
|
25
|
-
// rather than hand-rolled in the Dockerfile (the idiomatic, reproducible path).
|
|
26
|
-
// The Claude Code feature also installs Anthropic's default-deny egress posture
|
|
27
|
-
// tooling; our init-firewall.sh (postStart) is the OTB-tuned allow-list on top.
|
|
28
|
-
"features": {
|
|
29
|
-
"ghcr.io/devcontainers/features/node:1": { "version": "22" },
|
|
30
|
-
"ghcr.io/devcontainers/features/github-cli:1": {},
|
|
31
|
-
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
// NET_ADMIN + NET_RAW let the postStart firewall manage iptables/ipset. Without
|
|
35
|
-
// these the firewall fails closed (or warns if FIREWALL_OPTIONAL=1) — see
|
|
36
|
-
// init-firewall.sh. Docker Desktop and a DO Docker host both honor cap-add.
|
|
37
|
-
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
|
|
38
|
-
|
|
39
|
-
// The devcontainers Ubuntu base ships a non-root `vscode` user with passwordless
|
|
40
|
-
// sudo (needed for the postStart firewall). Run as that user, not root.
|
|
41
|
-
"remoteUser": "vscode",
|
|
42
|
-
|
|
43
|
-
"containerEnv": {
|
|
44
|
-
"DEVCONTAINER": "true",
|
|
45
|
-
// Where the art-pipeline Python venv lives (the Dockerfile prepends its bin
|
|
46
|
-
// to PATH); post-create.sh and healthcheck.sh reference it.
|
|
47
|
-
"OTB_VENV": "/opt/otb-venv",
|
|
48
|
-
// A modest Node heap headroom for the dev server + art pipeline.
|
|
49
|
-
"NODE_OPTIONS": "--max-old-space-size=4096"
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
// Standardize the in-container repo path (matches Anthropic's reference) so
|
|
53
|
-
// docs/scripts can assume /workspace regardless of the host folder name.
|
|
54
|
-
"workspaceFolder": "/workspace",
|
|
55
|
-
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
|
|
56
|
-
|
|
57
|
-
// Named volumes (NOT binds — nothing secret lands in the repo or image, per
|
|
58
|
-
// ADR 0022):
|
|
59
|
-
// - node_modules: a container-ISOLATED volume so the host's native binaries
|
|
60
|
-
// (e.g. a macOS-built `sharp`) never leak into this linux box, and vice
|
|
61
|
-
// versa. This is what makes "runs identically on a laptop" true — without
|
|
62
|
-
// it, bind-mounting the repo from macOS breaks sharp in the container.
|
|
63
|
-
// - the Claude Code login/config (so a rebuild doesn't force re-login),
|
|
64
|
-
// - shell history (quality-of-life),
|
|
65
|
-
// - ~/.config/otb (the per-builder Gemini key + GDS session survive rebuilds).
|
|
66
|
-
"mounts": [
|
|
67
|
-
"source=otb-node-modules-${devcontainerId},target=/workspace/node_modules,type=volume",
|
|
68
|
-
"source=otb-claude-config-${devcontainerId},target=/home/vscode/.claude,type=volume",
|
|
69
|
-
"source=otb-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
|
70
|
-
"source=otb-config-${devcontainerId},target=/home/vscode/.config/otb,type=volume"
|
|
71
|
-
],
|
|
72
|
-
|
|
73
|
-
// Lifecycle:
|
|
74
|
-
// postCreate — once, after the repo is mounted: fold in setup-builder.sh,
|
|
75
|
-
// install art deps, verify the stack (post-create.sh).
|
|
76
|
-
// postStart — every start: bring up the default-deny firewall.
|
|
77
|
-
// postAttach — every attach: run the BUILDER health check (session/auth/git).
|
|
78
|
-
// Non-fatal (|| true) so a not-yet-authed fresh box still attaches.
|
|
79
|
-
"postCreateCommand": "bash .devcontainer/post-create.sh",
|
|
80
|
-
"postStartCommand": "sudo /usr/local/bin/otb-init-firewall.sh",
|
|
81
|
-
"postAttachCommand": "node scripts/gds/doctor.js || true",
|
|
82
|
-
"waitFor": "postCreateCommand",
|
|
83
|
-
|
|
84
|
-
"customizations": {
|
|
85
|
-
"vscode": {
|
|
86
|
-
"extensions": [
|
|
87
|
-
"anthropic.claude-code",
|
|
88
|
-
"dbaeumer.vscode-eslint",
|
|
89
|
-
"esbenp.prettier-vscode"
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# healthcheck.sh — image-level toolchain integrity check for the Example
|
|
3
|
-
# Dev Box (ADR 0031 §3).
|
|
4
|
-
#
|
|
5
|
-
# This is the IMAGE half of "doctor.js as the health check": it answers "was the
|
|
6
|
-
# box built correctly?" using only what the image itself provides — no GDS
|
|
7
|
-
# session, no repo checkout, no network. The BUILDER half ("is *this builder*
|
|
8
|
-
# authed, hooked, and clean?") is scripts/gds/doctor.js, run at attach time.
|
|
9
|
-
#
|
|
10
|
-
# Two modes:
|
|
11
|
-
# --build run as the final Dockerfile RUN step (fail the build on a bad box).
|
|
12
|
-
# (default) the runtime HEALTHCHECK / a recreated-box probe (#598).
|
|
13
|
-
#
|
|
14
|
-
# Both check the same stable, image-level toolchain. Repo-level facts (sharp in
|
|
15
|
-
# node_modules, the art libs in the venv) are installed at first-run by
|
|
16
|
-
# post-create.sh and verified there — they are intentionally NOT gated here so
|
|
17
|
-
# the container never flaps "unhealthy" during provisioning.
|
|
18
|
-
#
|
|
19
|
-
# NOTE on ordering: Node, the GitHub CLI, and Claude Code are installed by
|
|
20
|
-
# devcontainer *features*, which run AFTER the Dockerfile is built. So the
|
|
21
|
-
# --build assertion can't see Node — it checks only what the Dockerfile itself
|
|
22
|
-
# provides (the Python venv + firewall tooling). The Node check runs at runtime,
|
|
23
|
-
# where features are present.
|
|
24
|
-
|
|
25
|
-
set -uo pipefail
|
|
26
|
-
|
|
27
|
-
MODE="${1:-runtime}"
|
|
28
|
-
fails=0
|
|
29
|
-
|
|
30
|
-
ok() { echo " ok $*"; }
|
|
31
|
-
bad() { echo " FAIL $*"; fails=$((fails + 1)); }
|
|
32
|
-
|
|
33
|
-
# Node >= 22 — runtime only (feature-installed after the Dockerfile build).
|
|
34
|
-
if [ "$MODE" != "--build" ]; then
|
|
35
|
-
if command -v node >/dev/null 2>&1; then
|
|
36
|
-
nver="$(node --version 2>/dev/null)"
|
|
37
|
-
nmaj="$(echo "$nver" | sed 's/^v//' | cut -d. -f1)"
|
|
38
|
-
if [[ "$nmaj" =~ ^[0-9]+$ ]] && [ "$nmaj" -ge 22 ]; then
|
|
39
|
-
ok "node $nver (>= 22)"
|
|
40
|
-
else
|
|
41
|
-
bad "node $nver found, need >= 22"
|
|
42
|
-
fi
|
|
43
|
-
else
|
|
44
|
-
bad "node not on PATH"
|
|
45
|
-
fi
|
|
46
|
-
else
|
|
47
|
-
echo " -- node check deferred to runtime (feature-installed after the Dockerfile)"
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Python >= 3.12 (the venv interpreter — must be first on PATH)
|
|
51
|
-
if command -v python3 >/dev/null 2>&1; then
|
|
52
|
-
pyver="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || echo "?")"
|
|
53
|
-
pymaj="${pyver%%.*}"; pymin="${pyver#*.}"
|
|
54
|
-
if [[ "$pymaj" =~ ^[0-9]+$ ]] && [[ "$pymin" =~ ^[0-9]+$ ]] \
|
|
55
|
-
&& { [ "$pymaj" -gt 3 ] || { [ "$pymaj" -eq 3 ] && [ "$pymin" -ge 12 ]; }; }; then
|
|
56
|
-
ok "python ${pyver} (>= 3.12) [$(command -v python3)]"
|
|
57
|
-
else
|
|
58
|
-
bad "python ${pyver} found, need >= 3.12"
|
|
59
|
-
fi
|
|
60
|
-
# The venv must be the active interpreter so the art pipeline's deps resolve.
|
|
61
|
-
if [ -n "${OTB_VENV:-}" ] && [ "$(command -v python3)" = "${OTB_VENV}/bin/python3" ]; then
|
|
62
|
-
ok "art-pipeline venv active (${OTB_VENV})"
|
|
63
|
-
else
|
|
64
|
-
bad "expected venv python at ${OTB_VENV:-<unset>}/bin/python3, got $(command -v python3)"
|
|
65
|
-
fi
|
|
66
|
-
else
|
|
67
|
-
bad "python3 not on PATH"
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# Firewall tooling present (the default-deny boundary depends on it).
|
|
71
|
-
for tool in iptables ipset dig jq; do
|
|
72
|
-
if command -v "$tool" >/dev/null 2>&1; then
|
|
73
|
-
ok "$tool present"
|
|
74
|
-
else
|
|
75
|
-
bad "$tool missing (firewall/init would fail)"
|
|
76
|
-
fi
|
|
77
|
-
done
|
|
78
|
-
|
|
79
|
-
if [ "$fails" -eq 0 ]; then
|
|
80
|
-
[ "$MODE" = "--build" ] && echo "toolchain integrity: OK (build)"
|
|
81
|
-
exit 0
|
|
82
|
-
fi
|
|
83
|
-
echo "toolchain integrity: ${fails} problem(s)"
|
|
84
|
-
exit 1
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# init-firewall.sh — default-deny outbound firewall for the Example Dev Box.
|
|
3
|
-
#
|
|
4
|
-
# ADR 0031 §3 + §6: the box is an org-controlled, egress-restricted surface. This
|
|
5
|
-
# script flips the container to default-DROP and then allow-lists ONLY the hosts
|
|
6
|
-
# the OTB toolchain legitimately needs. Everything else is blocked, so a builder
|
|
7
|
-
# session (or a compromised dependency) cannot quietly exfiltrate to an arbitrary
|
|
8
|
-
# host.
|
|
9
|
-
#
|
|
10
|
-
# Mechanism (mirrors Anthropic's reference init-firewall.sh):
|
|
11
|
-
# 1. Resolve every allow-listed host to IPs WHILE egress is still open.
|
|
12
|
-
# 2. Stuff them into an `ipset` (hash:net so GitHub's CIDR ranges fit).
|
|
13
|
-
# 3. Flip INPUT/OUTPUT/FORWARD to DROP, then re-allow loopback, established
|
|
14
|
-
# connections, DNS, SSH, and OUTPUT to the ipset.
|
|
15
|
-
# 4. Verify: a blocked host must fail; an allow-listed host must succeed.
|
|
16
|
-
#
|
|
17
|
-
# Allow-list (and WHY each is here — keep this tight, every addition is egress):
|
|
18
|
-
# - GitHub (api.github.com/meta ranges) ......... git push/pull, `gh`
|
|
19
|
-
# - npm registry (registry.npmjs.org) ............ `npm ci`
|
|
20
|
-
# - Anthropic API (api.anthropic.com + telemetry) the brain (tokens), Claude Code
|
|
21
|
-
# - THIS instance's own API host(s) .............. the GDS API (/api/gds/* CLI)
|
|
22
|
-
# (resolved from config/branding.json at runtime — task 1002757; a hardcoded
|
|
23
|
-
# founder host allow-listed someone else's server and BLOCKED the builder's own)
|
|
24
|
-
# - generativelanguage.googleapis.com ............ the pixel-art pipeline (Gemini) [OTB]
|
|
25
|
-
# - pypi.org + files.pythonhosted.org ............ `pip install` art deps [OTB]
|
|
26
|
-
#
|
|
27
|
-
# Runs as root via `sudo` from the devcontainer postStartCommand. Needs the
|
|
28
|
-
# NET_ADMIN + NET_RAW capabilities (granted by runArgs in devcontainer.json).
|
|
29
|
-
#
|
|
30
|
-
# Fail posture: FAIL-CLOSED by default. If the rules cannot be applied or the
|
|
31
|
-
# verification fails, the script exits non-zero. Set FIREWALL_OPTIONAL=1 to
|
|
32
|
-
# downgrade a cap/permission failure to a loud warning that still lets the
|
|
33
|
-
# container boot — intended ONLY for a laptop Docker host that won't grant
|
|
34
|
-
# NET_ADMIN (where the builder is already root of their own machine, so egress
|
|
35
|
-
# lockdown is advisory anyway — see ADR 0031 §6 "honest limit"). The DO box
|
|
36
|
-
# (#598) sets FIREWALL_OPTIONAL unset, so it always enforces.
|
|
37
|
-
|
|
38
|
-
set -euo pipefail
|
|
39
|
-
IFS=$'\n\t'
|
|
40
|
-
|
|
41
|
-
OPTIONAL="${FIREWALL_OPTIONAL:-0}"
|
|
42
|
-
|
|
43
|
-
# Hosts to allow-list by name (resolved via dig at runtime). GitHub is handled
|
|
44
|
-
# separately via its published meta ranges. Keep this list minimal.
|
|
45
|
-
ALLOWED_HOSTS=(
|
|
46
|
-
"registry.npmjs.org"
|
|
47
|
-
"api.anthropic.com"
|
|
48
|
-
"statsig.anthropic.com"
|
|
49
|
-
"sentry.io"
|
|
50
|
-
"generativelanguage.googleapis.com"
|
|
51
|
-
"pypi.org"
|
|
52
|
-
"files.pythonhosted.org"
|
|
53
|
-
# THIS instance's own hosts are appended below from its branding pack.
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
# Append the instance's OWN API hosts, read from config/branding.json at runtime
|
|
57
|
-
# (task 1002757). Was two hardcoded founder hosts, which allow-listed someone
|
|
58
|
-
# else's server and left the builder's own GDS API blocked — the devcontainer
|
|
59
|
-
# then failed every /builder-* call for a reason nothing in the log explained.
|
|
60
|
-
# GDS_ALLOW_HOSTS (space/comma separated) overrides for a non-standard setup.
|
|
61
|
-
REPO_ROOT_FW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
62
|
-
INSTANCE_HOSTS="${GDS_ALLOW_HOSTS:-}"
|
|
63
|
-
if [ -z "$INSTANCE_HOSTS" ]; then
|
|
64
|
-
INSTANCE_HOSTS="$(node -e '
|
|
65
|
-
const d = (require(process.argv[1]).branding().domains) || {};
|
|
66
|
-
const hosts = new Set();
|
|
67
|
-
// Only *_ORIGIN-shaped values yield a host, and only a real public FQDN
|
|
68
|
-
// counts: the neutral starter pack carries localhost:3000 and bare subdomain
|
|
69
|
-
// PREFIXES ("sandbox-", "term-"), none of which is an allow-listable host.
|
|
70
|
-
for (const v of Object.values(d)) {
|
|
71
|
-
if (typeof v !== "string" || !v.trim()) continue;
|
|
72
|
-
let host = null;
|
|
73
|
-
try { host = new URL(v).hostname; } catch { host = v.trim(); }
|
|
74
|
-
if (!host) continue;
|
|
75
|
-
if (!/^(?=.{1,253}$)[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(host)) continue;
|
|
76
|
-
if (/^localhost$/i.test(host)) continue;
|
|
77
|
-
hosts.add(host.toLowerCase());
|
|
78
|
-
}
|
|
79
|
-
process.stdout.write([...hosts].join(" "));
|
|
80
|
-
' "$REPO_ROOT_FW/src/branding" 2>/dev/null || true)"
|
|
81
|
-
fi
|
|
82
|
-
for h in ${INSTANCE_HOSTS//,/ }; do
|
|
83
|
-
[ -n "$h" ] && ALLOWED_HOSTS+=("$h")
|
|
84
|
-
done
|
|
85
|
-
|
|
86
|
-
log() { echo "[firewall] $*"; }
|
|
87
|
-
die() {
|
|
88
|
-
# Honor the documented escape hatch: a cap/permission failure on a laptop
|
|
89
|
-
# host downgrades to a warning instead of bricking container startup.
|
|
90
|
-
# The bypass is env-driven, so it can't be access-controlled from inside the
|
|
91
|
-
# script — the mitigation is a LOUD, greppable audit line so any use is
|
|
92
|
-
# visible in container logs / log aggregation. On the org DO box this must
|
|
93
|
-
# never be set; if this banner appears there, investigate.
|
|
94
|
-
if [ "$OPTIONAL" = "1" ]; then
|
|
95
|
-
log "==================== SECURITY AUDIT ===================="
|
|
96
|
-
log "egress firewall DISABLED via FIREWALL_OPTIONAL=1"
|
|
97
|
-
log " reason: $*"
|
|
98
|
-
log " the box is running WITHOUT default-deny egress (laptop-only escape hatch)"
|
|
99
|
-
log "======================================================="
|
|
100
|
-
exit 0
|
|
101
|
-
fi
|
|
102
|
-
log "ERROR: $*"
|
|
103
|
-
log "If this host genuinely cannot grant NET_ADMIN (e.g. a restricted laptop Docker),"
|
|
104
|
-
log "re-run with FIREWALL_OPTIONAL=1 to boot without egress lockdown."
|
|
105
|
-
exit 1
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
# Pre-flight: we must be root and have the iptables/ipset tooling + caps.
|
|
109
|
-
if [ "$(id -u)" -ne 0 ]; then
|
|
110
|
-
die "must run as root (expected: sudo $0)"
|
|
111
|
-
fi
|
|
112
|
-
command -v iptables >/dev/null 2>&1 || die "iptables not installed"
|
|
113
|
-
command -v ipset >/dev/null 2>&1 || die "ipset not installed"
|
|
114
|
-
command -v dig >/dev/null 2>&1 || die "dig (dnsutils) not installed"
|
|
115
|
-
|
|
116
|
-
# A NET_ADMIN probe: try a harmless list. If the kernel refuses, we lack the cap.
|
|
117
|
-
if ! iptables -L >/dev/null 2>&1; then
|
|
118
|
-
die "cannot manage iptables (NET_ADMIN capability missing?)"
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
log "resolving allow-list while egress is still open…"
|
|
122
|
-
|
|
123
|
-
# Fresh ipset. hash:net so we can store both single IPs and CIDR ranges.
|
|
124
|
-
# create -exist + flush (not destroy+create): on a manual re-run an iptables
|
|
125
|
-
# OUTPUT rule may still reference the set, which makes `destroy` fail. Flushing
|
|
126
|
-
# empties it in place while keeping the object valid.
|
|
127
|
-
ipset create allowed-domains hash:net -exist
|
|
128
|
-
ipset flush allowed-domains
|
|
129
|
-
|
|
130
|
-
add_ip() {
|
|
131
|
-
# Validate it looks like an IPv4 (or CIDR) before adding, so a bad DNS answer
|
|
132
|
-
# can't inject garbage into the set.
|
|
133
|
-
local ip="$1"
|
|
134
|
-
if [[ "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}(/[0-9]{1,2})?$ ]]; then
|
|
135
|
-
ipset add allowed-domains "$ip" 2>/dev/null || true
|
|
136
|
-
fi
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# --- GitHub: use the published meta ranges (covers github.com, api, git, web) -
|
|
140
|
-
log " + GitHub meta ranges"
|
|
141
|
-
gh_meta="$(curl -fsS --max-time 20 https://api.github.com/meta || true)"
|
|
142
|
-
if [ -n "$gh_meta" ] && command -v jq >/dev/null 2>&1; then
|
|
143
|
-
# .web + .api + .git are the egress-relevant range groups.
|
|
144
|
-
while read -r cidr; do
|
|
145
|
-
[ -n "$cidr" ] && add_ip "$cidr"
|
|
146
|
-
done < <(echo "$gh_meta" | jq -r '(.web // []) + (.api // []) + (.git // []) | .[]' 2>/dev/null | grep -E '^[0-9]+\.' || true)
|
|
147
|
-
else
|
|
148
|
-
# Fallback: resolve the hostnames directly if the meta API is unavailable.
|
|
149
|
-
log " (meta API unavailable — falling back to direct resolution)"
|
|
150
|
-
for h in github.com api.github.com codeload.github.com objects.githubusercontent.com; do
|
|
151
|
-
while read -r ip; do add_ip "$ip"; done < <(dig +short +noall +answer A "$h" 2>/dev/null | grep -E '^[0-9]+\.' || true)
|
|
152
|
-
done
|
|
153
|
-
fi
|
|
154
|
-
|
|
155
|
-
# --- Everything else: resolve by name -----------------------------------------
|
|
156
|
-
for host in "${ALLOWED_HOSTS[@]}"; do
|
|
157
|
-
log " + $host"
|
|
158
|
-
while read -r ip; do
|
|
159
|
-
add_ip "$ip"
|
|
160
|
-
done < <(dig +short +noall +answer A "$host" 2>/dev/null | grep -E '^[0-9]+\.' || true)
|
|
161
|
-
done
|
|
162
|
-
|
|
163
|
-
# --- Host networking: keep the container reachable from / to its Docker host --
|
|
164
|
-
# Allow the host gateway + the container's own /24 so SSH in, the dev server,
|
|
165
|
-
# and DNS-to-the-Docker-resolver keep working after we flip to DROP.
|
|
166
|
-
host_ip="$(ip route | awk '/default/ {print $3; exit}')"
|
|
167
|
-
if [ -n "${host_ip:-}" ]; then
|
|
168
|
-
host_net="$(echo "$host_ip" | sed 's/\.[0-9]*$/.0\/24/')"
|
|
169
|
-
log " + host network ${host_net}"
|
|
170
|
-
add_ip "$host_net"
|
|
171
|
-
fi
|
|
172
|
-
|
|
173
|
-
n="$(ipset list allowed-domains | grep -cE '^[0-9]+\.' || true)"
|
|
174
|
-
log "allow-list built: ${n} entries"
|
|
175
|
-
[ "$n" -gt 0 ] || die "allow-list is empty — refusing to flip to default-DROP (would sever everything)"
|
|
176
|
-
|
|
177
|
-
log "applying default-deny policy…"
|
|
178
|
-
|
|
179
|
-
# Flush prior rules so re-runs are idempotent.
|
|
180
|
-
iptables -F
|
|
181
|
-
iptables -X 2>/dev/null || true
|
|
182
|
-
|
|
183
|
-
# Loopback — always.
|
|
184
|
-
iptables -A INPUT -i lo -j ACCEPT
|
|
185
|
-
iptables -A OUTPUT -o lo -j ACCEPT
|
|
186
|
-
|
|
187
|
-
# Established/related return traffic.
|
|
188
|
-
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
189
|
-
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
190
|
-
|
|
191
|
-
# DNS — keep resolution working at runtime (long-running procs re-resolve).
|
|
192
|
-
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
193
|
-
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
194
|
-
|
|
195
|
-
# SSH — the Desktop/Remote-Control on-ramp (ADR 0031 §1).
|
|
196
|
-
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
|
|
197
|
-
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
|
|
198
|
-
|
|
199
|
-
# The actual allow-list: outbound only to resolved, vetted IPs.
|
|
200
|
-
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
|
|
201
|
-
|
|
202
|
-
# Default DROP for everything not explicitly allowed above.
|
|
203
|
-
iptables -P INPUT DROP
|
|
204
|
-
iptables -P FORWARD DROP
|
|
205
|
-
iptables -P OUTPUT DROP
|
|
206
|
-
|
|
207
|
-
log "verifying…"
|
|
208
|
-
|
|
209
|
-
# A blocked host MUST fail (proves the deny is real).
|
|
210
|
-
if curl -fsS --max-time 6 https://example.com >/dev/null 2>&1; then
|
|
211
|
-
die "verification FAILED: example.com is reachable but should be blocked"
|
|
212
|
-
fi
|
|
213
|
-
log " ✓ blocked host (example.com) is unreachable"
|
|
214
|
-
|
|
215
|
-
# An allow-listed host MUST succeed (proves we didn't sever everything).
|
|
216
|
-
if ! curl -fsS --max-time 10 https://api.github.com/zen >/dev/null 2>&1; then
|
|
217
|
-
die "verification FAILED: api.github.com is unreachable but should be allowed"
|
|
218
|
-
fi
|
|
219
|
-
log " ✓ allow-listed host (api.github.com) is reachable"
|
|
220
|
-
|
|
221
|
-
# Our own GDS API must be reachable (the CLI depends on it). Non-fatal warn:
|
|
222
|
-
# the host may be mid-deploy; the firewall correctness is already proven above.
|
|
223
|
-
# The instance's OWN first API host (task 1002757 — was a founder literal, so this
|
|
224
|
-
# probe reported a stranger's health as the container's connectivity).
|
|
225
|
-
GDS_PROBE_HOST="$(printf '%s\n' ${INSTANCE_HOSTS//,/ } | head -n 1)"
|
|
226
|
-
if [ -z "$GDS_PROBE_HOST" ]; then
|
|
227
|
-
log " · no instance API host resolved from the branding pack — skipping the GDS reachability probe"
|
|
228
|
-
elif curl -fsS --max-time 10 "https://$GDS_PROBE_HOST/healthz" 2>/dev/null | grep -q '^ok$'; then
|
|
229
|
-
log " ✓ GDS API ($GDS_PROBE_HOST) is reachable"
|
|
230
|
-
else
|
|
231
|
-
log " ! GDS API healthz did not return ok (host may be redeploying) — allow rule is in place"
|
|
232
|
-
fi
|
|
233
|
-
|
|
234
|
-
log "default-deny firewall active."
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# post-create.sh — first-run provisioning for the Example Dev Box.
|
|
3
|
-
#
|
|
4
|
-
# Runs once, as the `vscode` user, after the container is created and the repo is
|
|
5
|
-
# mounted at /workspace (devcontainer.json: postCreateCommand). This is where
|
|
6
|
-
# ADR 0031 §3's "fold setup-builder.sh into the container's first-run step"
|
|
7
|
-
# actually happens:
|
|
8
|
-
#
|
|
9
|
-
# 1. scripts/setup-builder.sh --no-auth --skip-keys
|
|
10
|
-
# → verifies the toolchain, runs `npm ci`, seeds .env.local from the
|
|
11
|
-
# (secret-free) example. The --no-auth flag skips the interactive
|
|
12
|
-
# GitHub Device Flow, which can't run in a non-interactive postCreate —
|
|
13
|
-
# the builder authenticates on first attach instead (see the banner).
|
|
14
|
-
# 2. pip install the pinned art-pipeline deps INTO the venv the image created.
|
|
15
|
-
# → setup-builder.sh doesn't touch Python deps; the container layer owns
|
|
16
|
-
# that, installing from the repo's manifest so it stays reproducible.
|
|
17
|
-
# 3. A full-stack verification (sharp loads, art libs import) so a green
|
|
18
|
-
# first-run means the box is genuinely ready, not just "built".
|
|
19
|
-
#
|
|
20
|
-
# No secrets are written here (ADR 0022): .env.local is copied from the example
|
|
21
|
-
# with blank placeholders; the per-builder Gemini key and GDS session arrive at
|
|
22
|
-
# runtime via the mounted ~/.config/otb volume and /builder-setup.
|
|
23
|
-
|
|
24
|
-
set -uo pipefail
|
|
25
|
-
|
|
26
|
-
cd /workspace 2>/dev/null || cd "$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
|
|
27
|
-
|
|
28
|
-
bold=$'\033[1m'; green=$'\033[32m'; yellow=$'\033[33m'; dim=$'\033[2m'; reset=$'\033[0m'
|
|
29
|
-
say() { echo "${bold}$*${reset}"; }
|
|
30
|
-
|
|
31
|
-
say "Example Dev Box — first-run provisioning"
|
|
32
|
-
echo
|
|
33
|
-
|
|
34
|
-
# git trusts the mounted workspace even though its owner differs from the
|
|
35
|
-
# container user (avoids "detected dubious ownership" on every git command).
|
|
36
|
-
git config --global --add safe.directory /workspace 2>/dev/null || true
|
|
37
|
-
|
|
38
|
-
# node_modules is a container-isolated named volume (see devcontainer.json). A
|
|
39
|
-
# fresh volume mounts root-owned, so hand it to the runtime user before the
|
|
40
|
-
# npm ci inside setup-builder.sh tries to populate it.
|
|
41
|
-
if [ -d node_modules ]; then
|
|
42
|
-
sudo chown vscode:vscode node_modules 2>/dev/null || true
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# --- 1. Bring up the firewall BEFORE installing dependencies ------------------
|
|
46
|
-
# postStartCommand also runs the firewall on every start, but that's AFTER this
|
|
47
|
-
# postCreate step — which would leave `npm ci` + `pip install` below running with
|
|
48
|
-
# unrestricted egress on first create, a window a malicious (post)install script
|
|
49
|
-
# could exfiltrate through. Apply default-deny up front so dependency installs
|
|
50
|
-
# happen behind the allow-list (npm + PyPI + GitHub are allow-listed, so they
|
|
51
|
-
# still work). Non-fatal: if it can't apply (e.g. a laptop without NET_ADMIN),
|
|
52
|
-
# postStart will report it; we don't block provisioning here.
|
|
53
|
-
say "1/4 Securing egress (default-deny firewall, before dependency installs)"
|
|
54
|
-
if sudo /usr/local/bin/otb-init-firewall.sh; then
|
|
55
|
-
echo " ${green}ok${reset} firewall active — installs run behind the allow-list"
|
|
56
|
-
else
|
|
57
|
-
echo " ${yellow}!!${reset} firewall did not apply (see above); dependency installs will run with open egress this once"
|
|
58
|
-
fi
|
|
59
|
-
echo
|
|
60
|
-
|
|
61
|
-
# --- 2. Fold in setup-builder.sh (toolchain + npm ci + .env.local) ------------
|
|
62
|
-
say "2/4 Toolchain + dependencies (scripts/setup-builder.sh --no-auth)"
|
|
63
|
-
if [ -x scripts/setup-builder.sh ]; then
|
|
64
|
-
if bash scripts/setup-builder.sh --no-auth --skip-keys; then
|
|
65
|
-
echo " ${green}ok${reset} setup-builder.sh completed"
|
|
66
|
-
else
|
|
67
|
-
echo " ${yellow}!!${reset} setup-builder.sh reported a problem — review the output above"
|
|
68
|
-
fi
|
|
69
|
-
else
|
|
70
|
-
echo " ${yellow}!!${reset} scripts/setup-builder.sh not found/executable — running npm ci directly"
|
|
71
|
-
npm ci --no-audit --no-fund || echo " ${yellow}!!${reset} npm ci failed"
|
|
72
|
-
fi
|
|
73
|
-
echo
|
|
74
|
-
|
|
75
|
-
# --- 2. Art pipeline Python deps into the venv --------------------------------
|
|
76
|
-
# Non-fatal: a box without art deps is still fully usable for game/GDS work.
|
|
77
|
-
# Retry a few times — on a macOS Docker Desktop bind mount, VirtioFS can
|
|
78
|
-
# transiently return EDEADLK ("Resource deadlock avoided") reading repo files;
|
|
79
|
-
# a retry (and a fresh container) usually clears it. The DO box (#598) has no
|
|
80
|
-
# bind mount, so this never bites there.
|
|
81
|
-
say "3/4 Art-pipeline Python deps (into ${OTB_VENV:-venv})"
|
|
82
|
-
if [ -f modules/art-pipeline/pipeline/requirements.txt ]; then
|
|
83
|
-
art_ok=0
|
|
84
|
-
for attempt in 1 2 3; do
|
|
85
|
-
if pip install --no-cache-dir -r modules/art-pipeline/pipeline/requirements.txt; then
|
|
86
|
-
echo " ${green}ok${reset} art deps installed"
|
|
87
|
-
art_ok=1
|
|
88
|
-
break
|
|
89
|
-
fi
|
|
90
|
-
echo " ${dim}--${reset} attempt ${attempt} failed; retrying…"
|
|
91
|
-
done
|
|
92
|
-
if [ "$art_ok" -eq 0 ]; then
|
|
93
|
-
echo " ${yellow}!!${reset} pip install failed after retries — the art pipeline won't run until resolved."
|
|
94
|
-
echo " On a macOS laptop this is usually Docker Desktop's VirtioFS (EDEADLK):"
|
|
95
|
-
echo " rebuild the container, or switch Docker Desktop → Settings → General →"
|
|
96
|
-
echo " file sharing to 'gRPC FUSE'. (The box is otherwise ready.)"
|
|
97
|
-
fi
|
|
98
|
-
else
|
|
99
|
-
echo " ${dim}--${reset} no modules/art-pipeline/pipeline/requirements.txt; skipping"
|
|
100
|
-
fi
|
|
101
|
-
echo
|
|
102
|
-
|
|
103
|
-
# --- 3. Full-stack verification -----------------------------------------------
|
|
104
|
-
say "4/4 Verifying the box is ready"
|
|
105
|
-
# sharp was verified here as "the native image dep". Task 1003207 removed it: its
|
|
106
|
-
# only consumers were the game-era snapshot renderers and an art-pipeline test,
|
|
107
|
-
# all of which left this core with the game. Nothing native remains to prove.
|
|
108
|
-
# The five art libs must import in the venv.
|
|
109
|
-
if python3 -c "import PIL, numpy, scipy, sklearn, certifi" >/dev/null 2>&1; then
|
|
110
|
-
echo " ${green}ok${reset} art libs import (Pillow, numpy, scipy, scikit-learn, certifi)"
|
|
111
|
-
else
|
|
112
|
-
echo " ${yellow}!!${reset} one or more art libs failed to import"
|
|
113
|
-
fi
|
|
114
|
-
echo
|
|
115
|
-
|
|
116
|
-
# --- Claude Code default permission mode (task 1251) --------------------------
|
|
117
|
-
# Match the host (infra/box-source-fetch.sh): default this container's interactive
|
|
118
|
-
# Claude Code to bypassPermissions so `claude` never stops for a prompt on the
|
|
119
|
-
# (isolated, often unattended) dev box. USER-level (~/.claude/settings.json — the
|
|
120
|
-
# persisted otb-claude-config volume), NEVER the repo's project .claude/settings.json,
|
|
121
|
-
# so laptops / local clones are unaffected; it merges with the project settings
|
|
122
|
-
# (which set no defaultMode). Idempotent — only sets the one key, preserving the rest.
|
|
123
|
-
say "Defaulting Claude Code to bypassPermissions (box-only ~/.claude/settings.json)"
|
|
124
|
-
if mkdir -p "$HOME/.claude" 2>/dev/null && node -e '
|
|
125
|
-
const fs = require("fs"), p = process.argv[1];
|
|
126
|
-
let s = {}; try { s = JSON.parse(fs.readFileSync(p, "utf8")) || {}; } catch (e) {}
|
|
127
|
-
if (typeof s !== "object" || s === null || Array.isArray(s)) s = {};
|
|
128
|
-
s.permissions = (s.permissions && typeof s.permissions === "object") ? s.permissions : {};
|
|
129
|
-
s.permissions.defaultMode = "bypassPermissions";
|
|
130
|
-
fs.writeFileSync(p, JSON.stringify(s, null, 2) + "\n");
|
|
131
|
-
' "$HOME/.claude/settings.json" 2>/dev/null; then
|
|
132
|
-
echo " ${green}ok${reset} defaultMode=bypassPermissions written"
|
|
133
|
-
else
|
|
134
|
-
echo " ${yellow}!!${reset} could not write ~/.claude/settings.json (claude will prompt normally)"
|
|
135
|
-
fi
|
|
136
|
-
echo
|
|
137
|
-
|
|
138
|
-
say "${green}Box provisioned.${reset}"
|
|
139
|
-
cat <<'BANNER'
|
|
140
|
-
|
|
141
|
-
Next, on first connect:
|
|
142
|
-
1. Authenticate the GDS CLI: node scripts/gds/setup.js (or /builder-setup)
|
|
143
|
-
2. See what you can claim: /builder-start
|
|
144
|
-
|
|
145
|
-
The pixel-art pipeline needs a Google AI Studio key in ~/.config/otb/env
|
|
146
|
-
(that path is a persisted volume, so it survives box rebuilds). Skip if you
|
|
147
|
-
are not running the art pipeline.
|
|
148
|
-
|
|
149
|
-
BANNER
|