For Agents — this is a RESUME-HERE note, not a completed build

tsidp was fully staged to give Transmute true Tailscale-identity OIDC, but the idp node was never brought up — three attempts were defeated by Aperture MCP instability and the 06:20 crash. Nothing is live. Config and the binary survive; pick up from the checklist below only once the hardware is stable.

Status

Owner deferred tsidp tonight due to the recurring CPU fault (the box crashed at 06:20 under build load). No idp node exists. Transmute is untouched and healthy — guest access still works, ALLOW_UNAUTHENTICATED left true.

What survives (verified this session)

ItemState
Binary/home/levander/go/bin/tsidptailscale.com/cmd/tsidp@v1.102.2 (matches host tailscale 1.102.2), 41,952,675 bytes, built 06:18, go1.26.7. Verified it EXISTS and RUNS after the 06:20 crash.
State dir/home/levander/tsidp/state (chmod 700, empty)
Designembedded tsnet (own idp node, own :443) — cleanly avoids the previously-flagged :443 collision. Tailnet-only serve, no Funnel.

Do not chase the wrong binary path

The binary is at /home/levander/go/bin/tsidp, not /home/levander/tsidp/tsidp. A prior check of the wrong path concluded it was gone. RESUME: ls /home/levander/go/bin/tsidp FIRST and rebuild only if genuinely absent — a rebuild is a sustained compile, exactly the all-core load that crashes this box. If a rebuild is truly needed, pin it: taskset -c 0-15,17-23 (off the faulty core).

Key facts locked in (so they are not re-derived)

  • Runtime: env TAILSCALE_USE_WIP_CODE=1 + TS_AUTHKEY=<key> (tsnet reads TS_AUTHKEY from env). Flags: -hostname idp -dir /home/levander/tsidp/state -port 443.
  • Tagging caveat: cmd/tsidp v1.102.2 has NO -advertise-tags flag — a tsnet node’s tags come only from the auth key. The Aperture-minted key is not pre-tagged, so idp registers untagged/user-owned; tag:telep must be applied by the owner at the device-approval step, or mint a pre-tagged key out-of-band.
  • Client registration: POST https://idp.taild4189d.ts.net/clients/new (tailnet-only) with redirect_uri=... → returns client_id (32-hex) + client_secret (64-hex), persisted in oidc-funnel-clients.json under the state dir.
  • Transmute redirect URI (verified against the app): https://transmute.taild4189d.ts.net/api/oidc/callback.
  • Username claim (verified): this tsidp emits email (plus username/name/sub) — NOT preferred_username. Use OIDC_USERNAME_CLAIM=email. (The earlier Pocket-ID attempt’s preferred_username would have broken here.)

The container-reachability question is the real open risk

Transmute’s backend runs in a bridge container that cannot resolve tailnet MagicDNS (Docker DNS → host upstream, no *.ts.net), and host-originated connections to sidecar :443 timed out in testing. Transmute is built for a split: it fetches discovery/token/jwks/userinfo from OIDC_INTERNAL_URL and rewrites only the browser-facing authorization_endpoint to OIDC_ISSUER_URL. The moment idp is up, test host→idp:443 AND container→idp:443.

  • Works → OIDC_ISSUER_URL=https://idp.taild4189d.ts.net, leave OIDC_INTERNAL_URL empty/same, add compose extra_hosts: ["idp.taild4189d.ts.net:<idp-tailnet-IP>"] so the container resolves the name.
  • ACL-blocked → give the owner a one-line ACL grant telep-mainframe → idp. Do NOT enable Funnel as a workaround.
  • The -local-port/loopbackURL approach is a DEAD END — tsidp hardcodes http://localhost:<port> for localhost requests, breaking the internal/external URL rewrite.

Auth-key state

The single Aperture auth_keys:create:once authorization was consumed and a one-time key was minted, but never used (node never joined). Single-use, ~1h expiry → it expires harmlessly. Key was never written to disk. RESUME needs a FRESH owner auth click (mcp__aperture__Tailnet_provision_node) — Aperture reconnects reset the flow 3× tonight, so collect the URL in one tight window and relay immediately.

Resume checklist (in order, once hardware is stable)

  1. ls /home/levander/go/bin/tsidp — rebuild only if absent (pinned taskset -c 0-15,17-23).
  2. Fresh owner auth click → collect the key (one call, no polling — a second call invalidates the pending link).
  3. Write /home/levander/tsidp/tsidp.env (chmod 600): TAILSCALE_USE_WIP_CODE=1 + TS_AUTHKEY=<key>. Install /etc/systemd/system/tsidp.service (unit below), daemon-reload, enable --now.
  4. Report the device-approval click-path; owner approves and applies tag:telep. Verify https://idp.taild4189d.ts.net/.well-known/openid-configuration returns valid discovery, no Funnel.
  5. POST .../clients/new with redirect_uri=https://transmute.taild4189d.ts.net/api/oidc/callback → capture client_id/client_secret (store chmod-600, never print).
  6. Test host→idp / container→idp reachability; apply extra_hosts (or the ACL grant) per the callout above.
  7. Re-read /home/levander/transmute/docker-compose.yml FRESH (preserve the 127.0.0.2:3313 bind from the hardening sweep AND cpuset: "0-7,10-23"). Back up. Add: OIDC_ISSUER_URL, OIDC_INTERNAL_URL (per reachability), OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_USERNAME_CLAIM=email, OIDC_AUTO_LAUNCH=true, OIDC_AUTO_CREATE_USERS=true, ALLOW_UNAUTHENTICATED=false, plus extra_hosts. Recreate ONLY the transmute container.
  8. Prove: Transmute 302s to idp.taild4189d.ts.net (not its own form). Full round-trip (log in as Tailscale identity → land in workspace) is owner-verify.

The unit

[Unit]
Description=tsidp (Tailscale OIDC identity provider, idp node)
After=network-online.target
Wants=network-online.target
[Service]
User=levander
WorkingDirectory=/home/levander/tsidp
EnvironmentFile=/home/levander/tsidp/tsidp.env
ExecStart=/home/levander/go/bin/tsidp -hostname idp -dir /home/levander/tsidp/state -port 443
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

Transmute OIDC env names (verified against its pydantic settings)

OIDC_ISSUER_URL, OIDC_INTERNAL_URL (backend→provider; falls back to issuer), OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_USERNAME_CLAIM, OIDC_AUTO_CREATE_USERS, OIDC_AUTO_LAUNCH, ALLOW_UNAUTHENTICATED.