feat(dev-env): shell scripts (git-hooks, worktree, deploy, regtest, nav) and smoke test

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-28 06:48:37 +02:00
commit dbd9e76027
13 changed files with 2496 additions and 0 deletions

View file

@ -0,0 +1,215 @@
#!/usr/bin/env bash
# dev-deploy: thin wrapper around the unified deploy flake
#
# Usage:
# dev-deploy <host> switch on host (uses locked deploy flake input)
# dev-deploy <host> test test build (no switch)
# dev-deploy <host> build local build only
# dev-deploy --local <host> same as switch but with --override-input
# wired up from local worktrees so the deploy
# builds against your in-progress changes
# dev-deploy all [test|build] every host in parallel
# dev-deploy update nix flake update
#
# Deploy host → SSH target mapping comes from /etc/dev-env/config.sh.
set -euo pipefail
if [[ -r /etc/dev-env/config.sh ]]; then
# shellcheck disable=SC1091
source /etc/dev-env/config.sh
fi
DEPLOY_DIR="${DEPLOY_DIR:-$DEV_ROOT/deploy}/${DEVENV_DEPLOY_FLAKE_INPUT:-unified}"
PROJECTS_JSON="${DEVENV_PROJECTS_JSON:-/etc/dev-env/projects.json}"
usage() {
cat <<EOF
dev-deploy — wraps nixos-rebuild against the unified deploy flake
USAGE:
dev-deploy <host> deploy (switch)
dev-deploy <host> test test build (no switch)
dev-deploy <host> build local build only (no target)
dev-deploy --local <host> deploy with --override-input from local worktrees
dev-deploy --local <host> test test with overrides
dev-deploy all [test|build] every host in parallel
dev-deploy update [args...] nix flake update
Configured targets:
EOF
if [[ -n "${!DEPLOY_TARGETS_KEYS[*]:-}" ]] 2>/dev/null \
|| [[ "${DEPLOY_TARGETS-}" == *"="* ]]; then
# DEPLOY_TARGETS is rendered as either a bash assoc array or
# KEY=VALUE lines depending on the shell — print whatever's set.
env | grep '^DEPLOY_TARGET_' | sed 's/^DEPLOY_TARGET_/ /' | sed 's/=/ → /' || true
fi
if [[ -r "$DEPLOY_DIR/deploy.sh" ]]; then
echo ""
echo "Backing flake: $DEPLOY_DIR"
fi
exit 1
}
[[ $# -lt 1 ]] && usage
# Pull the host → target map. We accept either:
# 1. DEPLOY_TARGET_<HOST>=<ssh> (rendered by config.nix as env vars)
# 2. ${DEPLOY_DIR}/deploy.sh's TARGETS associative array (fallback)
target_for() {
local host="$1"
local var="DEPLOY_TARGET_${host//-/_}"
if [[ -n "${!var:-}" ]]; then
echo "${!var}"
return
fi
# Fallback: parse the deploy.sh TARGETS array (simple regex; brittle
# but enough for our case)
if [[ -r "$DEPLOY_DIR/deploy.sh" ]]; then
awk -v h="$host" '
/^[[:space:]]*\[/ {
if (match($0, /\[([^]]+)\]="([^"]*)"/, m)) {
if (m[1] == h) { print m[2]; exit }
}
}
' "$DEPLOY_DIR/deploy.sh"
fi
}
# --- subcommands ---
cmd_update() {
shift
cd "$DEPLOY_DIR"
nix flake update "$@"
}
# Build the --override-input flags for a host using projects.json's
# `deployFlakeInput` mapping. For each project that has deployFlakeInput
# set AND a worktree resolved to a real on-disk path, we emit
# --override-input <input> path:<worktree-path>
# Picks the first worktree by default; pass DEVENV_OVERRIDE_WORKTREE=<name>
# to choose a specific one.
build_overrides() {
local args=()
local prefer="${DEVENV_OVERRIDE_WORKTREE:-}"
[[ -r "$PROJECTS_JSON" ]] || { printf '%s\n' ""; return; }
while IFS=$'\t' read -r input wt_path; do
[[ -z "$input" || "$input" == "null" ]] && continue
[[ -d "$wt_path" ]] || continue
args+=(--override-input "$input" "path:$wt_path")
done < <(jq -r --arg prefer "$prefer" '
to_entries[]
| select(.value.deployFlakeInput != null)
| .value as $v
| (if ($prefer | length) > 0 and ($v.worktrees | has($prefer))
then $v.worktrees[$prefer].path
else
($v.worktrees | to_entries | first.value.path // null)
end) as $path
| "\($v.deployFlakeInput)\t\($path // "")"
' "$PROJECTS_JSON")
printf '%s\0' "${args[@]}"
}
run_host() {
local host="$1" action="$2" use_local="$3"
local target
target="$(target_for "$host")"
if [[ -z "$target" ]] && [[ "$action" != "build" ]]; then
echo "no SSH target for host '$host' (set DEPLOY_TARGET_${host//-/_})" >&2
return 1
fi
cd "$DEPLOY_DIR"
local overrides=()
if $use_local; then
# Read the null-delimited overrides into an array
while IFS= read -r -d '' arg; do
[[ -n "$arg" ]] && overrides+=("$arg")
done < <(build_overrides)
if (( ${#overrides[@]} > 0 )); then
echo "[$host] using ${#overrides[@]} local override(s):"
local i=0
while (( i < ${#overrides[@]} )); do
echo " ${overrides[$((i+1))]} -> ${overrides[$((i+2))]}"
i=$((i + 3))
done
fi
fi
case "$action" in
deploy)
echo "[$host] switch on $target..."
nixos-rebuild switch \
--flake ".#$host" \
--target-host "$target" \
"${overrides[@]}"
;;
test)
echo "[$host] test on $target..."
nixos-rebuild test \
--flake ".#$host" \
--target-host "$target" \
"${overrides[@]}"
;;
build)
echo "[$host] local build..."
nix build \
".#nixosConfigurations.$host.config.system.build.toplevel" \
"${overrides[@]}"
;;
*)
echo "unknown action: $action" >&2
return 1
;;
esac
}
USE_LOCAL=false
if [[ "$1" == "--local" ]]; then
USE_LOCAL=true
shift
fi
case "$1" in
update)
cmd_update "$@"
;;
all)
action="${2:-deploy}"
# iterate every DEPLOY_TARGET_* env var
pids=()
hosts=()
for var in $(env | grep -o '^DEPLOY_TARGET_[A-Za-z0-9_]*' || true); do
host="${var#DEPLOY_TARGET_}"
host="${host//_/-}"
run_host "$host" "$action" "$USE_LOCAL" &
pids+=($!)
hosts+=("$host")
done
failed=()
for i in "${!pids[@]}"; do
if ! wait "${pids[$i]}"; then
failed+=("${hosts[$i]}")
fi
done
if (( ${#failed[@]} > 0 )); then
echo "FAILED: ${failed[*]}"
exit 1
fi
echo "All hosts deployed successfully."
;;
-h|--help)
usage
;;
*)
host="$1"
action="${2:-deploy}"
run_host "$host" "$action" "$USE_LOCAL"
;;
esac