From b49ff085ac91c0509689985f27ab4222ef52fef4 Mon Sep 17 00:00:00 2001 From: Alex Maldonado Date: Fri, 26 Jun 2026 14:36:10 -0400 Subject: [PATCH] feat: add docker credentials script --- home/alex/default.nix | 1 + home/modules/docker.nix | 8 ++++ justfile | 5 +++ scripts/docker-creds.sh | 85 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 home/modules/docker.nix create mode 100755 scripts/docker-creds.sh diff --git a/home/alex/default.nix b/home/alex/default.nix index 15d369b..2be319c 100644 --- a/home/alex/default.nix +++ b/home/alex/default.nix @@ -6,6 +6,7 @@ imports = [ ../modules/datalad.nix + ../modules/docker.nix ../modules/dropbox.nix ../modules/fonts.nix ../modules/ghostty.nix diff --git a/home/modules/docker.nix b/home/modules/docker.nix new file mode 100644 index 0000000..6433ebb --- /dev/null +++ b/home/modules/docker.nix @@ -0,0 +1,8 @@ +{ pkgs, ... }: +{ + home.packages = with pkgs; [ + docker-credential-helpers + pass + gnupg + ]; +} diff --git a/justfile b/justfile index 4adf6af..2af4f14 100644 --- a/justfile +++ b/justfile @@ -51,3 +51,8 @@ capture-hardware name: hosts: @./scripts/list-hosts.py +# Seed ~/.docker/config.json and (for pass) set up the GPG key + pass store. +# Override store: just docker-creds secretservice +# Unprotected key: just docker-creds pass --no-passphrase +docker-creds store='pass' flag='': + @./scripts/docker-creds.sh "{{store}}" "{{flag}}" diff --git a/scripts/docker-creds.sh b/scripts/docker-creds.sh new file mode 100755 index 0000000..9a90460 --- /dev/null +++ b/scripts/docker-creds.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +store="${1:-pass}" +no_passphrase=0 +[ "${2:-}" = "--no-passphrase" ] && no_passphrase=1 + +config_dir="$HOME/.docker" +config_file="$config_dir/config.json" + +# Identity used for the dedicated docker-creds GPG key. Stable so re-runs +# are idempotent (we look the key up by this uid rather than guessing). +gpg_name="Docker Credential Store" +gpg_email="${USER}@$(hostname)" +gpg_uid="$gpg_name <$gpg_email>" + +# --- credsStore in config.json (merge, never clobber auths) ------------- +if ! command -v "docker-credential-$store" >/dev/null 2>&1; then + echo "warning: docker-credential-$store not found in PATH" >&2 + echo " is docker-credential-helpers installed and the rebuild applied?" >&2 +fi + +mkdir -p "$config_dir" +python3 - "$config_file" "$store" <<'PY' +import json, os, sys +path, store = sys.argv[1], sys.argv[2] +config = {} +if os.path.exists(path): + with open(path) as f: + try: + config = json.load(f) + except json.JSONDecodeError: + config = {} +config["credsStore"] = store +with open(path, "w") as f: + json.dump(config, f, indent=2) + f.write("\n") +PY +chmod u+w "$config_file" +echo "Set credsStore = \"$store\" in $config_file" + +# The remaining steps only apply to the pass-backed store. +[ "$store" = "pass" ] || { echo "store is '$store'; skipping GPG/pass setup."; exit 0; } + +# --- GPG key (generate only if our uid doesn't already have one) -------- +if gpg --list-secret-keys "$gpg_uid" >/dev/null 2>&1; then + echo "GPG key for '$gpg_uid' already exists; skipping generation." +else + echo "Generating GPG key for '$gpg_uid'..." + protection="# (passphrase-protected; pinentry will prompt)" + if [ "$no_passphrase" -eq 1 ]; then + echo "warning: creating an UNPROTECTED key (--no-passphrase)." >&2 + echo " anyone who can read ~/.gnupg can decrypt your registry creds." >&2 + protection="%no-protection" + fi + gpg --batch --full-generate-key <&2; exit 1; } + +# --- pass init (only if the store isn't already initialized) ------------ +store_dir="${PASSWORD_STORE_DIR:-$HOME/.password-store}" +if [ -f "$store_dir/.gpg-id" ]; then + echo "pass already initialized at $store_dir; skipping init." +else + pass init "$fpr" +fi + +echo +echo "Done. Remaining manual step: docker login"