@bongos/core 1.17.3 → 1.18.0
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 +161 -91
- package/.devcontainer/healthcheck.sh +0 -0
- package/.devcontainer/init-firewall.sh +0 -0
- package/.devcontainer/post-create.sh +0 -0
- package/GOVERNANCE.md +1 -1
- package/README.md +26 -22
- package/bin/bongos.js +0 -0
- package/docker-entrypoint.sh +0 -0
- package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +94 -0
- package/docs/adr/0140-cloudbongos-standalone-reprovision.md +43 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +4 -345
- package/docs/api-reference.md +5 -17
- package/docs/file-map.md +7 -25
- package/docs/onboarding/diagrams/05-versioning.mmd +4 -6
- package/docs/onboarding/diagrams/assertions.json +5 -9
- package/modules/hall-ui/public/builders.js +37 -0
- package/modules/hall-ui/public/goals-page.js +98 -176
- package/modules/hall-ui/public/goals.css +183 -0
- package/modules/hall-ui/public/goals.html +35 -261
- package/modules/hall-ui/public/goals.js +7 -2
- package/modules/hall-ui/public/hall-widgets.js +12 -4
- package/modules/hall-ui/public/settings.css +355 -0
- package/modules/hall-ui/public/settings.html +86 -414
- package/modules/hall-ui/public/settings.js +96 -35
- package/modules/hall-ui/public/style.css +31 -0
- package/modules/hall-ui/public/watch.css +184 -0
- package/modules/hall-ui/public/watch.html +111 -285
- package/modules/hall-ui/public/watch.js +59 -21
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +83 -7
- package/public-landing/index.html +11 -76
- package/scripts/cron/style_guide_audit.sh +0 -0
- package/scripts/gds/api.js +0 -0
- package/scripts/gds/architect-audit.js +0 -0
- package/scripts/gds/audit-authorship.sh +0 -0
- package/scripts/gds/audit-replay.sh +0 -0
- package/scripts/gds/box.js +0 -0
- package/scripts/gds/capture.js +0 -0
- package/scripts/gds/conductor-dispatch.js +0 -0
- package/scripts/gds/coreB-carve-proof.sh +0 -0
- package/scripts/gds/db-backup-nightly.sh +0 -0
- package/scripts/gds/db-backup-offsite.sh +0 -0
- package/scripts/gds/doctor.js +0 -0
- package/scripts/gds/grade-smoke.js +0 -0
- package/scripts/gds/hooks/pre-push.js +0 -0
- package/scripts/gds/install-git-hooks.js +0 -0
- package/scripts/gds/main-audit.js +0 -0
- package/scripts/gds/oauth-secret.js +0 -0
- package/scripts/gds/onboarding-analyze.js +0 -0
- package/scripts/gds/onboarding-ship.js +0 -0
- package/scripts/gds/paste-token.js +0 -0
- package/scripts/gds/portability-smoke-art.sh +0 -0
- package/scripts/gds/portability-smoke-memory.sh +0 -0
- package/scripts/gds/provision.js +125 -4
- package/scripts/gds/push-main.js +0 -0
- package/scripts/gds/render-ideas.js +0 -0
- package/scripts/gds/security-baseline-check.sh +0 -0
- package/scripts/gds/session.js +0 -0
- package/scripts/gds/ship.js +11 -1
- package/scripts/gds/smoke-batch-claim.sh +0 -0
- package/scripts/gds/smoke-batch-release.sh +0 -0
- package/scripts/gds/smoke-box.sh +0 -0
- package/scripts/gds/smoke-claim-consistency.sh +0 -0
- package/scripts/gds/smoke-dependencies.sh +0 -0
- package/scripts/gds/smoke-write.sh +0 -0
- package/scripts/hall-preview/fixtures/access-requests.json +20 -1
- package/scripts/hall-preview/fixtures/audit-log.json +68 -1
- package/scripts/hall-preview/fixtures/blockers__detail.json +12 -0
- package/scripts/hall-preview/fixtures/box__me.json +5 -0
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +69 -1
- package/scripts/hall-preview/fixtures/builders__roster.json +48 -1
- package/scripts/hall-preview/fixtures/grades__by-builder.json +32 -1
- package/scripts/hall-preview/fixtures/inbox__detail.json +15 -0
- package/scripts/hall-preview/fixtures/me__art-key__own.json +6 -0
- package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +209 -0
- package/scripts/hall-preview/fixtures/me__render.json +5 -0
- package/scripts/hall-preview/fixtures/me__skill-prefs.json +143 -0
- package/scripts/hall-preview/fixtures/me__sound-prefs.json +67 -0
- package/scripts/hall-preview/fixtures/me__wandering.json +43 -0
- package/scripts/hall-preview/fixtures/override-requests.json +15 -1
- package/scripts/hall-preview/fixtures/security__docs.json +10 -1
- package/scripts/hall-preview/fixtures/security__reports.json +31 -1
- package/scripts/hall-preview/fixtures/sessions__search.json +9 -1
- package/scripts/hall-preview/server.js +18 -1
- package/scripts/migrate.sh +0 -0
- package/scripts/migrate_cost_ledger.sh +0 -0
- package/scripts/render-diagrams.sh +0 -0
- package/scripts/security/dep-audit.js +0 -0
- package/scripts/setup-builder.js +0 -0
- package/scripts/setup-builder.sh +0 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +0 -0
- package/src/module-api.js +20 -1
- package/tests/provision.mjs +69 -0
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.18.0",
|
|
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.
|
|
3
|
+
"version": "1.18.0",
|
|
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",
|
|
@@ -182,6 +182,25 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
182
182
|
}
|
|
183
183
|
.reassure.on{display:block;}
|
|
184
184
|
|
|
185
|
+
/* one-click deep link to GitHub's own "New repository" page with the name
|
|
186
|
+
pre-filled (greenfield only). No permissions — the visitor creates the empty
|
|
187
|
+
repo in GitHub's own UI; we just fill in the blanks. */
|
|
188
|
+
.ghbtn{
|
|
189
|
+
display:inline-flex;align-items:center;gap:9px;text-decoration:none;cursor:pointer;
|
|
190
|
+
background:#24292f;color:#fff;border:none;border-radius:999px;
|
|
191
|
+
font-family:var(--font-body);font-weight:700;font-size:15px;padding:12px 22px;
|
|
192
|
+
box-shadow:0 12px 26px -12px rgba(36,41,47,0.55);
|
|
193
|
+
transition:transform .12s ease, background .12s ease;
|
|
194
|
+
}
|
|
195
|
+
.ghbtn:hover{background:#1b1f24;transform:translateY(-1px);}
|
|
196
|
+
.ghbtn:active{transform:translateY(0) scale(.98);}
|
|
197
|
+
.ghbtn .gh{width:18px;height:18px;fill:currentColor;}
|
|
198
|
+
.ghcreate{display:none;margin-bottom:18px;}
|
|
199
|
+
.ghcreate.on{display:block;}
|
|
200
|
+
.ghcreate-note{font-size:13px;font-weight:500;color:var(--ink-faint);line-height:1.5;margin-top:9px;max-width:52ch;}
|
|
201
|
+
.ghcreate-note strong{color:var(--ink-soft);}
|
|
202
|
+
.steps .ghbtn{font-size:14px;padding:9px 16px;margin-top:10px;}
|
|
203
|
+
|
|
185
204
|
/* buttons */
|
|
186
205
|
.nav{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:26px;}
|
|
187
206
|
.btn{
|
|
@@ -394,6 +413,13 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
394
413
|
<input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
|
|
395
414
|
</label>
|
|
396
415
|
</div>
|
|
416
|
+
<div class="ghcreate" id="ghCreate">
|
|
417
|
+
<a class="ghbtn" id="ghCreateBtn" href="https://github.com/new" target="_blank" rel="noopener">
|
|
418
|
+
<svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
419
|
+
Create this repo on GitHub
|
|
420
|
+
</a>
|
|
421
|
+
<p class="ghcreate-note">Opens GitHub with the name already filled in — just click <strong>Create repository</strong> and leave it empty, then come back and press Next. Already made it? Just press Next.</p>
|
|
422
|
+
</div>
|
|
397
423
|
<div class="hint" id="h-home"> </div>
|
|
398
424
|
<div class="nav">
|
|
399
425
|
<button type="button" class="btn ghost" data-back="1">← Back</button>
|
|
@@ -470,7 +496,7 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
470
496
|
/* ── state ── */
|
|
471
497
|
var DRAFT_KEY = 'cb-create-draft';
|
|
472
498
|
var CREATED_KEY = 'cb-created-instance';
|
|
473
|
-
var state = { step: 0, name: '', mode: 'greenfield', owner: '', repo: '', slug: '', slugTouched: false, domain: '' };
|
|
499
|
+
var state = { step: 0, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
|
|
474
500
|
try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
|
|
475
501
|
var me = null;
|
|
476
502
|
|
|
@@ -483,6 +509,7 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
483
509
|
if (handedName && handedName.trim()) {
|
|
484
510
|
state.name = handedName.trim().slice(0, 80);
|
|
485
511
|
state.slug = deriveSlug(state.name); state.slugTouched = false; // (hoisted declaration)
|
|
512
|
+
state.repo = deriveRepo(state.name); state.repoTouched = false; // auto-repo from the name
|
|
486
513
|
state.step = state.step >= 1 && state.step <= 4 ? state.step : 1;
|
|
487
514
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
488
515
|
history.replaceState(null, '', location.pathname);
|
|
@@ -507,14 +534,19 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
507
534
|
var v = String(s || '');
|
|
508
535
|
return v.length >= 2 && v.length <= 40 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(v) && v.indexOf('--') === -1;
|
|
509
536
|
}
|
|
510
|
-
function
|
|
537
|
+
function slugify(name, maxLen) {
|
|
511
538
|
return String(name || '').toLowerCase()
|
|
512
539
|
.replace(/['’]/g, '')
|
|
513
540
|
.replace(/[^a-z0-9]+/g, '-')
|
|
514
541
|
.replace(/-+/g, '-')
|
|
515
542
|
.replace(/^-|-$/g, '')
|
|
516
|
-
.slice(0,
|
|
543
|
+
.slice(0, maxLen);
|
|
517
544
|
}
|
|
545
|
+
function deriveSlug(name) { return slugify(name, 40); }
|
|
546
|
+
/* Repo-name default from the project name — the same slug-safe transform (GitHub
|
|
547
|
+
repo names accept [A-Za-z0-9._-], so a lowercase-dashed name is always valid and
|
|
548
|
+
matches the placeholder). Wider cap than the handle; the visitor can still edit. */
|
|
549
|
+
function deriveRepo(name) { return slugify(name, 100); }
|
|
518
550
|
function looksLikeDomain(d) {
|
|
519
551
|
return /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/i.test(String(d || ''));
|
|
520
552
|
}
|
|
@@ -524,6 +556,20 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
524
556
|
var origin = 'https://' + domain;
|
|
525
557
|
return { homepageUrl: origin, callbackUrl: origin + '/api/gds/auth/web/callback' };
|
|
526
558
|
}
|
|
559
|
+
/* GitHub's own "New repository" page, pre-filled with the owner + name. No auth or
|
|
560
|
+
permissions on our side — the visitor creates the empty repo in GitHub's UI, which
|
|
561
|
+
is exactly what the scaffold then clones. `owner` selects the account/org; omitting
|
|
562
|
+
a blank param just leaves that field empty on GitHub. */
|
|
563
|
+
function githubNewRepoUrl(owner, repo) {
|
|
564
|
+
var u = new URL('https://github.com/new');
|
|
565
|
+
if (repo) u.searchParams.set('name', repo);
|
|
566
|
+
if (owner) u.searchParams.set('owner', owner);
|
|
567
|
+
return u.toString();
|
|
568
|
+
}
|
|
569
|
+
function updateGhCreateHref() {
|
|
570
|
+
var a = $('ghCreateBtn');
|
|
571
|
+
if (a) a.href = githubNewRepoUrl(state.owner, state.repo);
|
|
572
|
+
}
|
|
527
573
|
function esc(s) {
|
|
528
574
|
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
529
575
|
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
@@ -559,12 +605,25 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
559
605
|
$('f-slug').value = state.slug;
|
|
560
606
|
$('f-domain').value = state.domain;
|
|
561
607
|
|
|
608
|
+
/* Auto-fill the repo name from the project name until the visitor edits it
|
|
609
|
+
directly (mirrors the handle's auto-derive). The owner is filled once we know
|
|
610
|
+
who's signed in — see the /me boot below. */
|
|
611
|
+
if (!state.repoTouched && state.name && !state.repo) {
|
|
612
|
+
state.repo = deriveRepo(state.name);
|
|
613
|
+
$('f-repo').value = state.repo;
|
|
614
|
+
}
|
|
615
|
+
|
|
562
616
|
$('f-name').addEventListener('input', function () {
|
|
563
617
|
state.name = this.value;
|
|
564
618
|
if (!state.slugTouched) {
|
|
565
619
|
state.slug = deriveSlug(state.name);
|
|
566
620
|
$('f-slug').value = state.slug;
|
|
567
621
|
}
|
|
622
|
+
if (!state.repoTouched) {
|
|
623
|
+
state.repo = deriveRepo(state.name);
|
|
624
|
+
$('f-repo').value = state.repo;
|
|
625
|
+
updateGhCreateHref();
|
|
626
|
+
}
|
|
568
627
|
var h = $('h-name');
|
|
569
628
|
if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
|
|
570
629
|
else { h.innerHTML = ' '; }
|
|
@@ -577,14 +636,18 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
577
636
|
$('c-green').setAttribute('aria-pressed', String(mode === 'greenfield'));
|
|
578
637
|
$('c-adopt').setAttribute('aria-pressed', String(mode === 'adopt'));
|
|
579
638
|
$('adoptNote').classList.toggle('on', mode === 'adopt');
|
|
639
|
+
// The "create the repo on GitHub" button only makes sense when starting fresh —
|
|
640
|
+
// an adopted repo already exists.
|
|
641
|
+
$('ghCreate').classList.toggle('on', mode === 'greenfield');
|
|
642
|
+
updateGhCreateHref();
|
|
580
643
|
saveDraft();
|
|
581
644
|
}
|
|
582
645
|
$('c-green').addEventListener('click', function () { setMode('greenfield'); });
|
|
583
646
|
$('c-adopt').addEventListener('click', function () { setMode('adopt'); });
|
|
584
647
|
setMode(state.mode);
|
|
585
648
|
|
|
586
|
-
$('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); saveDraft(); });
|
|
587
|
-
$('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); saveDraft(); });
|
|
649
|
+
$('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; updateGhCreateHref(); saveDraft(); });
|
|
650
|
+
$('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; updateGhCreateHref(); saveDraft(); });
|
|
588
651
|
|
|
589
652
|
$('f-slug').addEventListener('input', function () {
|
|
590
653
|
state.slugTouched = true;
|
|
@@ -719,9 +782,12 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
719
782
|
|
|
720
783
|
var steps = [];
|
|
721
784
|
var repoLabel = state.owner && state.repo ? '<strong>' + esc(state.owner + '/' + state.repo) + '</strong>' : 'your new repository';
|
|
785
|
+
var ghNewUrl = githubNewRepoUrl(state.owner, state.repo);
|
|
786
|
+
var ghIcon = '<svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>';
|
|
722
787
|
steps.push(adopt
|
|
723
788
|
? '<li data-onboard-step="github-repo"><b>Layer onto your existing repo</b><p>In your repo run <code style="font-family:var(--font-mono)">bongos init --adopt</code>. It shows a conflict pre-flight and writes <em>nothing</em> until you accept — your files and history stay yours, and your existing backlog is imported. Commit and push.</p></li>'
|
|
724
|
-
: '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>
|
|
789
|
+
: '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
|
|
790
|
+
'<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + ghIcon + 'Create ' + repoLabel + ' on GitHub</a></li>');
|
|
725
791
|
if (!state.domain) {
|
|
726
792
|
steps.push('<li><b>Add a domain you own</b><p>Sign-in on your new instance needs a real domain. When you have one, come back here or ask your hall — the address gets wired in minutes.</p></li>');
|
|
727
793
|
}
|
|
@@ -829,8 +895,10 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
829
895
|
again.addEventListener('click', function () {
|
|
830
896
|
stopPolling();
|
|
831
897
|
try { sessionStorage.removeItem(CREATED_KEY); sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
|
|
832
|
-
state = { step: 1, name: '', mode: 'greenfield', owner: '', repo: '', slug: '', slugTouched: false, domain: '' };
|
|
898
|
+
state = { step: 1, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
|
|
833
899
|
$('f-name').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
|
|
900
|
+
// Re-seed the owner from the signed-in account for the next project.
|
|
901
|
+
if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
|
|
834
902
|
$('h-name').innerHTML = ' ';
|
|
835
903
|
setMode('greenfield');
|
|
836
904
|
show(1);
|
|
@@ -884,6 +952,14 @@ input[type=text].bad{border-color:var(--err);}
|
|
|
884
952
|
.then(function (data) {
|
|
885
953
|
me = data.builder || data.me || data;
|
|
886
954
|
renderWho();
|
|
955
|
+
// Default the GitHub owner to the signed-in account until the visitor edits
|
|
956
|
+
// it — one less field to fill for the common "my own account" case.
|
|
957
|
+
if (!state.ownerTouched && !state.owner && me && me.github_login) {
|
|
958
|
+
state.owner = me.github_login;
|
|
959
|
+
$('f-owner').value = state.owner;
|
|
960
|
+
updateGhCreateHref();
|
|
961
|
+
saveDraft();
|
|
962
|
+
}
|
|
887
963
|
// returning after a create? resume watching that instance instead of a blank wizard
|
|
888
964
|
var created = null;
|
|
889
965
|
try { created = JSON.parse(sessionStorage.getItem(CREATED_KEY) || 'null'); } catch (e) {}
|
|
@@ -45,20 +45,17 @@ body{
|
|
|
45
45
|
.stage{
|
|
46
46
|
position:relative;z-index:1;min-height:100vh;
|
|
47
47
|
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
48
|
-
text-align:center;padding:48px 24px
|
|
48
|
+
text-align:center;padding:48px 24px 64px;gap:8px;
|
|
49
49
|
}
|
|
50
50
|
/* a soft white glow behind the mascot so a white- or transparent-bg PNG blends */
|
|
51
51
|
.hero-mascot{
|
|
52
52
|
position:relative;width:min(540px,86vw);margin-bottom:6px;
|
|
53
|
-
/* cap by height on short viewports so the hall CTA + sign-in hint always clear
|
|
54
|
-
the fixed footer (task 2094); on tall desktops the width cap wins, unchanged */
|
|
55
|
-
max-height:50vh;display:flex;justify-content:center;
|
|
56
53
|
}
|
|
57
54
|
.hero-mascot::before{
|
|
58
55
|
content:"";position:absolute;inset:-6% -10%;z-index:-1;border-radius:50%;
|
|
59
56
|
background:radial-gradient(ellipse at center, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.7) 45%, rgba(255,255,255,0) 72%);
|
|
60
57
|
}
|
|
61
|
-
.hero-mascot img{display:block;width:100%;height:auto;
|
|
58
|
+
.hero-mascot img{display:block;width:100%;height:auto;}
|
|
62
59
|
/* fallback shown if buddha.png is not present yet */
|
|
63
60
|
.mascot-fallback{
|
|
64
61
|
width:min(540px,86vw);aspect-ratio:1.6/1;border-radius:28px;
|
|
@@ -80,27 +77,9 @@ body{
|
|
|
80
77
|
border:none;border-radius:999px;padding:15px 32px;text-decoration:none;
|
|
81
78
|
display:inline-flex;align-items:center;gap:9px;
|
|
82
79
|
background:var(--accent);color:#fff;box-shadow:0 12px 28px -10px var(--accent);
|
|
83
|
-
transition:transform .12s ease, box-shadow .12s ease
|
|
80
|
+
transition:transform .12s ease, box-shadow .12s ease;
|
|
84
81
|
}
|
|
85
82
|
.btn:hover{transform:translateY(-1px);box-shadow:0 16px 34px -10px var(--accent);}
|
|
86
|
-
.btn.ghost{background:rgba(255,255,255,0.75);color:var(--ink);box-shadow:0 6px 18px -8px rgba(42,31,23,0.25);}
|
|
87
|
-
.btn.ghost:hover{box-shadow:0 10px 24px -8px rgba(42,31,23,0.3);}
|
|
88
|
-
/* signed-out: the create door is visible but resting — it hints, and clicking it
|
|
89
|
-
starts the GitHub sign-in (returning straight into /create). */
|
|
90
|
-
.btn.resting{background:#eeddc8;color:#a08a74;box-shadow:none;}
|
|
91
|
-
.btn.resting:hover{background:#ecd6ba;transform:none;box-shadow:none;}
|
|
92
|
-
.cta-row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;}
|
|
93
|
-
.signin-hint{font-size:13.5px;font-weight:600;color:var(--ink-soft);margin-top:14px;min-height:19px;}
|
|
94
|
-
.signin-hint a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
|
|
95
|
-
.signin-hint a:hover{text-decoration:underline;}
|
|
96
|
-
.who{
|
|
97
|
-
position:fixed;top:16px;right:20px;z-index:2;display:flex;align-items:center;gap:9px;
|
|
98
|
-
font-size:14px;font-weight:600;color:var(--ink-soft);min-height:30px;
|
|
99
|
-
}
|
|
100
|
-
.who img{width:28px;height:28px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 8px rgba(42,31,23,0.14);}
|
|
101
|
-
.who a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
|
|
102
|
-
.who a:hover{text-decoration:underline;}
|
|
103
|
-
.who .who-act{padding-left:10px;margin-left:2px;border-left:1px solid rgba(42,31,23,0.18);}
|
|
104
83
|
.foot{
|
|
105
84
|
position:fixed;bottom:18px;left:0;right:0;z-index:1;text-align:center;
|
|
106
85
|
color:var(--ink-soft);font-size:13px;opacity:0.8;
|
|
@@ -119,68 +98,24 @@ body{
|
|
|
119
98
|
</div>
|
|
120
99
|
<div class="wordmark" data-brand-product>cloud bongos</div>
|
|
121
100
|
<p class="tagline" data-brand-tagline>Building software is laying down a beat.</p>
|
|
122
|
-
<
|
|
123
|
-
<a class="btn" id="hallBtn" data-link-hall href="https://builders.cloudbongos.com">Enter the Hall →</a>
|
|
124
|
-
</div>
|
|
125
|
-
<p class="signin-hint" id="signinHint" aria-live="polite"></p>
|
|
101
|
+
<a class="btn" data-link-hall href="https://builders.cloudbongos.com">Enter the Hall →</a>
|
|
126
102
|
</main>
|
|
127
|
-
<div class="who" id="who" aria-live="polite"></div>
|
|
128
103
|
|
|
129
104
|
<div class="foot">A non-profit, capture-resistant commons. Built by agents, on Cloud Bongos.</div>
|
|
130
105
|
|
|
131
106
|
<script>
|
|
132
107
|
/* Brand-pack fill (R57). The server injects window.__BRANDING__ synchronously
|
|
133
|
-
ahead of this script (src/
|
|
108
|
+
ahead of this script (src/gds/serve-internal.js); the baked markup above is
|
|
134
109
|
the no-JS / standalone fallback. A differently-branded game-less instance
|
|
135
110
|
overrides the product name, tagline, and Hall link from its own config. */
|
|
136
111
|
(function () {
|
|
137
112
|
var b = (typeof window !== 'undefined' && window.__BRANDING__) || null;
|
|
138
|
-
if (b)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* Membership-aware hall access (goal 35, task 2094). This is a PROJECT's own
|
|
147
|
-
landing page. Signed in as an admitted builder here → "Enter the Hall".
|
|
148
|
-
Otherwise → "Request access" (this project's own hall shows the request-
|
|
149
|
-
access card), with a quiet "already a builder? sign in" for returning
|
|
150
|
-
builders. Sign-in is itself gated to approved builders (auth.js), so a
|
|
151
|
-
newcomer who tries it is routed to request access anyway. The identity +
|
|
152
|
-
action live top-right. Degrades to the request-access visual when the API
|
|
153
|
-
is unreachable (static preview / standalone viewing). */
|
|
154
|
-
var hallHref = (b && b.domains && b.domains.buildersOrigin) || 'https://builders.cloudbongos.com';
|
|
155
|
-
var signInHref = '/api/bongos/auth/web/start?return=' + encodeURIComponent('/');
|
|
156
|
-
function esc(s) {
|
|
157
|
-
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
158
|
-
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
var hallBtn = document.getElementById('hallBtn');
|
|
162
|
-
var signinHint = document.getElementById('signinHint');
|
|
163
|
-
var who = document.getElementById('who');
|
|
164
|
-
|
|
165
|
-
function showSignedOut() {
|
|
166
|
-
if (hallBtn) { hallBtn.textContent = 'Request access'; hallBtn.setAttribute('href', hallHref); }
|
|
167
|
-
if (signinHint) signinHint.innerHTML = 'Already a builder here? <a href="' + signInHref + '">Sign in with GitHub</a>.';
|
|
168
|
-
if (who) who.innerHTML = '<a href="' + signInHref + '">Sign in with GitHub</a>';
|
|
169
|
-
}
|
|
170
|
-
function showMember(me) {
|
|
171
|
-
if (hallBtn) { hallBtn.textContent = 'Enter the Hall →'; hallBtn.setAttribute('href', hallHref); }
|
|
172
|
-
if (signinHint) signinHint.textContent = '';
|
|
173
|
-
if (who) who.innerHTML =
|
|
174
|
-
(me.avatar_url ? '<img src="' + esc(me.avatar_url) + '" alt="">' : '') +
|
|
175
|
-
'<span>' + esc(me.github_login || 'signed in') + '</span>' +
|
|
176
|
-
'<a class="who-act" href="' + hallHref + '">Enter hall →</a>';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
showSignedOut(); // default until /me resolves (and the no-JS request-access intent)
|
|
180
|
-
fetch('/api/bongos/me', { credentials: 'same-origin' })
|
|
181
|
-
.then(function (res) { if (!res.ok) throw new Error(String(res.status)); return res.json(); })
|
|
182
|
-
.then(function (data) { showMember(data.builder || data.me || data); })
|
|
183
|
-
.catch(function () { /* signed out / not a member / unreachable: request-access visual stands */ });
|
|
113
|
+
if (!b) return;
|
|
114
|
+
var id = b.identity || {}, copy = b.copy || {}, dom = b.domains || {};
|
|
115
|
+
function set(sel, val){ if(!val) return; document.querySelectorAll(sel).forEach(function(n){ n.textContent = val; }); }
|
|
116
|
+
if (id.productName) set('[data-brand-product]', id.productName.toLowerCase());
|
|
117
|
+
if (copy.tagline) set('[data-brand-tagline]', copy.tagline);
|
|
118
|
+
if (dom.buildersOrigin) document.querySelectorAll('[data-link-hall]').forEach(function(n){ n.setAttribute('href', dom.buildersOrigin); });
|
|
184
119
|
})();
|
|
185
120
|
</script>
|
|
186
121
|
</body>
|
|
File without changes
|
package/scripts/gds/api.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/box.js
CHANGED
|
File without changes
|
package/scripts/gds/capture.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/doctor.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/provision.js
CHANGED
|
@@ -98,6 +98,11 @@ const CONFIG = {
|
|
|
98
98
|
// <slug>.caddy file (idempotent overwrite, no fragile append to a shared file);
|
|
99
99
|
// the main Caddyfile carries a one-time `import <dir>/*.caddy` (operator setup).
|
|
100
100
|
caddySnippetDir: process.env.PROVISION_CADDY_SNIPPET_DIR || '/etc/caddy/sites',
|
|
101
|
+
// Where nightly pg_dump backups land (db-backup-nightly.sh's GDS_BACKUP_DIR
|
|
102
|
+
// default). Shared across co-tenant instances on one box — dumps are named
|
|
103
|
+
// `<db>-<ts>.sql.gz`, so each instance's files + retention prune stay scoped to
|
|
104
|
+
// its own DB. Owned by the app user, mode 0750. (task 2213, productizes 2210.)
|
|
105
|
+
backupDir: process.env.PROVISION_BACKUP_DIR || '/var/backups/gds',
|
|
101
106
|
// Dedicated-droplet defaults (only used for hosting_shape='dedicated').
|
|
102
107
|
region: process.env.PROVISION_REGION || 'nyc3',
|
|
103
108
|
baseImage: process.env.PROVISION_BASE_IMAGE || 'ubuntu-24-04-x64',
|
|
@@ -252,6 +257,10 @@ function seedFirstVersionCmd(inst, { privileged = false } = {}) {
|
|
|
252
257
|
// It is the ADR 0108 DESTINATION model; these generators make provision.js drive the
|
|
253
258
|
// scripts/deploy/deploy-instance.sh runbook API-driven instead of by hand.
|
|
254
259
|
const CORE_PKG_DIR = 'node_modules/@bongos/core';
|
|
260
|
+
// The unix account the instance's Node process + its backup timer run as. Mirrors
|
|
261
|
+
// serviceUnit's hardcoded `User=lars`/`Group=lars` (the droplet's app user); kept as
|
|
262
|
+
// one constant so the backup dir owner + the backup unit's User= can't drift from it.
|
|
263
|
+
const APP_USER = 'lars';
|
|
255
264
|
function standaloneRoot(inst, { base = CONFIG.standaloneBase } = {}) { return path.posix.join(base, inst.slug); }
|
|
256
265
|
// Sync the instance's own repo (host content + the core pin). Mirrors deploy-instance.sh
|
|
257
266
|
// step 1 — assumes the repo is already cloned at standaloneRoot (initial clone is a
|
|
@@ -447,6 +456,80 @@ WantedBy=multi-user.target
|
|
|
447
456
|
}
|
|
448
457
|
function serviceUnitPath(inst) { return `/etc/systemd/system/${inst.slug}.service`; }
|
|
449
458
|
|
|
459
|
+
// ── nightly DB backup: per-instance timer + service (task 2213) ─────────────
|
|
460
|
+
// PRODUCTIZES the manual task-2210 fix: a newly-provisioned instance was born with
|
|
461
|
+
// NO backups (dir absent, GDS_BACKUP_DB unset → the script fell back to the
|
|
462
|
+
// nonexistent `amazonprimea` default, and the repo's global gds-db-backup units
|
|
463
|
+
// hardcode /home/lars/amazonprimea — wrong for the consumer node_modules/@bongos/
|
|
464
|
+
// core layout). These generators emit a PER-INSTANCE unit pair that fixes all three:
|
|
465
|
+
// • name → `<slug>-db-backup.{service,timer}` so co-tenant instances on one box
|
|
466
|
+
// never collide (each dumps its OWN db; the script's retention prune is
|
|
467
|
+
// already `<db>-*.sql.gz`-scoped, so a shared backupDir is safe).
|
|
468
|
+
// • DB → Environment=GDS_BACKUP_DB=<instance db>, set on the unit (not read from
|
|
469
|
+
// the web.env) so the dump targets the right database with no fallback.
|
|
470
|
+
// • path → ExecStart resolves the canonical script INSIDE the instance's pinned
|
|
471
|
+
// @bongos/core (standalone) or this control-plane checkout (co-tenant),
|
|
472
|
+
// the SAME workDir/CORE_PKG_DIR split serviceUnit uses — never the old
|
|
473
|
+
// monorepo path.
|
|
474
|
+
function backupUnitName(inst) { return `${inst.slug}-db-backup`; }
|
|
475
|
+
function backupServicePath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.service`; }
|
|
476
|
+
function backupTimerPath(inst) { return `/etc/systemd/system/${backupUnitName(inst)}.timer`; }
|
|
477
|
+
// The consumer-layout-correct absolute path to db-backup-nightly.sh. Standalone runs
|
|
478
|
+
// it from the instance's own pinned core; co-tenant/dedicated from this checkout's
|
|
479
|
+
// scripts/gds/ — mirrors serviceUnit's execStart resolution so they can't diverge.
|
|
480
|
+
function backupScriptPath(inst) {
|
|
481
|
+
const rel = 'scripts/gds/db-backup-nightly.sh';
|
|
482
|
+
return inst.hosting_shape === 'standalone'
|
|
483
|
+
? path.posix.join(standaloneRoot(inst), CORE_PKG_DIR, rel)
|
|
484
|
+
: path.posix.join(REPO_ROOT, rel);
|
|
485
|
+
}
|
|
486
|
+
function backupService(inst, { backupDir = CONFIG.backupDir } = {}) {
|
|
487
|
+
return `[Unit]
|
|
488
|
+
Description=Nightly pg_dump of the ${inst.slug} GDS DB (provisioned by provision.js, task 2213)
|
|
489
|
+
After=postgresql.service
|
|
490
|
+
Requires=postgresql.service
|
|
491
|
+
|
|
492
|
+
[Service]
|
|
493
|
+
Type=oneshot
|
|
494
|
+
User=${APP_USER}
|
|
495
|
+
Group=${APP_USER}
|
|
496
|
+
# The DB to dump + where dumps land — set on the unit (not via the instance web.env)
|
|
497
|
+
# so the dump targets the right database with no fallback to a stale default (root
|
|
498
|
+
# cause of the task-2210 breakage). db-backup-nightly.sh reads both as env.
|
|
499
|
+
Environment=GDS_BACKUP_DB=${dbName(inst)}
|
|
500
|
+
Environment=GDS_BACKUP_DIR=${backupDir}
|
|
501
|
+
# Consumer-layout path: the canonical script inside this instance's pinned core.
|
|
502
|
+
ExecStart=/bin/bash ${backupScriptPath(inst)}
|
|
503
|
+
# Modest hardening — the script only needs read on the pg socket + write on the
|
|
504
|
+
# backup dir. Mirrors the repo's gds-db-backup.service.
|
|
505
|
+
NoNewPrivileges=true
|
|
506
|
+
ProtectSystem=strict
|
|
507
|
+
ReadWritePaths=${backupDir}
|
|
508
|
+
ProtectHome=read-only
|
|
509
|
+
PrivateTmp=true
|
|
510
|
+
|
|
511
|
+
[Install]
|
|
512
|
+
# Not socket-activated; the timer drives this. The timer's WantedBy is what enables.
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
function backupTimer(inst) {
|
|
516
|
+
return `[Unit]
|
|
517
|
+
Description=Daily timer for the ${inst.slug} GDS DB pg_dump (provisioned by provision.js, task 2213)
|
|
518
|
+
|
|
519
|
+
[Timer]
|
|
520
|
+
# 04:00 UTC daily, mirroring the repo's gds-db-backup.timer (low-traffic window).
|
|
521
|
+
OnCalendar=*-*-* 04:00:00 UTC
|
|
522
|
+
# Catch up on next boot if the box was off when the timer was due.
|
|
523
|
+
Persistent=true
|
|
524
|
+
# Jitter so simultaneous timers don't dogpile the disk at exactly 04:00.
|
|
525
|
+
RandomizedDelaySec=300
|
|
526
|
+
Unit=${backupUnitName(inst)}.service
|
|
527
|
+
|
|
528
|
+
[Install]
|
|
529
|
+
WantedBy=timers.target
|
|
530
|
+
`;
|
|
531
|
+
}
|
|
532
|
+
|
|
450
533
|
// The branding pack a provisioned CO-TENANT instance loads: the shared, committed
|
|
451
534
|
// neutral starter (the co-tenant model shares ONE checkout, differentiated only by
|
|
452
535
|
// env — src/branding.js's GDS_BRANDING_FILE knob). Per-instance identity/domains
|
|
@@ -950,6 +1033,21 @@ async function provisionInstance(inst, deps) {
|
|
|
950
1033
|
boxExec(`${sudoP}systemctl enable --now ${inst.slug}`);
|
|
951
1034
|
} else { log(' (unit baked into cloud-init)'); }
|
|
952
1035
|
|
|
1036
|
+
// ── step 4b: nightly DB backup — dir + consumer-layout timer/service (task 2213) ──
|
|
1037
|
+
// Every instance must be born WITH backups (the task-2210 gap: cloudbongos was
|
|
1038
|
+
// provisioned without any). Create the app-user-owned backup dir, install the
|
|
1039
|
+
// per-instance timer/service (correct db + consumer-layout script path), enable
|
|
1040
|
+
// the timer. Idempotent: `install -d` no-ops on an existing dir, unit writes are
|
|
1041
|
+
// overwrites, `enable --now` is idempotent. Dedicated bakes the same into cloud-init.
|
|
1042
|
+
log(' [backup] create backup dir + install + enable nightly DB backup timer');
|
|
1043
|
+
if (local) {
|
|
1044
|
+
boxExec(`${sudoP}install -d -o ${APP_USER} -g ${APP_USER} -m 0750 ${CONFIG.backupDir}`);
|
|
1045
|
+
boxWriteFile(backupServicePath(inst), backupService(inst), { mode: 0o644, sudo: true });
|
|
1046
|
+
boxWriteFile(backupTimerPath(inst), backupTimer(inst), { mode: 0o644, sudo: true });
|
|
1047
|
+
boxExec(`${sudoP}systemctl daemon-reload`);
|
|
1048
|
+
boxExec(`${sudoP}systemctl enable --now ${backupUnitName(inst)}.timer`);
|
|
1049
|
+
} else { log(' (backup dir + units baked into cloud-init)'); }
|
|
1050
|
+
|
|
953
1051
|
// ── step 5: DNS — Cloudflare A-record UPSERT for the instance's domain (P4) ──
|
|
954
1052
|
// Formalized in P4 (#1945): the shared box-dns-cloudflare.sh hook UPSERTs an A
|
|
955
1053
|
// record pointing the domain at its target IP (recordIp), in the domain's own
|
|
@@ -1009,12 +1107,28 @@ async function provisionInstance(inst, deps) {
|
|
|
1009
1107
|
// SAME pure command generators so local + dedicated never diverge.
|
|
1010
1108
|
function dedicatedUserData(inst) {
|
|
1011
1109
|
const i = { ...inst, db_name: inst.db_name || inst.slug };
|
|
1110
|
+
// task 2213: a dedicated droplet gets the same nightly backup as local shapes —
|
|
1111
|
+
// write the per-instance unit pair (base64-encoded to sidestep YAML block-scalar
|
|
1112
|
+
// escaping of the multi-line bodies), create the app-user-owned backup dir, and
|
|
1113
|
+
// enable the timer alongside the web unit. Mirrors provisionInstance's step 4b.
|
|
1114
|
+
const svcB64 = Buffer.from(backupService(i), 'utf8').toString('base64');
|
|
1115
|
+
const timerB64 = Buffer.from(backupTimer(i), 'utf8').toString('base64');
|
|
1012
1116
|
return `#cloud-config
|
|
1117
|
+
write_files:
|
|
1118
|
+
- path: ${backupServicePath(i)}
|
|
1119
|
+
encoding: b64
|
|
1120
|
+
permissions: '0644'
|
|
1121
|
+
content: ${svcB64}
|
|
1122
|
+
- path: ${backupTimerPath(i)}
|
|
1123
|
+
encoding: b64
|
|
1124
|
+
permissions: '0644'
|
|
1125
|
+
content: ${timerB64}
|
|
1013
1126
|
runcmd:
|
|
1014
1127
|
- [ bash, -lc, "${dbCreateCmd(i)} || true" ]
|
|
1015
1128
|
- [ bash, -lc, "cd ${REPO_ROOT} && ${migrateCmd(i)}" ]
|
|
1016
1129
|
- [ bash, -lc, "${seedFirstVersionCmd(i)}" ]
|
|
1017
|
-
- [ bash, -lc, "
|
|
1130
|
+
- [ bash, -lc, "install -d -o ${APP_USER} -g ${APP_USER} -m 0750 ${CONFIG.backupDir}" ]
|
|
1131
|
+
- [ bash, -lc, "systemctl daemon-reload && systemctl enable --now ${i.slug} ${backupUnitName(i)}.timer" ]
|
|
1018
1132
|
`;
|
|
1019
1133
|
}
|
|
1020
1134
|
|
|
@@ -1034,10 +1148,16 @@ async function teardownInstance(inst, deps) {
|
|
|
1034
1148
|
if (inst.hosting_shape !== 'dedicated') {
|
|
1035
1149
|
boxExec(`${sudoP}systemctl disable --now ${inst.slug}`, { allowFail: true });
|
|
1036
1150
|
boxExec(`${sudoP}rm -f ${serviceUnitPath(inst)}`, { allowFail: true });
|
|
1151
|
+
// Stop + remove the nightly backup units too (task 2213). The backup DIR + its
|
|
1152
|
+
// dumps are left in place — like the DB below, they are precious data a teardown
|
|
1153
|
+
// must not silently destroy.
|
|
1154
|
+
boxExec(`${sudoP}systemctl disable --now ${backupUnitName(inst)}.timer`, { allowFail: true });
|
|
1155
|
+
boxExec(`${sudoP}rm -f ${backupServicePath(inst)} ${backupTimerPath(inst)}`, { allowFail: true });
|
|
1037
1156
|
boxExec(`${sudoP}systemctl daemon-reload`, { allowFail: true });
|
|
1038
|
-
// DB + Caddy block are left for an explicit operator (data is
|
|
1039
|
-
// teardown should not silently dropdb).
|
|
1040
|
-
|
|
1157
|
+
// DB + Caddy block + backup dumps are left for an explicit operator (data is
|
|
1158
|
+
// precious; a teardown should not silently dropdb or delete backups). Log the
|
|
1159
|
+
// manual follow-ups.
|
|
1160
|
+
log(` (left for the operator: dropdb ${dbName(inst)}, remove the Caddy block for ${inst.slug}, prune ${CONFIG.backupDir}/${dbName(inst)}-*.sql.gz)`);
|
|
1041
1161
|
} else if (apply && deps.doApi && inst.droplet_id) {
|
|
1042
1162
|
await deps.doApi.deleteDroplet(inst.droplet_id);
|
|
1043
1163
|
}
|
|
@@ -1396,6 +1516,7 @@ module.exports = {
|
|
|
1396
1516
|
CONFIG, MAX_INTENT_ATTEMPTS,
|
|
1397
1517
|
// pure step generators (tested directly)
|
|
1398
1518
|
dbName, dbCreateCmd, migrateCmd, seedFirstVersionCmd, serviceUnit, serviceUnitPath, webEnvBody, webEnvPath,
|
|
1519
|
+
backupUnitName, backupServicePath, backupTimerPath, backupScriptPath, backupService, backupTimer,
|
|
1399
1520
|
standaloneRoot, standalonePullCmd, standaloneInstallCmd, standaloneMigrateCmd,
|
|
1400
1521
|
// standalone scaffold (ADR 0108 / 0125, task 2055)
|
|
1401
1522
|
parseTargetRef, instanceRepoRemote, instanceInitSpec, scaffoldStandaloneRepo, coreVersionSafe,
|
package/scripts/gds/push-main.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/session.js
CHANGED
|
File without changes
|
package/scripts/gds/ship.js
CHANGED
|
@@ -84,7 +84,17 @@ const GRADER_TIMEOUT_MS = 240_000; // 4 min — Opus on a big diff with Read too
|
|
|
84
84
|
// feature worktree this script lives in. Falls back to two-levels-up if we
|
|
85
85
|
// can't ask git (e.g. fresh clone with no `main` worktree set up yet).
|
|
86
86
|
const REPO_ROOT = findMainWorktree(__dirname) || path.resolve(__dirname, '..', '..');
|
|
87
|
-
|
|
87
|
+
// Sound playback is COSMETIC — the ship pipeline must NEVER depend on it. Guard
|
|
88
|
+
// the require so a missing/broken player (e.g. .claude/sounds/ absent on a fresh
|
|
89
|
+
// clone or after a re-seed) degrades to a silent no-op instead of crashing ship.js
|
|
90
|
+
// at load. A cosmetic feature can't be allowed to break shipping (task 1002199).
|
|
91
|
+
const { playSound } = (() => {
|
|
92
|
+
try {
|
|
93
|
+
return require(path.resolve(__dirname, '../../.claude/sounds/play.js'));
|
|
94
|
+
} catch {
|
|
95
|
+
return { playSound: () => {} };
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
88
98
|
const DROPLET = 'lars@104.236.254.243';
|
|
89
99
|
const BEHIND_MAIN_LIMIT = 5;
|
|
90
100
|
|
|
File without changes
|
|
File without changes
|
package/scripts/gds/smoke-box.sh
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|