{ pkgs, ... }: { environment.systemPackages = [ pkgs.python3 (pkgs.writeShellScriptBin "update-ref" '' set -euo pipefail RED='\033[31m'; YEL='\033[33m'; NC='\033[0m' die() { printf "''${RED}error:''${NC} %s\n" "$*" >&2; exit 2; } warn() { printf "''${YEL}warning:''${NC} %s\n" "$*" >&2; } # ---- Must be in a git repo (current dir) ---- git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "This directory is not a git project" CUR_REPO_ROOT="$(git rev-parse --show-toplevel)" CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD)" # ---- Athenix checkout ---- ATHENIX_DIR="$HOME/athenix" ATHENIX_REPO="" ATHENIX_BRANCH="" # ---- Git automation options for CURRENT repo ---- COMMIT_MSG="" PUSH_SPEC="" # ---- Push + mode controls ---- PUSH_SET=0 # user explicitly set push true/false DO_PUSH=0 # final push decision MODE_FORCE="" # "", "local", "remote" (from -l/-r) # ---- Required subcommand ---- TARGET="" # user= OR system=: usage() { cat >&2 <<'EOF' usage: update-ref [--athenix-repo=PATH|URL|-R PATH|URL] [--athenix-branch=BRANCH|-b BRANCH] [-m "msg" | --message "msg"] [-p[=false] [remote[=URL]] | --push[=false] [remote[=URL]]] [--make-local|-l] [--make-remote|-r] user= | system=: push default: - determined from the existing builtins.fetchGit url in the target entry: remote url -> push=true file/local -> push=false - if the entry doesn't exist: push=false unless --make-remote EOF exit 2 } is_remote_url() { # https://, http://, ssh://, or scp-style git@host:org/repo printf "%s" "$1" | grep -qE '^(https?|ssh)://|^[^/@:]+@[^/:]+:' } derive_full_hostname() { devtype="$1" hostkey="$2" if printf "%s" "$hostkey" | grep -q '-' || printf "%s" "$hostkey" | grep -q "^$devtype"; then printf "%s" "$hostkey" elif printf "%s" "$hostkey" | grep -qE '^[0-9]+$'; then printf "%s" "$devtype$hostkey" else printf "%s" "$devtype-$hostkey" fi } extract_existing_fetch_url() { # args: mode file username key python3 - "$1" "$2" "$3" "$4" <<'PY' import sys, re, pathlib mode = sys.argv[1] # user | system path = pathlib.Path(sys.argv[2]) username = sys.argv[3] # user mode key = sys.argv[4] # system mode: exact string key text = path.read_text() def find_fetch_block_user(u): m = re.search(r'(?s)\n\s*' + re.escape(u) + r'\.external\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', text) return m.group(1) if m else None def find_fetch_block_system(k): m = re.search(r'(?s)\n\s*"' + re.escape(k) + r'"\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', text) return m.group(1) if m else None def extract_url(block): if not block: return "" m = re.search(r'url\s*=\s*"([^"]+)"\s*;', block) return m.group(1) if m else "" block = None if mode == "user": block = find_fetch_block_user(username) else: block = find_fetch_block_system(key) print(extract_url(block)) PY } # ---- Parse args ---- while [ "$#" -gt 0 ]; do case "$1" in user=*|system=*) [ -z "$TARGET" ] || die "Only one subcommand allowed (user=... or system=...)" TARGET="$1" shift ;; --athenix-repo=*) ATHENIX_REPO="''${1#*=}" shift ;; -R) [ "$#" -ge 2 ] || usage ATHENIX_REPO="$2" shift 2 ;; --athenix-branch=*) ATHENIX_BRANCH="''${1#*=}" shift ;; -b) [ "$#" -ge 2 ] || usage ATHENIX_BRANCH="$2" shift 2 ;; -m|--message) [ "$#" -ge 2 ] || usage COMMIT_MSG="$2" shift 2 ;; -p|--push) PUSH_SET=1 DO_PUSH=1 # optional next token remote or remote=URL (only if it doesn't look like another flag/subcommand) if [ "$#" -ge 2 ] && printf "%s" "$2" | grep -qvE '^(user=|system=|--|-l$|-r$)'; then PUSH_SPEC="$2" shift 2 else shift fi ;; -p=*|--push=*) PUSH_SET=1 val="''${1#*=}" case "$val" in false|0|no|off) DO_PUSH=0 ;; true|1|yes|on|"") DO_PUSH=1 ;; *) die "Invalid value for --push: $val (use true/false)" ;; esac shift ;; --make-local|-l) MODE_FORCE="local" shift ;; --make-remote|-r) MODE_FORCE="remote" shift ;; -h|--help) usage ;; *) die "Unknown argument: $1" ;; esac done [ -n "$TARGET" ] || die "Missing required subcommand: user= or system=:" # ---- Validate athenix checkout ---- [ -d "$ATHENIX_DIR" ] || die "$ATHENIX_DIR does not exist" git -C "$ATHENIX_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "$ATHENIX_DIR is not a git project" if [ -z "$ATHENIX_BRANCH" ]; then ATHENIX_BRANCH="$(git -C "$ATHENIX_DIR" rev-parse --abbrev-ref HEAD)" fi # ---- Determine target file + identifiers ---- FILE="" MODE="" USERNAME="" DEVTYPE="" HOSTKEY="" case "$TARGET" in user=*) MODE="user" USERNAME="''${TARGET#user=}" [ -n "$USERNAME" ] || die "user=: username missing" FILE="$ATHENIX_DIR/users.nix" ;; system=*) MODE="system" RHS="''${TARGET#system=}" printf "%s" "$RHS" | grep -q ':' || die "system=... must be system=:" DEVTYPE="''${RHS%%:*}" HOSTKEY="''${RHS#*:}" [ -n "$DEVTYPE" ] || die "system=:: device-type missing" [ -n "$HOSTKEY" ] || die "system=:: hostkey missing" FILE="$ATHENIX_DIR/inventory.nix" ;; esac [ -f "$FILE" ] || die "File not found: $FILE" # ---- Determine existing fetchGit url in the entry (for push default) ---- EXISTING_URL="" ENTRY_EXISTS=0 if [ "$MODE" = "user" ]; then EXISTING_URL="$(extract_existing_fetch_url user "$FILE" "$USERNAME" "")" [ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true else FULL="$(derive_full_hostname "$DEVTYPE" "$HOSTKEY")" EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$HOSTKEY")" if [ -n "$EXISTING_URL" ]; then ENTRY_EXISTS=1 elif [ "$FULL" != "$HOSTKEY" ]; then EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$FULL")" [ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true fi fi # ---- Default push based on existing entry url unless user explicitly set it ---- if [ "$PUSH_SET" -eq 0 ]; then if [ "$ENTRY_EXISTS" -eq 1 ] && is_remote_url "$EXISTING_URL"; then DO_PUSH=1 else # entry missing or local/file url if [ "$MODE_FORCE" = "remote" ]; then DO_PUSH=1 else DO_PUSH=0 fi fi fi # If forcing local and user didn't explicitly demand push, push defaults off. if [ "$MODE_FORCE" = "local" ] && [ "$PUSH_SET" -eq 0 ]; then DO_PUSH=0 fi # ---- Dirty check prompt ---- if [ -n "$(git status --porcelain)" ]; then warn "This branch has untracked or uncommitted changes. Would you like to add, commit''${DO_PUSH:+, and push}? [y/N] " read -r ans || true case "''${ans:-N}" in y|Y|yes|YES) git add -A if ! git diff --cached --quiet; then if [ -n "$COMMIT_MSG" ]; then git commit -m "$COMMIT_MSG" else git commit fi else warn "No staged changes to commit." fi ;; *) warn "Proceeding without committing. (rev will be last committed HEAD.)" ;; esac elif [ -n "$COMMIT_MSG" ]; then # clean tree but message provided: nothing to do : fi # ---- Push current repo if requested ---- PUSH_REMOTE_URL="" if [ "$DO_PUSH" -eq 1 ]; then if [ -n "$PUSH_SPEC" ]; then if printf "%s" "$PUSH_SPEC" | grep -q '='; then REM_NAME="''${PUSH_SPEC%%=*}" REM_URL="''${PUSH_SPEC#*=}" [ -n "$REM_NAME" ] || die "--push remote-name=URL: remote-name missing" [ -n "$REM_URL" ] || die "--push remote-name=URL: URL missing" if git remote get-url "$REM_NAME" >/dev/null 2>&1; then git remote set-url "$REM_NAME" "$REM_URL" else git remote add "$REM_NAME" "$REM_URL" fi git push -u "$REM_NAME" "$CUR_BRANCH" PUSH_REMOTE_URL="$REM_URL" else REM_NAME="$PUSH_SPEC" git push -u "$REM_NAME" "$CUR_BRANCH" PUSH_REMOTE_URL="$(git remote get-url "$REM_NAME")" fi else # default: push to upstream, error if none if ! git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1; then die "No upstream is set. Set a default upstream with \"git branch -u /\"" fi git push UPSTREAM_REMOTE="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} | cut -d/ -f1)" PUSH_REMOTE_URL="$(git remote get-url "$UPSTREAM_REMOTE")" fi fi # ---- Always get current HEAD hash (after optional commit/push) ---- CUR_REV="$(git -C "$CUR_REPO_ROOT" rev-parse HEAD)" # ---- Determine url to write into fetchGit (respecting make-local/make-remote) ---- if [ "$MODE_FORCE" = "local" ]; then FETCH_URL="file://$CUR_REPO_ROOT" elif [ "$MODE_FORCE" = "remote" ]; then if [ "$DO_PUSH" -eq 1 ]; then FETCH_URL="$PUSH_REMOTE_URL" elif [ "$ENTRY_EXISTS" -eq 1 ] && [ -n "$EXISTING_URL" ] && is_remote_url "$EXISTING_URL"; then FETCH_URL="$EXISTING_URL" else CUR_ORIGIN="$(git remote get-url origin 2>/dev/null || true)" [ -n "$CUR_ORIGIN" ] && is_remote_url "$CUR_ORIGIN" || die "--make-remote requires a remote url (set origin or use -p remote=URL)" FETCH_URL="$CUR_ORIGIN" fi else if [ "$DO_PUSH" -eq 1 ]; then FETCH_URL="$PUSH_REMOTE_URL" else FETCH_URL="file://$CUR_REPO_ROOT" fi fi # ---- Rewrite athenix file ---- python3 - "$MODE" "$FILE" "$FETCH_URL" "$CUR_REV" "$USERNAME" "$DEVTYPE" "$HOSTKEY" <<'PY' import sys, re, pathlib mode = sys.argv[1] path = pathlib.Path(sys.argv[2]) fetch_url = sys.argv[3] rev = sys.argv[4] username = sys.argv[5] devtype = sys.argv[6] hostkey = sys.argv[7] text = path.read_text() def mk_fetch_block(url, rev): return ( 'builtins.fetchGit {\n' f' url = "{url}";\n' f' rev = "{rev}";\n' ' submodules=true;\n' ' }' ) fetch_block = mk_fetch_block(fetch_url, rev) def full_hostname(devtype, hostkey): if hostkey.startswith(devtype) or "-" in hostkey: return hostkey if hostkey.isdigit(): return f"{devtype}{hostkey}" return f"{devtype}-{hostkey}" if mode == "user": m = re.search(r'(?s)(athenix\.users\s*=\s*\{)(.*?)(\n\s*\};)', text) if not m: raise SystemExit("error: could not locate `athenix.users = { ... };` block") head, body, tail = m.group(1), m.group(2), m.group(3) key_re = re.compile(r'(?s)(\n\s*' + re.escape(username) + r'\.external\s*=\s*)builtins\.fetchGit\s*\{.*?\};') if key_re.search(body): body = key_re.sub(lambda mm: mm.group(1) + fetch_block + ';', body) else: body = body + f'\n {username}.external = {fetch_block};\n' new_text = text[:m.start()] + head + body + tail + text[m.end():] path.write_text(new_text) sys.exit(0) elif mode == "system": block_re = re.compile(r'(?s)(\n\s*' + re.escape(devtype) + r'\s*=\s*\{)(.*?)(\n\s*\};)') m = block_re.search(text) if not m: raise SystemExit(f"error: could not locate `{devtype} = {{ ... }};` block in inventory.nix") head, body, tail = m.group(1), m.group(2), m.group(3) candidates = [hostkey, full_hostname(devtype, hostkey)] candidates = list(dict.fromkeys(candidates)) replaced = False for k in candidates: entry_re = re.compile(r'(?s)(\n\s*"' + re.escape(k) + r'"\s*=\s*)builtins\.fetchGit\s*\{.*?\};') if entry_re.search(body): body = entry_re.sub(lambda mm: mm.group(1) + fetch_block + ';', body) replaced = True break if not replaced: body = body + f'\n "{hostkey}" = {fetch_block};\n' new_text = text[:m.start()] + head + body + tail + text[m.end():] path.write_text(new_text) sys.exit(0) else: raise SystemExit("error: unknown mode") PY printf "updated %s (athenix branch: %s)\n" "$FILE" "$ATHENIX_BRANCH" >&2 printf " url = %s\n" "$FETCH_URL" >&2 printf " rev = %s\n" "$CUR_REV" >&2 '') ]; }