[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s - #423

Closed
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill
Closed

[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s#423
bussyjd wants to merge 2 commits into
mainfrom
feature/cluster-bootstrap-skill

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

Summary

Scaffold a new cluster-bootstrap skill that lets obol-agent stand up a k3s cluster across one or more hosts (LAN or cloud) over SSH using k3sup, then prepares it for obol stack up. Topology (nodes, roles, labels) is persisted under $OBOL_CONFIG_DIR/cluster-bootstrap/ so the chart-side wiring can consume it later.

This is a scaffold PR: the bootstrap flows are wired up and the multi-node design is decided, but the chart changes that consume the bootstrap output are intentionally deferred (see "Out of scope" below). Don't run against a production cluster yet.

What's in

  • internal/embed/skills/cluster-bootstrap/SKILL.md — topology model (single-host / lan-multi / cloud-multi), subcommand reference, caveats.
  • internal/embed/skills/cluster-bootstrap/scripts/bootstrap.py — k3sup wrapper: single, server, join, kubeconfig-path, status, label. Persists topology to topology.json, keeps server token mode 0600, writes kubeconfig to the standard $OBOL_CONFIG_DIR/kubeconfig.yaml.
  • internal/embed/skills/cluster-bootstrap/references/multi-node-design.md — full design doc with rejected alternatives.

Locked decisions

Storage — Option A (single primary node). One node carries obol.org/storage=primary (server by default). Stateful Deployments will gain nodeAffinity to that label; --no-storage-primary opts out. State is single-node, recoverable from PVC backup. Rejected: Longhorn/OpenEBS (≥3 nodes, ~500MiB/node baseline) and NFS dual-StorageClass (SPOF + fsync semantics break SQLite/BoltDB workloads).

Cloudflared — Shape 2 (pools list). The chart will render one Deployment per cloudflared.pools entry; each pool has its own replicas, nodeSelector, and Cloudflare credentials, with hostname PodAntiAffinity capping at one replica per node within a pool. bootstrap.py server|join --cloudflared-pool <name> records obol.org/cloudflared-pool=<name> (default default). Rejected: DaemonSet (can't run >1 tunnel per beefy node), single-Deployment+replicas (no edge-vs-cloud credential separation).

Out of scope (next ticket)

  • local-path.yaml chart honoring storage.primaryLabel
  • Stateful Deployments adding nodeAffinity to the primary label
  • cloudflared chart rewriting the single Deployment as a range over pools (with hostname-antiaffinity invariant + NOTES.txt warning when replicas > matching nodes)
  • obol stack up consuming topology.json to feed those values
  • End-to-end multi-node smoke test

Test plan

  • go build ./... clean
  • go test ./internal/embed/... passes (CRD/RBAC validation unchanged)
  • bootstrap.py --help, bootstrap.py server --help, bootstrap.py join --help parse cleanly
  • bootstrap.py status on a fresh state returns "no nodes recorded"
  • Live single-host install on a Linux box (deferred, requires non-Mac target)
  • LAN server + agent join (deferred, requires multi-node test bed)

Notes

  • Branch name is feature/cluster-bootstrap-skill per request; project convention elsewhere is feat/. Happy to rename if preferred.
  • Does not touch any production infra templates (local-path.yaml, cloudflared chart). All changes live under internal/embed/skills/cluster-bootstrap/.

Generated by Claude Code

@bussyjdbussyjd changed the title feat(skills): scaffold cluster-bootstrap for LAN/cloud k3s[DIRTY] feat(skills): scaffold cluster-bootstrap for LAN/cloud k3sMay 5, 2026
claude added 2 commits May 12, 2026 17:35
Wraps k3sup over SSH for single-host install, server install, and agent
join. Persists topology to $OBOL_CONFIG_DIR/cluster-bootstrap/ and the
k3s admin kubeconfig to the standard location so `obol stack up` can
take over once multi-node infra concerns are resolved.
Multi-node storage placement and cloudflared HA are intentionally
deferred — the design tradeoffs are captured in
references/multi-node-design.md and the skill exposes flag stubs
(--storage-primary, --edge-node) so the CLI surface won't churn once
we pick paths.
Storage: single primary node (Option A). One node carries
obol.org/storage=primary; stateful Deployments will gain nodeAffinity to
that label. Bootstrap records the label on the server by default;
--no-storage-primary opts out.
Cloudflared: pools list (Shape 2). The chart will render one Deployment
per cloudflared.pools entry, each with its own replicas, nodeSelector,
and credentials, plus hostname PodAntiAffinity to keep one replica per
node within a pool. Bootstrap records obol.org/cloudflared-pool on each
node so the chart-rewrite ticket can read topology.json.
Rejected paths and rationale captured in references/multi-node-design.md.
The actual chart-side changes (local-path nodeAffinity, cloudflared range
over pools) remain a separate ticket.
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Superseded — the cluster-bootstrap-skill scaffold landed directly on main via commits 9b32a5e (initial scaffold, 2026-05-04) and b33188e (multi-node lock, 2026-05-05) before this PR was opened. The branch lags behind main with incremental doc tweaks but adds no new functionality. Closing to keep the open-PR list tidy; reopen if there's something on the branch worth salvaging that I missed.

@bussyjdbussyjd closed this May 14, 2026
@OisinKyne
OisinKyne deleted the feature/cluster-bootstrap-skill branch July 1, 2026 12:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bussyjd@claude