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:
Padreug 2026-06-15 21:18:49 +02:00
commit e38d313db2
17 changed files with 2925 additions and 147 deletions

View file

@ -0,0 +1,168 @@
#!/usr/bin/env bash
# dev-env: navigation helpers
#
# Sourced by user shells (not invoked as a script) via the loader in
# /etc/profile.d/dev-env-functions.sh. Every function reads
# /etc/dev-env/config.sh at call time, so adding a new worktree on disk
# is immediately visible without a nixos-rebuild.
# Load runtime config (idempotent).
_devenv_load_config() {
if [[ -r /etc/dev-env/config.sh ]]; then
# shellcheck disable=SC1091
source /etc/dev-env/config.sh
fi
}
# Navigate to the dev root.
dev() {
_devenv_load_config
cd "${DEV_ROOT:-$HOME/dev}" || return 1
}
# Navigate to an lnbits worktree.
# Usage: lb [worktree] (worktree ∈ whatever the filesystem shows, e.g. dev/main)
lb() {
_devenv_load_config
local env="${1:-}"
local lnbits_dir="${LNBITS_DIR:-$DEV_ROOT/lnbits}"
if [[ -z "$env" ]]; then
echo "Usage: lb <worktree>"
echo ""
echo "Current lnbits worktrees:"
if [[ -d "$lnbits_dir" ]]; then
for d in "$lnbits_dir"/*/; do
[[ -d "$d" ]] || continue
local name branch
name="$(basename "$d")"
branch="$(git -C "$d" branch --show-current 2>/dev/null || echo '?')"
printf " %-12s (%s)\n" "$name" "$branch"
done
fi
return 0
fi
if [[ -d "$lnbits_dir/$env" ]]; then
cd "$lnbits_dir/$env" || return 1
else
echo "Unknown lnbits worktree: $env"
return 1
fi
}
# Navigate within a project-group folder (a `category` directory holding
# one or more sub-repos). Sub-repos may be single clones or bare-repo
# worktree sets; worktree sets accept an optional worktree name.
# Usage: g <category> [repo] [worktree]
_dev_group_nav() {
local group_dir="$1" repo="${2:-}" worktree="${3:-dev}"
if [[ -z "$repo" ]]; then
echo "repos under $(basename "$group_dir")/:"
if [[ -d "$group_dir" ]]; then
for d in "$group_dir"/*/; do
[[ -d "$d" ]] && echo " $(basename "$d")"
done
fi
return 0
fi
# Single-clone sub-repo (has its own .git at the top level)
if [[ -d "$group_dir/$repo/.git" ]]; then
cd "$group_dir/$repo" || return 1
return 0
fi
# Worktree-based sub-repo
if [[ -d "$group_dir/$repo/$worktree" ]]; then
cd "$group_dir/$repo/$worktree" || return 1
elif [[ -d "$group_dir/$repo" ]]; then
cd "$group_dir/$repo" || return 1
else
echo "Unknown repo under $(basename "$group_dir")/: $repo"
return 1
fi
}
# Navigate into a project category directory.
# Usage: g <category> [repo] [worktree]
g() {
_devenv_load_config
local category="${1:-}"
if [[ -z "$category" ]]; then
echo "Usage: g <category> [repo] [worktree]"
echo ""
echo "Categories under $DEV_ROOT:"
for d in "$DEV_ROOT"/*/; do
[[ -d "$d" ]] && echo " $(basename "$d")"
done
return 0
fi
_dev_group_nav "$DEV_ROOT/$category" "${2:-}" "${3:-dev}"
}
# Navigate to an extension under shared/extensions.
ext() {
_devenv_load_config
local extension="${1:-}"
local ext_root="${SHARED_DIR:-$DEV_ROOT/shared}/extensions"
if [[ -z "$extension" ]]; then
echo "Available extensions:"
[[ -d "$ext_root" ]] && ls -1 "$ext_root"
return 0
fi
if [[ -d "$ext_root/$extension" ]]; then
cd "$ext_root/$extension" || return 1
else
echo "Unknown extension: $extension"
[[ -d "$ext_root" ]] && ls -1 "$ext_root"
return 1
fi
}
# Navigate to a deploy host config inside the deploy flake working copy.
deploy_nav() {
_devenv_load_config
local host="${1:-}"
local deploy_root="${DEPLOY_DIR:-$DEV_ROOT/deploy}"
if [[ -z "$host" ]]; then
echo "Usage: dep <host>"
echo ""
echo "Deploy targets (from /etc/dev-env/config.sh):"
env | grep '^DEPLOY_TARGET_' | sed 's/^DEPLOY_TARGET_/ /' | sed 's/=/ → /' || true
return 1
fi
if [[ -d "$deploy_root/hosts/$host" ]]; then
cd "$deploy_root/hosts/$host" || return 1
elif [[ -d "$deploy_root/$host" ]]; then
cd "$deploy_root/$host" || return 1
elif [[ -d "$deploy_root" ]]; then
cd "$deploy_root" || return 1
else
echo "Deploy path not found: $deploy_root"
return 1
fi
}
alias dep=deploy_nav
# Navigate to the shared repos directory.
shared() {
_devenv_load_config
cd "${SHARED_DIR:-$DEV_ROOT/shared}" || return 1
}
# Navigate to the bare-repos directory.
repos() {
_devenv_load_config
cd "${REPOS_DIR:-$DEV_ROOT/repos}" || return 1
}
# Navigate to the upstream-prs directory.
prs() {
_devenv_load_config
cd "${UPSTREAM_PRS_DIR:-$DEV_ROOT/upstream-prs}" || return 1
}