feat(dev-env): backport matured dev-env implementation from /etc/nixos
Replace the stub dev-env with the real, working implementation that grew
in the reference machine config — de-identified for the public scaffold.
Nix layer:
- options.nix: full project schema (url/upstream/fork/category/
worktreeRoot/worktrees{branch,path,remote}/isClone/deployFlakeInput),
deploy.targets, github.forkUser, writeDirenvHints. Drops the
forgejo-URL block + deploy-flake auto-derivation (incoherent in a
scaffold that uses explicit per-project urls).
- lib.nix: mkProject + worktreePath/bareRepoPath/projectRemotes,
generalized to the explicit-url model (origin falls back to upstream).
- config.nix: renders /etc/dev-env/{config.sh,projects.json,
tmux-sessions.json}, installs helpers via writeShellScriptBin, loads
shell functions into interactive shells, wires the git pre-commit hook.
Scripts (config-driven, read /etc/dev-env at runtime):
- bootstrap.sh, nav.sh, worktree.sh, pr-helpers.sh, rebase.sh,
status.sh, deploy.sh, regtest.sh, tmux-launch.sh.
- Stripped aiolabs/forgejo/bitspire/lamassu/webapp hardcoding; the
github-fork remote is renamed 'fork' to match git.remotes vocabulary.
- Removes the dev.sh stub (the matured impl uses discrete commands +
shell functions, not a unified 'dev' CLI).
presets/example.nix: a worked, generic project list replacing the
identity-specific aiolabs preset. tests/smoke.nix + flake checks
exercise the schema; 'nix flake check' is green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
773632562e
commit
e38d313db2
17 changed files with 2925 additions and 147 deletions
217
modules/dev-env/scripts/deploy.sh
Normal file
217
modules/dev-env/scripts/deploy.sh
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env bash
|
||||
# dev-deploy: thin wrapper around your 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
|
||||
# (devEnv.deploy.targets). The flake working copy lives at
|
||||
# ${DEV_ROOT}/deploy[/<flakeInput>].
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -r /etc/dev-env/config.sh ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/dev-env/config.sh
|
||||
fi
|
||||
|
||||
DEPLOY_BASE="${DEPLOY_DIR:-$DEV_ROOT/deploy}"
|
||||
if [[ -n "${DEVENV_DEPLOY_FLAKE_INPUT:-}" ]]; then
|
||||
DEPLOY_DIR="$DEPLOY_BASE/$DEVENV_DEPLOY_FLAKE_INPUT"
|
||||
else
|
||||
DEPLOY_DIR="$DEPLOY_BASE"
|
||||
fi
|
||||
PROJECTS_JSON="${DEVENV_PROJECTS_JSON:-/etc/dev-env/projects.json}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
dev-deploy — wraps nixos-rebuild against your 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
|
||||
env | grep '^DEPLOY_TARGET_' | sed 's/^DEPLOY_TARGET_/ /' | sed 's/=/ → /' || true
|
||||
if [[ -r "$DEPLOY_DIR/flake.nix" ]]; 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 a deploy.sh TARGETS array (simple regex; brittle
|
||||
# but enough as a fallback)
|
||||
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
|
||||
Reference in a new issue