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,153 @@
# Standalone smoke test for the dev-env module.
#
# Builds a minimal nixosConfiguration that imports the dev-env module
# (plus core.nix for the `lnbits-sensei.*` option namespace and a
# minimum-bootable stub) and exercises a representative slice of the
# option schema:
#
# - worktree-based project with upstream + fork (3 remotes)
# - worktree-based project without upstream (origin only)
# - isClone project with upstream
# - pure-upstream project (origin falls back to upstream)
# - minimal project (mkProject defaults)
# - tmux session schema
# - deploy targets
#
# Consumed by the top-level flake.nix as `checks.${system}.dev-env-smoke`
# so `nix flake check` catches schema regressions without building a
# full system.
#
# This is NOT bootable as a real system — the fileSystem/bootloader
# stubs only exist to satisfy module evaluation. Do not try to deploy.
{ nixpkgs, home-manager }:
nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
home-manager.nixosModules.home-manager
# The option namespace (lnbits-sensei.*) and the dev-env module.
../../core.nix
../default.nix
# Minimal host stub + dev-env exercise.
(
{ config, ... }:
{
# ---- Minimum bootable stubs (never actually booted) ----
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
};
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
nixpkgs.hostPlatform = "x86_64-linux";
system.stateVersion = "24.11";
# home-manager wants these set even with zero real users.
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# ---- Exercise the dev-env schema ----
lnbits-sensei = {
enable = true;
user = "smoke";
devEnv = {
enable = true;
scaffoldPath = "/home/smoke/dev/lnbits-sensei";
# Explicit root — don't depend on the user-derived default.
root = "/tmp/dev-env-smoke";
github.forkUser = "testuser";
deploy.targets = {
host-a = "root@10.0.0.1";
host-b = "root@10.0.0.2";
};
# Disable regtest to avoid dragging docker into the eval.
regtest.enable = false;
tmux = {
enable = true;
sessions.dev = {
cwd = "lnbits";
windows = [
{
name = "dev";
cwd = "lnbits/dev";
cmd = "nvim .";
}
{
name = "term";
cwd = "lnbits/dev";
cmd = null;
}
];
};
};
# Use mkProject so we exercise the lib helper in addition to
# the raw submodule schema.
projects =
let
mk = config.lnbits-sensei.devEnv.lib.mkProject;
in
{
# Worktree-based with upstream + fork (3 remotes).
worktree-with-upstream = mk {
name = "worktree-with-upstream";
category = "shared";
url = "git@github.com:testuser/worktree-with-upstream.git";
upstream = "https://github.com/upstream-org/worktree-with-upstream";
worktrees = {
main.branch = "main";
dev.branch = "dev";
feature.branch = "feature/x";
};
};
# Worktree-based, no upstream (origin only).
worktree-no-upstream = mk {
name = "worktree-no-upstream";
category = "shared";
url = "git@github.com:testuser/worktree-no-upstream.git";
worktrees = {
alpha.branch = "alpha";
beta.branch = "beta";
};
};
# Single-clone with upstream (rebase helper target).
clone-with-upstream = mk {
name = "clone-with-upstream";
category = "test";
url = "git@github.com:testuser/clone-with-upstream.git";
upstream = "https://github.com/upstream-org/clone-with-upstream";
isClone = true;
};
# Pure-upstream — origin falls back to upstream.
pure-upstream = mk {
name = "pure-upstream";
category = "refs";
upstream = "https://github.com/upstream-org/pure-upstream";
worktrees.master.branch = "master";
};
# Minimal project — mkProject defaults fill everything.
minimal = mk {
name = "minimal";
url = "git@github.com:testuser/minimal.git";
};
};
};
};
}
)
];
}