A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
73
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 165 lines 8.2 kB view raw view rendered
1# Credential Helper Rewrite 2 3## Context 4 5The current credential helper (`cmd/credential-helper/main.go`, ~1070 lines) is a monolithic single-file binary with a manual `switch` dispatch. It has no help text, hangs silently when run without stdin, embeds interactive device auth inside the Docker protocol `get` command (blocking pushes for up to 2 minutes while polling), and only supports one account per registry. Users want multi-account support (e.g., `evan.jarrett.net` and `michelle.jarrett.net` on the same `atcr.io`) and multi-registry support (e.g., `atcr.io` + `buoy.cr`). 6 7## Approach 8 9Rewrite using **Cobra** (already a project dependency) for the CLI framework and **charmbracelet/huh** for interactive prompts (select menus, confirmations, spinners). Separate Docker protocol commands (machine-readable, hidden) from user-facing commands (interactive, discoverable). Model after `gh auth` UX patterns. 10 11**Smart account auto-detection**: The `get` command inspects the parent process command line (`/proc/<ppid>/cmdline` on Linux, `ps` on macOS) to determine which image Docker is pushing/pulling. Since ATCR URLs are `host/<identity>/repo:tag`, we can extract the identity and auto-select the matching account — no prompts, no manual switching needed in the common case. 12 13## Command Tree 14 15``` 16docker-credential-atcr 17 ├── get (Docker protocol — stdin/stdout, hidden, smart account detection) 18 ├── store (Docker protocol — stdin, hidden) 19 ├── erase (Docker protocol — stdin, hidden) 20 ├── list (Docker protocol extension, hidden) 21 ├── login (Interactive device flow with huh prompts) 22 ├── logout (Remove account credentials) 23 ├── status (Show all accounts with active indicators) 24 ├── switch (Switch active account — auto-toggle for 2, select for 3+) 25 ├── configure-docker (Auto-edit ~/.docker/config.json credHelpers) 26 ├── update (Self-update, existing logic preserved) 27 └── version (Built-in via cobra) 28``` 29 30## Smart Account Resolution (`get` command) 31 32The `get` command resolves which account to use with this priority chain — fully non-interactive: 33 34``` 351. Parse parent process cmdline → extract identity from image ref 36 docker push atcr.io/evan.jarrett.net/test:latest 37 → parent cmdline contains "evan.jarrett.net" → use that account 38 392. Fall back to active account (set by `switch` command) 40 413. Fall back to sole account (if only one exists for this registry) 42 434. Error with helpful message: 44 "Multiple accounts for atcr.io. Run: docker-credential-atcr switch" 45``` 46 47**Parent process detection** (in `helpers.go`): 48- Linux: read `/proc/<ppid>/cmdline` (null-separated args) 49- macOS: `ps -o args= -p <ppid>` 50- Windows: best-effort via `wmic` or skip (fall to active account) 51- Parse image ref: find the arg matching `<registry-host>/<identity>/...`, extract `<identity>` 52- Graceful failure: if parent isn't Docker, cmdline unreadable, or image ref not parseable → fall through to active account 53 54## File Structure 55 56``` 57cmd/credential-helper/ 58 main.go — Cobra root command, version vars, subcommand registration 59 config.go — Config types, load/save/migrate, getConfigPath 60 device_auth.go — authorizeDevice(), validateCredentials() HTTP logic 61 protocol.go — Docker protocol: get, store, erase, list (all hidden) 62 cmd_login.go — login command (huh prompts + device flow) 63 cmd_logout.go — logout command (huh confirm) 64 cmd_status.go — status display 65 cmd_switch.go — switch command (huh select) 66 cmd_configure.go — configure-docker (edit ~/.docker/config.json) 67 cmd_update.go — update command (moved from existing code) 68 helpers.go — openBrowser, buildAppViewURL, isInsecureRegistry, parentCmdline, etc. 69``` 70 71## Config Format (`~/.atcr/device.json`) 72 73```json 74{ 75 "version": 2, 76 "registries": { 77 "https://atcr.io": { 78 "active": "evan.jarrett.net", 79 "accounts": { 80 "evan.jarrett.net": { 81 "handle": "evan.jarrett.net", 82 "did": "did:plc:abc123", 83 "device_secret": "atcr_device_..." 84 }, 85 "michelle.jarrett.net": { 86 "handle": "michelle.jarrett.net", 87 "did": "did:plc:def456", 88 "device_secret": "atcr_device_..." 89 } 90 } 91 }, 92 "https://buoy.cr": { 93 "active": "evan.jarrett.net", 94 "accounts": { ... } 95 } 96 } 97} 98``` 99 100**Migration**: `loadConfig()` auto-detects and migrates from old formats: 101- Legacy single-device `{handle, device_secret, appview_url}` → v2 102- Current multi-registry `{credentials: {url: {...}}}` → v2 103- Writes back migrated config on first load 104 105## Key Behavioral Changes 106 107| Command | Current | New | 108|---------|---------|-----| 109| `get` | Opens browser, polls 2min if no creds | Smart detection → active account → error | 110| `get` (multi-account) | N/A (single account only) | Auto-detects identity from parent cmdline | 111| `get` (no stdin) | Hangs forever | Detects terminal, prints help, exits 1 | 112| `get` (OAuth expired) | Auto-opens browser, polls | Prints login URL, exits 1 | 113| `store` | No-op | Stores if secret is device secret (`atcr_device_*`) | 114| `erase` | Removes all creds for host | Removes active account only | 115| No args | Prints bare usage | Prints full cobra help with all commands | 116 117## Dependencies 118 119- `github.com/spf13/cobra` — already in go.mod 120- `github.com/charmbracelet/huh` — new (pure Go, CGO_ENABLED=0 safe) 121 122No changes to `.goreleaser.yaml` needed. 123 124## Implementation Order 125 126### Phase 1: Foundation 1271. `helpers.go` — move utility functions verbatim + add `getParentCmdline()` and `detectIdentityFromParent(registryHost)` 1282. `config.go` — new config types + migration from old formats 1293. `main.go` — Cobra root command, register all subcommands 130 131### Phase 2: Docker Protocol (must work for existing users) 1324. `device_auth.go` — extract `authorizeDevice()` + `validateCredentials()` 1335. `protocol.go``get`/`store`/`erase`/`list` using new config with smart account resolution 134 135### Phase 3: User Commands 1366. `cmd_login.go` — interactive device flow with huh spinner 1377. `cmd_status.go` — display all registries/accounts 1388. `cmd_switch.go` — huh select for account switching 1399. `cmd_logout.go` — huh confirm for removal 14010. `cmd_configure.go` — Docker config.json manipulation 14111. `cmd_update.go` — move existing update logic 142 143### Phase 4: Polish 14412. Add `huh` to go.mod 14513. Delete old `main.go` contents (replaced by new files) 146 147## What to Keep vs Rewrite 148 149**Keep** (move to new files): `openBrowser()`, `buildAppViewURL()`, `isInsecureRegistry()`, `getDockerInsecureRegistries()`, `readDockerDaemonConfig()`, `stripPort()`, `isTerminal()`, `authorizeDevice()` HTTP logic, `validateCredentials()`, all update/version check functions. 150 151**Rewrite**: `main()`, `handleGet()` (split into non-interactive `get` with smart detection + interactive `login`), `handleStore()` (implement actual storage), `handleErase()` (multi-account aware), config types and loading. 152 153**New**: `list`, `login`, `logout`, `status`, `switch`, `configure-docker` commands. Config migration. Parent process identity detection. huh integration. 154 155## Verification 156 1571. Build: `go build -o bin/docker-credential-atcr ./cmd/credential-helper` 1582. Help works: `bin/docker-credential-atcr --help` shows all user commands 1593. Protocol works: `echo "atcr.io" | bin/docker-credential-atcr get` returns credentials or helpful error 1604. No hang: `bin/docker-credential-atcr get` (no stdin pipe) detects terminal, prints help, exits 1615. Smart detection: `docker push atcr.io/evan.jarrett.net/test:latest` auto-selects `evan.jarrett.net` 1626. Login flow: `bin/docker-credential-atcr login` triggers device auth with huh prompts 1637. Status: `bin/docker-credential-atcr status` shows configured accounts 1648. Config migration: Place old-format `~/.atcr/device.json`, run any command, verify auto-migration 1659. GoReleaser: `CGO_ENABLED=0 go build ./cmd/credential-helper` succeeds