/** * Ephemeral, isolated Docker credential context for ECR pushes. * * Why this exists. `docker login --password-stdin ` delegates * credential STORAGE to the configured `credsStore` credential helper. On * macOS that helper is `docker-credential-osxkeychain`, which cannot overwrite * a pre-existing keychain entry — it exits non-zero with * `errSecDuplicateItem (-25299)` ("The specified item already exists in the * keychain"). A single stale entry then makes EVERY `fjall deploy` fail at * "Authenticating to ECR…" until the user manually runs * `security delete-internet-password`. The same `docker login` is also a * read-modify-write of the global `~/.docker/config.json`, which races and can * corrupt under concurrent deploys. * * The fix. Fjall's own ECR auth must never depend on, mutate, or be corrupted * by the user's OS keychain. `createEcrAuthSession` writes a throwaway, * deploy-scoped `DOCKER_CONFIG` directory whose `config.json` carries the ECR * token inline under `auths` and has NO `credsStore` — so docker and buildx * read the token straight from the file and the keychain is never touched. The * user's real environment is preserved where it matters: * - docker contexts (the active daemon endpoint — e.g. OrbStack, colima, a * remote TLS endpoint) are symlinked in, so a non-default `currentContext` * still resolves; * - the `cli-plugins` directory is symlinked in, so `docker buildx` resolves * under the redirected `DOCKER_CONFIG`. On macOS, Docker Desktop / OrbStack * install buildx ONLY into `~/.docker/cli-plugins`; without this link every * `docker buildx` call is "unknown command" for the whole session and the * build dies at builder bootstrap (the Linux worker keeps buildx in a system * plugin dir, so it is unaffected); * - the buildx builder store is pointed at the real one via `BUILDX_CONFIG`, * so the persistent "fjall" builder is reused rather than re-created; * - all non-credential config keys (proxies, HttpHeaders, currentContext, * other registries' inline auths, per-registry credHelpers) are carried * over. * The session env (`DOCKER_CONFIG` + `BUILDX_CONFIG`) is applied to every * docker subprocess for the deploy; `dispose()` removes the temp dir. * * Trade-off (documented, accepted). Dropping the global `credsStore` is * unavoidable: docker ignores an inline `auths[...].auth` whenever a * `credsStore` is configured, so the only way to honour our inline ECR token * is to write a config without it. The cost is that a base image pulled from a * NON-ECR private registry whose credentials live ONLY in the OS keychain * (never inline, never via a per-registry credHelper) would lose them for this * build. That is rare for Fjall apps (public base images) and is the correct * exchange for fixing a hard, deploy-blocking failure that hits every macOS * user with a stale keychain entry. ECR tokens are short-lived (~12h); the * file is mode-0600 inside a mode-0700 temp dir and is removed after the push. */ import type { DockerCliLogger } from "./DockerCli.js"; export interface EcrAuthSession { /** Env vars to apply to every docker subprocess for the duration of the deploy. */ readonly env: Readonly>; /** Remove the ephemeral config dir. Best-effort; never throws. */ dispose(): Promise; } export interface CreateEcrAuthSessionParams { /** ECR registry — bare host or `https://`; normalised to a host key. */ readonly registry: string; /** Always `AWS` for ECR. */ readonly username: string; /** The decoded ECR authorization token (the password half of `AWS:`). */ readonly password: string; /** The env the DockerCli was constructed with — source of the real config dir. */ readonly baseEnv: NodeJS.ProcessEnv; readonly logger: DockerCliLogger; } export declare function createEcrAuthSession(params: CreateEcrAuthSessionParams): Promise;