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>
213 lines
7.2 KiB
Nix
213 lines
7.2 KiB
Nix
# lnbits-sensei dev-env — wire-up.
|
|
#
|
|
# Nix owns the configuration; bash owns the runtime. This file renders
|
|
# the machine-readable config (/etc/dev-env/config.sh + projects.json +
|
|
# tmux-sessions.json), installs the helper scripts on PATH, loads the
|
|
# shell-function modules into interactive shells, and wires the shared
|
|
# git pre-commit hook. It never materializes repos on disk — that is the
|
|
# job of the user-invoked `dev-env-bootstrap` script.
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib) mkIf mkMerge;
|
|
cfg = config.lnbits-sensei.devEnv;
|
|
user = config.lnbits-sensei.user;
|
|
helpers = cfg.lib;
|
|
|
|
# Resolve a project's complete shape (paths + remotes) once, so the
|
|
# JSON renderer and the bash scripts both see identical data.
|
|
resolveProject =
|
|
name: project:
|
|
let
|
|
bare = helpers.bareRepoPath name project;
|
|
remotes = helpers.projectRemotes project;
|
|
|
|
cloneCategoryDir =
|
|
if project.category != null then "${cfg.root}/${project.category}" else cfg.root;
|
|
clonePath = "${cloneCategoryDir}/${project.worktreeRoot}";
|
|
|
|
resolvedWorktrees = lib.mapAttrs (wtName: wt: {
|
|
inherit (wt) branch remote;
|
|
path = helpers.worktreePath name project wtName;
|
|
}) project.worktrees;
|
|
in
|
|
{
|
|
inherit (project)
|
|
upstream
|
|
fork
|
|
category
|
|
worktreeRoot
|
|
isClone
|
|
deployFlakeInput
|
|
;
|
|
barePath = bare;
|
|
clonePath = clonePath;
|
|
remotes = remotes;
|
|
worktrees = resolvedWorktrees;
|
|
};
|
|
|
|
resolvedProjects = lib.mapAttrs resolveProject cfg.projects;
|
|
|
|
projectsJson = pkgs.writeText "dev-env-projects.json" (builtins.toJSON resolvedProjects);
|
|
|
|
tmuxSessionsJson = pkgs.writeText "dev-env-tmux-sessions.json" (builtins.toJSON cfg.tmux.sessions);
|
|
|
|
# Render /etc/dev-env/config.sh — the bash-readable runtime config.
|
|
# Provides DEV_ROOT, REPOS_DIR, etc. and per-host DEPLOY_TARGET_<HOST>
|
|
# env vars (the format dev-deploy looks for).
|
|
renderConfigSh = pkgs.writeText "dev-env-config.sh" ''
|
|
# Auto-generated by the lnbits-sensei dev-env module — do not edit.
|
|
# Source from /etc/dev-env/config.sh
|
|
|
|
export DEV_ROOT="${cfg.root}"
|
|
export REPOS_DIR="${cfg.root}/repos"
|
|
export LNBITS_DIR="${cfg.root}/lnbits"
|
|
export DEPLOY_DIR="${cfg.root}/deploy"
|
|
export SHARED_DIR="${cfg.root}/shared"
|
|
export LOCAL_DIR="${cfg.root}/local"
|
|
export DOCS_DIR="${cfg.root}/docs"
|
|
export UPSTREAM_PRS_DIR="${cfg.root}/upstream-prs"
|
|
|
|
export GITHUB_SSH="git@github.com"
|
|
${lib.optionalString (cfg.github.forkUser != null) ''
|
|
export GITHUB_FORK_USER="${cfg.github.forkUser}"
|
|
''}
|
|
|
|
export DEVENV_PROJECTS_JSON="/etc/dev-env/projects.json"
|
|
export DEVENV_WRITE_DIRENV_HINTS="${if cfg.writeDirenvHints then "1" else "0"}"
|
|
${lib.optionalString (cfg.deploy.flakeInput != null) ''
|
|
export DEVENV_DEPLOY_FLAKE_INPUT="${cfg.deploy.flakeInput}"
|
|
''}
|
|
|
|
# Deploy targets — one env var per host
|
|
${lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (
|
|
host: target: ''export DEPLOY_TARGET_${lib.replaceStrings [ "-" ] [ "_" ] host}="${target}"''
|
|
) cfg.deploy.targets
|
|
)}
|
|
'';
|
|
|
|
# Bash script wrappers — load source verbatim from ./scripts/*.sh.
|
|
# Using readFile keeps editor tooling/shellcheck working on the .sh
|
|
# files.
|
|
mkScriptBin = name: src: pkgs.writeShellScriptBin name (builtins.readFile src);
|
|
|
|
# Sourceable bash modules (functions only) loaded by
|
|
# /etc/profile.d/dev-env-functions.sh into every interactive shell.
|
|
shellFnSources = [
|
|
./scripts/nav.sh
|
|
./scripts/worktree.sh
|
|
./scripts/pr-helpers.sh
|
|
]
|
|
++ lib.optional cfg.regtest.enable ./scripts/regtest.sh;
|
|
|
|
shellFnLoader = pkgs.writeText "dev-env-functions.sh" ''
|
|
# Auto-generated by the lnbits-sensei dev-env module.
|
|
# Sources every dev-env shell-function module into the current shell.
|
|
${lib.concatMapStringsSep "\n" (src: ''
|
|
if [[ -r ${src} ]]; then
|
|
# shellcheck disable=SC1090
|
|
source ${src}
|
|
fi
|
|
'') shellFnSources}
|
|
'';
|
|
|
|
in
|
|
{
|
|
config = mkIf cfg.enable (mkMerge [
|
|
{
|
|
# 1) /etc/dev-env/* config files (machine-readable)
|
|
environment.etc = {
|
|
"dev-env/config.sh".source = renderConfigSh;
|
|
"dev-env/projects.json".source = projectsJson;
|
|
"dev-env/tmux-sessions.json".source = tmuxSessionsJson;
|
|
};
|
|
|
|
# 2) Loader so interactive shells (login OR non-login) get the
|
|
# functions. NixOS only sources /etc/profile.d/*.sh from
|
|
# /etc/profile (login shells); GUI-launched terminals are
|
|
# interactive non-login shells. `interactiveShellInit` is
|
|
# sourced by both /etc/bashrc and /etc/zshrc on every
|
|
# interactive shell, which is what we want.
|
|
environment.etc."profile.d/dev-env-functions.sh".source = shellFnLoader;
|
|
environment.interactiveShellInit = ''
|
|
if [[ -r /etc/profile.d/dev-env-functions.sh ]]; then
|
|
# shellcheck disable=SC1091
|
|
source /etc/profile.d/dev-env-functions.sh
|
|
fi
|
|
'';
|
|
|
|
# 3) System packages — every standalone helper.
|
|
environment.systemPackages = [
|
|
# core deps used by every script
|
|
pkgs.git
|
|
pkgs.jq
|
|
|
|
# standalone helpers
|
|
(mkScriptBin "dev-env-bootstrap" ./scripts/bootstrap.sh)
|
|
(mkScriptBin "dev-status" ./scripts/status.sh)
|
|
(mkScriptBin "dev-tm" ./scripts/tmux-launch.sh)
|
|
(mkScriptBin "dev-deploy" ./scripts/deploy.sh)
|
|
(mkScriptBin "rebase" ./scripts/rebase.sh)
|
|
]
|
|
++ lib.optionals cfg.regtest.enable [
|
|
(mkScriptBin "regtest-start" (
|
|
pkgs.writeShellScript "rs" ''
|
|
source ${./scripts/regtest.sh}
|
|
regtest-start "$@"
|
|
''
|
|
))
|
|
(mkScriptBin "regtest-stop" (
|
|
pkgs.writeShellScript "rs2" ''
|
|
source ${./scripts/regtest.sh}
|
|
regtest-stop "$@"
|
|
''
|
|
))
|
|
(mkScriptBin "regtest-status" (
|
|
pkgs.writeShellScript "rs3" ''
|
|
source ${./scripts/regtest.sh}
|
|
regtest-status "$@"
|
|
''
|
|
))
|
|
(mkScriptBin "regtest-lnbits-rebuild" (
|
|
pkgs.writeShellScript "rs5" ''
|
|
source ${./scripts/regtest.sh}
|
|
regtest-lnbits-rebuild "$@"
|
|
''
|
|
))
|
|
(mkScriptBin "regtest-lnbits-restart" (
|
|
pkgs.writeShellScript "rs6" ''
|
|
source ${./scripts/regtest.sh}
|
|
regtest-lnbits-restart "$@"
|
|
''
|
|
))
|
|
];
|
|
|
|
# 4) tmpfiles to ensure the rebase-log state dir exists. Everything
|
|
# else is created by dev-env-bootstrap on demand.
|
|
systemd.tmpfiles.rules = [
|
|
"d /home/${user}/.local/state/dev-env 0755 ${user} ${user} -"
|
|
];
|
|
}
|
|
|
|
# 5) Shared git pre-commit via core.hooksPath, applied per-user via
|
|
# home-manager so the user's git config picks it up.
|
|
(mkIf cfg.gitHooks.enable {
|
|
home-manager.users.${user} =
|
|
{ ... }:
|
|
{
|
|
home.file.".local/share/lnbits-sensei/git-hooks/pre-commit" = {
|
|
source = ./scripts/git-hooks/pre-commit;
|
|
executable = true;
|
|
};
|
|
programs.git.settings.core.hooksPath =
|
|
"/home/${user}/.local/share/lnbits-sensei/git-hooks";
|
|
};
|
|
})
|
|
]);
|
|
}
|