This repository has been archived on 2026-06-22. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
lnbits-sensei/modules/dev-env/config.nix
Padreug e38d313db2 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>
2026-06-15 21:18:49 +02:00

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";
};
})
]);
}