53 Commits
Author SHA1 Message Date
Evan JarrettandClaude Opus 5 7d9de7c090 admin: resolve handles after limiting top users
The dashboard's top-users panel resolved a handle for every user with a
quota record, then sorted and truncated to ten. On a hold with ~500 crew
that is ~500 serial identity lookups to render ten rows.

Each lookup goes through the shared identity directory, whose HTTP client
allows 10s per request. One stalled lookup consumed the entire reverse
proxy budget, so the panel returned a partial body and the client hung up
mid-render:

  admin/auth.go:161 "Failed to render template"
    template=partials/top_users.html
    error="write: broken pipe"
  "GET /admin/api/top-users?limit=10" - 200 4096B in 10.005s

Sort and truncate first, then resolve only the surviving rows, so the
count is bounded by the limit rather than by hold size. Resolve those
concurrently under a 3s deadline: a slow lookup now degrades to a bare
DID instead of taking the whole request down with it.

The crew tab has the same underlying problem in a different shape, one
lazy-load request per row, and is not addressed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 20:22:46 -05:00
Evan JarrettandClaude Opus 4.8 3f21cc98b8 hold/gc: sweep orphaned scan and image config records
A user who deletes a manifest record directly on their PDS was invisible
to the hold. purgeManifest only fires on appview-driven deletes, and the
GC orphan sweep only walked io.atcr.hold.layer, so scan and image config
records survived forever and the user's storage number never dropped.

analyzeRecords now also walks io.atcr.hold.scan and
io.atcr.hold.image.config, applying the same orphan test the layer sweep
uses: a record dies only when its owning PDS was reachable and
demonstrably lacks the manifest. Unreachable PDS, unparseable URI,
unparseable timestamp, and immature records all keep.

