Skip to content

Add Kubernetes static-host publishing backend - #4

Merged
grinco merged 20 commits into
mainfrom
feat/kubernetes-backend
Jul 4, 2026
Merged

Add Kubernetes static-host publishing backend#4
grinco merged 20 commits into
mainfrom
feat/kubernetes-backend

Conversation

@grinco

@grinco grinco commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Stacked on #3 (base = feat/configurable-backend) so this diff shows only the Kubernetes work. Merge after #3.

What

Adds a third publishing backend, selected with PAGEDROP_BACKEND=kubernetes: a self-hosted PageDrop host service on Kubernetes with viewing behind the operator's SSO proxy. Best HTML/CSS/JS fidelity (raw serving, no iframe sandbox) and clean internal URLs; no native Docs/Slides copies or Drive search. Built to the spec/plan in docs/lld/2026-07-04-pagedrop-kubernetes-backend-{design,plan}.md.

Topology — dumb server + smart adapter (mirrors the Apps Script split):

MCP ──Bearer token──▶ :8081 /api/*  (write/list/search/update)  ┐
                                                                ├─ pagedrop-host pod (PVC /data)
viewer ─SSO proxy──▶ :8080 /p/<id>, /  (rendered + index)       ┘
  • Host service (src/host/, zero new deps — node:http/node:fs): two ports, atomic + exclusive-create storage on a PVC, path-traversal-guarded ids, crypto.timingSafeEqual bearer auth (fail-closed), HTML-escaped index page, update returns 404 (never creates), /healthz+/readyz.
  • Adapter (src/adapters/k8s/): KubernetesPublisher implements Publisher (wraps HTML via src/core, composes /p/<id> view URLs), HostClient transport, config loader; wired into createPublisher().
  • Packaging: Dockerfile (non-root, tsx) and a Helm chart (deploy/helm/pagedrop-host/) — Deployment replicas:1/Recreate over an RWO PVC, probes, Secret, two ingresses (SSO-fronted viewing + internal-only API), and a deny-by-default NetworkPolicy for the write API.

Reviews

Design was hardened via companion architectural review before implementation (Security model, atomic storage, deny-by-default network posture, escaping). Executed test-first, task-by-task, with per-unit verification and a final independent whole-branch review; its one Important finding (NetworkPolicy rendering allow-all with empty sources) and an actionable Minor (readiness probe checked nothing) are fixed and verified (5193f0b). A companion review of the full branch diff is also in flight.

Verification

  • npx tsc --noEmit clean; npm test88/88.
  • docker build + container /healthz smoke → OK.
  • helm lint clean; helm template confirms deny-by-default (no 8081 allow-rule with empty sources; rule appears when a source is set) and readiness on the disk-checking api /readyz.
  • The appsscript (default) and gcp backends are unchanged.

🤖 Generated with Claude Code

grinco added 19 commits July 4, 2026 12:15
Incorporate the companion review: explicit Security model section (trusted
token holders, SSO as sole viewer gate, content trusted-by-construction),
atomic exclusive-create storage with collision retry and crash-safe renames,
update returns 404 (no create), timingSafeEqual, /readyz vs /healthz,
index-page metadata escaping, NetworkPolicy + internal-only API ingress,
single-writer RWO constraint, and documented scale ceiling. Rejected the
CSP/content-sanitization finding on /p/:id as contrary to the fidelity goal;
deferred index-DB/pagination/metrics as non-goals.
… restrictable+safe-default view exposure, graceful shutdown)
@grinco

grinco commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

Updated with two fix rounds addressing the final whole-branch review and a companion architectural review:

  • 5193f0b — NetworkPolicy deny-by-default for the write API (empty sources no longer render allow-all); readiness probe moved to the disk-checking api /readyz.
  • a1201cd — view-port /readyz now also checks disk writability; NetworkPolicy can restrict the viewing port (8080) to configured sources (closing the in-cluster SSO-bypass gap), viewing ingress now defaults off (safe-by-default), and the host service handles SIGTERM/SIGINT for graceful shutdown.

Verification: 89/89 tests, tsc clean, helm lint/template confirm the deny-by-default and safe-default behaviors.

- Steer first-timers to the appsscript backend (no cloud account/billing/admin)
  and label gcp/kubernetes as advanced.
- Rewrite the Claude Code install block to default to Apps Script with no
  backend-choice friction, one-step-at-a-time plain-language guidance, and gcp
  only as an explicit opt-in escape hatch.
@grinco

grinco commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

Added an onboarding-clarity commit: the guided Claude Code install block now defaults to the Apps Script backend (no Google Cloud account needed) with no backend-choice friction for first-timers, and the Backends section steers newcomers to appsscript (gcp/kubernetes labelled advanced). Defaults verified: createPublisher falls back to appsscript, and .mcp.json.example ships PAGEDROP_BACKEND=appsscript.

@grinco
grinco changed the base branch from feat/configurable-backend to main July 4, 2026 11:34
@grinco
grinco merged commit ed9f3f2 into main Jul 4, 2026
2 checks passed
@grinco
grinco deleted the feat/kubernetes-backend branch July 4, 2026 11:34
grinco added a commit that referenced this pull request Jul 4, 2026
Add Kubernetes static-host publishing backend
Sign up for free to 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.

1 participant