Compare commits

...
Author SHA1 Message Date
issue-fix-bot 4c3300488a fix: [alert] CephPoolLostRedundancy (rook-ceph) (#968)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
2026-09-25 17:44:07 +00:00
k.renner cf362548f1 Merge pull request 'feat(wiki): API key "paperclip" for the Paperclip agents' (#972) from feat/wiki-api-key-paperclip into master
ci/woodpecker/cron/issue-fix Pipeline was successful
Reviewed-on: k.renner/homelab#972
2026-09-25 17:35:27 +00:00
claude-botandClaude Fable 5.1 f732528551 feat(wiki): API key "paperclip" for the Paperclip agents
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Second named key in apps/ai/wiki-api-keys (OpenBao version 4), mapped to
Auth__ApiKeys__paperclip; writes through the API are attributed to it.
Reloader rolls the pod when the Secret changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 19:33:38 +02:00
svc-argus (Paperclip service account) 890eab7dc3 Merge pull request 'test(access): remove the REN-3 branch protection probe file' (#970) from test/ren-3-access-cleanup into master
ci/woodpecker/cron/issue-fix Pipeline failed
ci/woodpecker/cron/alert-monitor Pipeline was successful
2026-09-25 13:17:37 +00:00
svc-atlasandPaperclip 5343b65562 test(access): remove the REN-3 branch protection probe file
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Leaves master's tree byte-identical to before the test.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-09-25 13:17:36 +00:00
svc-argus (Paperclip service account) f56391d88f Merge pull request 'test(access): REN-3 branch protection probe' (#969) from test/ren-3-access-check into master 2026-09-25 13:17:22 +00:00
svc-atlasandPaperclip d32bf7a4ab test(access): REN-3 branch protection probe
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-09-25 13:17:00 +00:00
k.renner e82303c314 Merge pull request 'feat(paperclip): disable sign-up; document the Claude connection' (#967) from feat/paperclip-signup-off into master
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#967
2026-09-25 11:57:08 +00:00
claude-botandClaude Opus 5.5 56e30bf065 fix(paperclip): agents use the Service name for the API
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
The cluster-internal name keeps working when runs move to sandbox pods
(option C), where loopback would not reach the server. The name is
added to PAPERCLIP_ALLOWED_HOSTNAMES because the private-hostname guard
otherwise returns 403 for it. Same-namespace traffic is already allowed
by the baseline CNP in both directions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:52:48 +02:00
claude-botandClaude Opus 5.5 161d0561e7 fix(paperclip): agents call the API on loopback, not through the gate
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was canceled
Upstream derives the agents' PAPERCLIP_API_URL from the public URL, so
agent API calls went out through Traefik to the paperclip-gate outpost.
With intercept_header_auth the outpost treats the agent's Authorization
header as an Authentik credential and returns 401; agents could not
create tasks, post cards or update status.

Agents run in the server pod (option B), so PAPERCLIP_API_URL is set to
http://localhost:3100 (upstream keeps an explicit value and passes it to
every run). Loopback passes the private-hostname guard and agent JWTs
still authenticate each call; the gate stays fully enforced for browser
traffic instead of carving out /api/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:51:43 +02:00
claude-botandClaude Opus 5.5 30931d3f50 feat(paperclip): disable sign-up; document the Claude connection
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was canceled
The first admin is claimed, so PAPERCLIP_AUTH_DISABLE_SIGN_UP flips to
true.

Spec: Claude auth is a Paperclip-managed subscription connection created
once via the local sign-in. The server-env CLAUDE_CODE_OAUTH_TOKEN was
never used (stripAiAuthBindings blanks it for managed runs; upstream
accepts no pasted setup-token), so it is dropped from the spec. The
login must run as the node user: exec sessions start as root and
root-owned 0600 credentials are unreadable to the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:48:29 +02:00
4 changed files with 37 additions and 16 deletions
@@ -16,7 +16,7 @@ credentials are out of scope (see "Deferred").
|---|---|
| Execution model | **B — server-local runs** (upstream default): agent CLIs are child processes of the server, in the server container. Trial only. |
| Upgrade path | If Paperclip is kept: **C** — `@paperclipai/plugin-kubernetes` sandbox provider + kubernetes-sigs agent-sandbox controller (`agents.x-k8s.io`), per-run Pods and Secrets. Separate spec. |
| Claude auth | **One shared subscription OAuth token** (`CLAUDE_CODE_OAUTH_TOKEN`, from `claude setup-token`) for all agents. |
| Claude auth | **One shared Claude subscription AI connection**, created once via Paperclip's local sign-in (`claude auth login` into `ai-local-logins/<id>`); Paperclip stores and refreshes it. Server-env `CLAUDE_CODE_OAUTH_TOKEN` is not used: `stripAiAuthBindings` blanks it for managed runs, and upstream accepts no pasted setup-token (the device-code flow needs a Daytona sandbox). |
| Bot credentials | Out of scope. Future "AIR-Manager" Paperclip role handles permission requests. |
| Exposure | LAN-only, behind an Authentik forward-auth gate, then Paperclip's own login. |
@@ -25,8 +25,8 @@ credentials are out of scope (see "Deferred").
Upstream `runChildProcess()` spawns every local adapter with
`{...sanitizeInheritedPaperclipEnv(process.env), ...opts.env}`; the sanitizer strips only
`PAPERCLIP_*` keys. Every agent therefore sees `DATABASE_URL`, `BETTER_AUTH_SECRET`,
`CLAUDE_CODE_OAUTH_TOKEN`, the shared filesystem (including every other agent's
workspace) and every agent-scoped secret it can read from disk. Under B, no bot may hold
the shared filesystem (including every other agent's workspace and the
`ai-local-logins` credentials) and every agent-scoped secret it can read from disk. Under B, no bot may hold
a credential we would not tolerate any other bot using. Per-bot isolation is C's job.
## Architecture
@@ -79,6 +79,11 @@ All modeled on `kubernetes/apps/paperless/` unless stated.
claim (`/bootstrap/claim`) requires an already signed-in Better Auth account.
A follow-up flips it to `true`.
- `PAPERCLIP_TELEMETRY_DISABLED=1` (read by `resolveTelemetryConfig`).
- `PAPERCLIP_API_URL=http://paperclip.paperclip.svc.cluster.local:3100` plus
`PAPERCLIP_ALLOWED_HOSTNAMES=paperclip.paperclip.svc.cluster.local`: upstream
otherwise hands agents the public URL, and the gate (`intercept_header_auth`)
rejects their bearer tokens. The Service name also works for sandbox pods under
option C; agent JWTs still authenticate each call.
- `envFrom: secretRef paperclip-secrets`
- Reloader: `secret.reloader.stakater.com/reload: paperclip-secrets`.
@@ -106,7 +111,6 @@ extract):
- `PAPERCLIP_TOOL_ACTION_SIGNING_SECRET`
- `PAPERCLIP_SECRETS_MASTER_KEY` (64-char hex). Held in OpenBao, not on the PVC, so
encrypted agent secrets in Postgres survive PVC loss.
- `CLAUDE_CODE_OAUTH_TOKEN` (shared subscription token)
### Ingress and SSO
- Hostname `paperclip.renner.uno`, Traefik `Ingress`, `cert-manager.io/cluster-issuer:
@@ -152,10 +156,15 @@ available since v0.35.0). First use of digest reflection in this repo.
- Nothing is granted under B that we would not tolerate any agent misusing.
## Bootstrap (one-time)
1. Place `apps/paperclip` values in OpenBao (generated secrets + shared OAuth token).
2. Merge; Flux deploys.
3. Claim the admin account via the `bootstrap-ceo` invite, add yourself to
"Paperclip Users" in Authentik.
1. Place `apps/paperclip` values in OpenBao (the three generated secrets).
2. Merge; Flux deploys with sign-up enabled.
3. Sign up through the gate and claim the instance as admin; then set
`PAPERCLIP_AUTH_DISABLE_SIGN_UP=true`.
4. In onboarding "Connect a model", choose Claude subscription and run the shown login
**as the node user** (the entrypoint makes exec sessions root, and root-owned 0600
credentials are unreadable to the server):
`kubectl exec -it -n paperclip deploy/paperclip -- gosu node sh -c 'export CLAUDE_CONFIG_DIR=<shown path> && mkdir -p "$CLAUDE_CONFIG_DIR" && claude auth login'`
Repeat after loss of the PVC or database.
## Verification
- Flux Kustomization `paperclip` Ready; CNPG `pclp-pg` healthy; first ScheduledBackup
@@ -163,7 +172,7 @@ available since v0.35.0). First use of digest reflection in this repo.
- `curl https://paperclip.renner.uno/api/health` through the gate returns healthy after
Authentik login; unauthenticated request redirects to `auth.renner.uno`.
- Direct pod access from another namespace is denied (CNP).
- A test agent (claude-local) completes a trivial task using the shared OAuth token.
- A test agent (claude-local) completes a trivial task using the shared Claude connection.
- Flux ImagePolicy reports a `nightly@sha256:` digest.
## Deferred
@@ -52,19 +52,23 @@ spec:
- alert: CephPoolLostRedundancy
# Names the pool, which POOL_NO_REDUNDANCY does not.
#
# There is no pool-size metric, but bytes_used/stored IS the
# replication factor. The >1e6 floor drops pools too small to measure
# meaningfully, empty ones included.
# There is no pool-size metric, but bytes_used/stored is the
# replication factor for replicated pools and (k+m)/k for EC pools.
# The EC 2+1 pools (cephfs-bulk, rgw buckets.data) sit at exactly
# 1.5 with ~1e-7 accounting drift either side, so the cut-off is
# midway between them and an unreplicated pool (1.0) (#968).
# The >1e6 floor drops pools too small to measure meaningfully,
# empty ones included.
#
# No pool is exempt.
expr: |
ceph_pool_bytes_used / ceph_pool_stored < 1.5 and ceph_pool_stored > 1e6
ceph_pool_bytes_used / ceph_pool_stored < 1.25 and ceph_pool_stored > 1e6
for: 30m
labels:
severity: critical
annotations:
summary: "Ceph pool {{ $labels.pool_id }} is running unreplicated"
description: "Pool {{ $labels.pool_id }} has a replication factor below 1.5, meaning a single OSD loss destroys data. No pool is meant to be unreplicated — an unreplicated pool also blocks ALL OSD maintenance cluster-wide, since `ceph osd ok-to-stop` refuses while its PGs would go offline. Check with `kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd pool ls detail`."
description: "Pool {{ $labels.pool_id }} has a raw/stored ratio below 1.25 (replicated size 1), meaning a single OSD loss destroys data. No pool is meant to be unreplicated — an unreplicated pool also blocks ALL OSD maintenance cluster-wide, since `ceph osd ok-to-stop` refuses while its PGs would go offline. Check with `kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd pool ls detail`."
- alert: CephHealthError
# HEALTH_ERR means data availability or redundancy is at risk.
+6 -2
View File
@@ -49,9 +49,13 @@ spec:
value: private
- name: PAPERCLIP_PUBLIC_URL
value: https://paperclip.renner.uno
# Sign-up stays open until the first admin is claimed; the gate restricts who reaches it.
# Agents reach the API via the Service, not through the Authentik gate.
- name: PAPERCLIP_API_URL
value: http://paperclip.paperclip.svc.cluster.local:3100
- name: PAPERCLIP_ALLOWED_HOSTNAMES
value: paperclip.paperclip.svc.cluster.local
- name: PAPERCLIP_AUTH_DISABLE_SIGN_UP
value: "false"
value: "true"
- name: PAPERCLIP_TELEMETRY_DISABLED
value: "1"
envFrom:
@@ -42,6 +42,10 @@ spec:
remoteRef:
key: apps/ai/wiki-api-keys
property: workstation
- secretKey: Auth__ApiKeys__paperclip
remoteRef:
key: apps/ai/wiki-api-keys
property: paperclip
---
# TypeSafe System One key for the judge adapter (Wiki:Judges:Models:typesafe).
apiVersion: external-secrets.io/v1