Splitting the grace period was required to make this useful, not
cosmetic. Records only need to outlast a push (blobs and layer records
are written before the manifest reaches the user's PDS), so they age out
at 24h. Blobs stay on the 7 day window, but since records now age out
faster than the blobs they name, a record can no longer serve as its
blob's clock. Blob age comes from S3 LastModified instead, which
WalkBlobs already received from ListObjectsV2 and was discarding.

Net effect: pruning an old manifest frees the user's storage on the next
nightly run rather than never, while the bytes are still reclaimed on
the same best-effort schedule as before.

DeleteManifestAuxRecord is deliberately narrow, accepting only the scan
and image config collections, so a bug in the sweep can't reach captain,
crew, or layer records.

Not addressed: the SBOM and vuln report blobs those scan records point
at. They share the /repos/{holdDID}/blobs/ prefix with the hold's avatar
and OG images, so collecting them needs a referenced-CID set built from
the hold's own records rather than a prefix walk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-08 21:24:00 -05:00
Evan Jarrett 6758996300 add SBOM package diffing, verify hold-service captain records
- diff view gains a Packages tab with added/removed/changed/unchanged
  package tables and purl-derived type/license/upstream links
- captain records verified against the DID's atcr_hold service before
  caching (processor + batch backfill), preventing forged holds
- fix empty-handle updates clobbering cached handles and colliding on
  the UNIQUE constraint
- move fillPrevCIDs into repo.go; DirectRepoOperator is now canonical,
  repomgr kept as a test oracle
- surface read-only crew status in hold selector
- reconcile docs
2026-06-13 12:49:03 -05:00
Evan Jarrett ab4a4ebf9d admin panel long running imrovements, billing fixes, ui cleanup
1. Multiple registry domains + per-user domain preference

The biggest feature. The appview can serve several registry domains (e.g. buoy.cr, atcr.io),
and users can now pick which one shows up in their pull/push commands.

- Lexicon/record: adds registryDomain (and documents ociClient)
to the sailor profile (lexicons/.../profile.json, pkg/atproto/lexicon.go).
- DB: new registry_domain column on users (schema.sql + migration 0027),
with GetUserByDID/Handle reads, UpdateUserRegistryDomain writer,
and Jetstream caching it on profile updates (writes unconditionally so clearing propagates).
- UI/handlers: new UpdateRegistryDomainHandler + /api/profile/registry-domain route,
a <select> in the user settings panel (only shown when >1 domain configured), and resolveRegistryURL()
which falls back to the primary domain if the user's pref is stale/removed. Tests added for all of it.

2. default_hold_did removed → first managed_holds entry is the default

Consolidates two overlapping config fields into one. ServerConfig.DefaultHoldDID is gone;
PrimaryHoldDID() now returns managed_holds[0]. managed_holds is now REQUIRED.
Updated in config, validation, server wiring, test harness, example YAML, and the deploy template.

3. Admin long-running operations → generic background-job framework

New pkg/hold/admin/jobs.go introduces a reusable startJob/jobRegistry pattern
 (a detached context.Background() job + a /admin/api/jobs/{key}/status polling endpoint).
This replaces the bespoke scan-backfill goroutine state machine, and now also wraps crew tier remap and crew import
all three previously looped synchronously on the request context and got 504'd/cancelled mid-run by the reverse proxy.
 Forms switched from POST-redirect to htmx fragments (job_progress.html, job_result.html, crew_import_results.html)
 the old crew_import_results.html page and scan_backfill_progress.html partial were deleted.
This is also captured as a new rule in CLAUDE.md.

4. Cascade-delete manifest on last-tag deletion

DeleteTagHandler now, after removing the last tag pointing to a digest, cascade-deletes the manifest itself
 (PDS + DB + hold blob purge) — but only if it's not a child of a manifest list (multi-arch parent).
 New GetTagDigest and ShouldCascadeDeleteManifest queries back it, plus cascade_delete_test.go.
 Also switches tag rkey computation to the atproto.RepositoryTagToRKey helper.

5. Billing simplification

Drops the OwnerBadge config option (hold-owner supporter badge).
The user-profile template no longer special-cases an "owner" badge value (only "Captain").
Example tiers renamed to the nautical scheme (deckhand/bosun/quartermaster).

6. Build/deploy: go generate always runs via Make

make generate is now a phony target that always runs go generate ./... (regenerating cbor_gen, icon sprites, etc.),
 and build-trixie depends on it. The deploy tooling (provision.go/update.go)
drops its own runGenerate calls since the Makefile handles it.

7. New cmd/firehose-tap tool (untracked)

A standalone CLI that subscribes to a com.atproto.sync.subscribeRepos endpoint and pretty-prints events,
with emphasis on Sync 1.1 compliance fields (per-op prev CIDs, commit prevData) and a --validate CI mode.
Fits with the recent "more sync1.1 compliant" commit.
2026-06-05 20:57:25 -05:00
Evan Jarrett efabb677e4 clean up some functions to use indigo helpers. make repomgr more sync1.1 compliant 2026-05-26 20:26:29 -05:00
Evan Jarrett 2f02d3e7e5 add a way to change crew tier on admin page 2026-05-18 22:39:54 -05:00
Evan Jarrett 04e10b6818 fix quota message 2026-05-16 17:20:12 -05:00
Evan Jarrett 902fba4553 convert alert to modal. go fix the codebase 2026-05-16 11:59:47 -05:00
Evan Jarrett 13a793ca90 improve admin tooling 2026-04-29 10:37:36 -05:00
Evan Jarrett 9af6eccc9d improvements to how scanning works, and helmchart ui 2026-04-29 10:12:25 -05:00
Evan Jarrett a602bf08d1 fix missing icons, update light mode theme colors 2026-04-22 21:41:48 -05:00
Evan Jarrett 9e09401cb3 fix play/pause button on carousel 2026-04-22 20:28:55 -05:00
Evan Jarrett c7783bf87c more hardening, inline tangled svg into the sprite 2026-04-22 20:13:40 -05:00
Evan Jarrett 267012b41e impeccable:harden on all admin panel 2026-04-21 23:14:05 -05:00
Evan Jarrett f057f169f0 large list of ui fixes for accessibility/hardening etc. 2026-04-21 21:18:13 -05:00
Evan Jarrett 38c693acc9 impeccable pass 2026-04-19 17:35:41 -05:00
Evan Jarrett 9809c26281 update fonts 2026-04-14 20:56:53 -05:00
Evan Jarrett e843b7233c more ui fixes and scanner fixes 2026-04-12 20:48:24 -05:00
Evan Jarrett fd5bfc3c50 ui fixes for repo page, fix scanner priority, cleanup goreleaser scripts 2026-04-03 16:48:21 -05:00
Evan Jarrett f20170f595 digest page improvements 2026-03-29 13:01:40 -07:00
Evan Jarrett 23db9be665 add repo page editor. fix deleting all untagged actually deleting all untagged 2026-03-23 21:16:13 -05:00
Evan Jarrett 385f8987fe overhaul repo pages, add tab for 'artifacts' (tags, manifests, helm charts). implement digest page with layer commands and vuln reports 2026-03-22 21:10:47 -05:00
Evan Jarrett 11a8be1413 upcloud provision fixes and relay tweaks 2026-03-01 20:52:41 -06:00
Evan Jarrett 136c0a0ecc billing refactor, move billing to appview, move webhooks to appview 2026-02-26 22:28:09 -06:00
Evan Jarrett 1e04c91507 update npm packages 2026-02-22 16:24:02 -06:00
Evan Jarrett 2b9ea997ac fix tier and supporter badge assignments. normalize did:web adresses with ports. various minor fixes 2026-02-22 11:16:55 -06:00
Evan Jarrett 356f9d529a actually check if the requestCrawl endpoint exists via HEAD 2026-02-21 14:24:37 -06:00
Evan Jarrett f90a46e0a4 begin implement supporter badges, clean up lexicons, various other changes 2026-02-20 22:12:18 -06:00
Evan Jarrett 76383ec764 fix vuln scanner db not refreshing 2026-02-19 22:08:02 -06:00
Evan Jarrett 200d8a7bb9 lazy load crew membership in admin panel 2026-02-18 22:45:43 -06:00
Evan Jarrett 22b2d69cb3 admin panel fixes 2026-02-18 21:40:53 -06:00
Evan Jarrett 5615dd4132 update GC options, minor fix to scanners 2026-02-18 20:26:49 -06:00
Evan Jarrett 27cf78158b vuln scanner fixes, major refactor of the credential helper. 2026-02-17 22:38:25 -06:00
Evan Jarrett cd4986c0c8 fix did validation in hold admin 2026-02-16 21:16:11 -06:00
Evan Jarrett cecf6d4b7c some request crawl relay fixes 2026-02-14 21:49:10 -06:00
Evan Jarrett e3843db9d8 Implement did:plc support for holds with the ability to import/export CARs.
did:plc Identity Support (pkg/hold/pds/did.go, pkg/hold/config.go, pkg/hold/server.go)

  The big feature — holds can now use did:plc identities instead of only did:web. This adds:
  - LoadOrCreateDID() — resolves hold DID by priority: config DID > did.txt on disk > create new
  - CreatePLCIdentity() — builds a genesis operation, signs with rotation key, submits to PLC directory
  - EnsurePLCCurrent() — on boot, compares local signing key + URL against PLC directory and auto-updates if they've drifted (requires rotation key)
  - New config fields: did_method (web/plc), did, plc_directory_url, rotation_key_path
  - GenerateDIDDocument() now uses the stored DID instead of always deriving did:web from URL
  - NewHoldServer wired up to call LoadOrCreateDID instead of GenerateDIDFromURL

  CAR Export/Import (pkg/hold/pds/export.go, pkg/hold/pds/import.go, cmd/hold/repo.go)

  New CLI subcommands for repo backup/restore:
  - atcr-hold repo export — streams the hold's repo as a CAR file to stdout
  - atcr-hold repo import <file>... — reads CAR files, upserts all records in a single atomic commit. Uses a bulkImportRecords method that opens a delta session, checks each record for
  create vs update, commits once, and fires repo events.
  - openHoldPDS() helper to spin up a HoldPDS from config for offline CLI operations

  Admin UI Fixes (pkg/hold/admin/)

  - Logout changed from GET to POST — nav template now uses a <form method=POST> instead of an <a> link (prevents CSRF on logout)
  - Removed return_to parameter from login flow — simplified redirect logic, auth middleware now redirects to /admin/auth/login without query params

  Config/Deploy

  - config-hold.example.yaml and deploy/upcloud/configs/hold.yaml.tmpl updated with the four new did:plc config fields
  - go.mod / go.sum — added github.com/did-method-plc/go-didplc dependency
2026-02-14 15:17:53 -06:00
Evan Jarrett c0cf3fb94f update dependencies 2026-02-12 20:28:00 -06:00
Evan Jarrett 92c31835e2 implement the ability to promote a hold as a successor as a way to migrate users to a new storage server 2026-02-12 20:14:19 -06:00
Evan Jarrett ac32a98104 clean up GC implementation 2026-02-11 20:44:07 -06:00
Evan Jarrett 150975a9fa more admin ui changes 2026-02-11 09:50:45 -06:00
Evan Jarrett 22d5396589 optimize queries for admin panel 2026-02-10 22:51:51 -06:00
Evan Jarrett aad9ebfc8b fix lint and unit tests 2026-02-09 22:39:38 -06:00
Evan Jarrett 7ba42080c5 more admin panel fixes, allow for fallback relays and jetstreams, improve star lexicon to allow for repo_page backlinks 2026-02-09 21:53:02 -06:00
Evan Jarrett ef0161fb0e update settings page, move admin-panel to tailwind/daisy 2026-02-06 11:23:12 -06:00
Evan Jarrett 34f342f637 lots of refactor and cleanup to allow for branding overrides 2026-02-02 22:42:15 -06:00
Evan Jarrett d0843323fe more pagespeed fixes 2026-01-17 15:48:40 -06:00
Evan Jarrett 4c0f20a32e begin large refactor of UI to use tailwind and daisy 2026-01-14 14:42:04 -06:00
Evan Jarrett d4b88b5105 more lint fixes. enable autofix 2026-01-06 23:56:17 -06:00
Evan Jarrett 9704fe091d use chi/render to simplify returned json 2026-01-06 22:47:21 -06:00
Eduardo CuducosandTangled c82dad81f7 Implements linter for pkg/hold missing warnings 2026-01-07 04:16:16 +00:00