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

@ -1,22 +1,111 @@
# lnbits-sensei dev-env — helpers.
#
# Skeleton-only. Place dev-env-internal helpers here (project path
# resolution, worktree-path expansion, remote-URL canonicalisation)
# rather than in the global `lnbits-sensei.lib` so they're scoped to
# the dev-env module and don't pollute the public helper namespace.
# dev-env-internal helpers scoped to this module (project path
# resolution, worktree-path expansion, remote construction) rather than
# the global `lnbits-sensei.lib` namespace. Exposed as
# `config.lnbits-sensei.devEnv.lib` so config.nix and the example preset
# can build project entries with less boilerplate.
{ config, lib, ... }:
let
inherit (lib) mkOption types;
cfg = config.lnbits-sensei.devEnv;
# mkProject: shorthand constructor for `devEnv.projects.<name>`.
#
# Fills in conventional defaults so consumers write less boilerplate.
# The returned attrset matches the `projectType` submodule schema.
#
# mkProject {
# name = "lnbits";
# url = "git@git.example.com:you/lnbits.git";
# upstream = "https://github.com/lnbits/lnbits";
# worktrees = { main.branch = "main"; dev.branch = "dev"; };
# }
mkProject =
{
name ? null,
category ? null,
url ? null, # origin remote URL
upstream ? null,
fork ? null, # defaults from github.forkUser when upstream is set
worktrees ? { },
isClone ? false,
deployFlakeInput ? null,
worktreeRoot ? null, # defaults to name
}:
{
inherit
url
upstream
category
isClone
deployFlakeInput
;
fork =
if fork != null then
fork
else if upstream != null && cfg.github.forkUser != null && name != null then
"git@github.com:${cfg.github.forkUser}/${name}.git"
else
null;
worktreeRoot =
if worktreeRoot != null then
worktreeRoot
else if name != null then
name
else
"";
inherit worktrees;
};
# Resolve worktree filesystem paths. Projects with a category live
# under ${root}/${category}/${worktreeRoot}/<worktree>; otherwise
# ${root}/${worktreeRoot}/<worktree>.
worktreePath =
_projectName: project: worktreeName:
let
root = cfg.root;
sub =
if project.category != null then
"${project.category}/${project.worktreeRoot}"
else
project.worktreeRoot;
leaf =
let
wt = project.worktrees.${worktreeName} or null;
in
if wt != null && wt.path != null then wt.path else worktreeName;
in
"${root}/${sub}/${leaf}";
# Bare repo path (always ${root}/repos/<basename>.git). The basename
# is derived from the project name; the bare object DB is shared by
# every worktree of the project.
bareRepoPath = projectName: _project: "${cfg.root}/repos/${projectName}.git";
# Construct the remotes for a project. `origin` is `url` if set,
# otherwise falls back to `upstream` (pure-upstream tracking repo).
# Projects with neither end up with no origin (filtered out).
projectRemotes =
project:
lib.filterAttrs (_: v: v != null) {
origin =
if project.url != null then
project.url
else
project.upstream;
upstream = project.upstream;
fork = project.fork;
};
helpers = {
# Resolve a project's on-disk root given a project name.
# projectRoot = name: "${config.lnbits-sensei.devEnv.root}/${name}";
projectRoot = _name: throw "dev-env.lib.projectRoot: not yet implemented";
# Resolve a worktree path: <root>/<project>/<worktree>.
worktreePath =
_project: _worktree: throw "dev-env.lib.worktreePath: not yet implemented";
inherit
mkProject
worktreePath
bareRepoPath
projectRemotes
;
};
in
{