feat(dev-env): aiolabs dev environment — options, lib, config, presets
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
02504f9fd3
commit
7960f82494
6 changed files with 1689 additions and 0 deletions
143
modules/dev-env/README.md
Normal file
143
modules/dev-env/README.md
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
# modules/dev-env
|
||||||
|
|
||||||
|
Declarative NixOS module for managing a multi-project dev environment.
|
||||||
|
|
||||||
|
## Design principles
|
||||||
|
|
||||||
|
1. **Nix owns the configuration**, bash owns the runtime. Nix renders
|
||||||
|
`/etc/dev-env/config.sh` and `projects.json`; installed bash scripts
|
||||||
|
source those at call time. Navigation helpers walk the filesystem at
|
||||||
|
runtime — adding a new branch is `git worktree add`, never
|
||||||
|
`nixos-rebuild`.
|
||||||
|
|
||||||
|
2. **The deploy flake is the single source of truth** for deployed refs.
|
||||||
|
Set `dev-env.deploy.flakeInput = "deploy-flake"` and projects will be
|
||||||
|
derived from `inputs.deploy-flake.inputs` whose URLs live under the
|
||||||
|
configured forgejo host. Hand-authored extras go in
|
||||||
|
`dev-env.projects`.
|
||||||
|
|
||||||
|
3. **Bootstrap is user-invoked, not an activation hook.**
|
||||||
|
`dev-env-bootstrap` materializes bare repos + worktrees. It is never
|
||||||
|
run during `nixos-rebuild` — rebuilds stay fast and offline.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `default.nix` | Imports options, lib, config. Module entry point. |
|
||||||
|
| `options.nix` | `mkOption` declarations for every knob. |
|
||||||
|
| `lib.nix` | `mkProject` constructor + `deriveFromFlakeInput` parser. |
|
||||||
|
| `config.nix` | Renders config files, installs scripts, wires git hooks. |
|
||||||
|
| `presets/aiolabs.nix` | Hand-authored project list for the aiolabs ecosystem. |
|
||||||
|
| `scripts/*.sh` | Ported + new bash helpers loaded via `builtins.readFile`. |
|
||||||
|
| `scripts/git-hooks/pre-commit` | Shared secret-scanner hook (via `core.hooksPath`). |
|
||||||
|
| `docs/*.md` | Runbooks shipped to the user's `~/Documents/dev-env/`. |
|
||||||
|
|
||||||
|
## Using from omni
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# omni/flake.nix
|
||||||
|
inputs.deploy-flake = {
|
||||||
|
url = "git+ssh://forgejo@git.atitlan.io/padreug/deploy-unified";
|
||||||
|
};
|
||||||
|
|
||||||
|
# omni/configuration.nix
|
||||||
|
imports = [
|
||||||
|
./modules/dev-env
|
||||||
|
./modules/dev-env/presets/aiolabs.nix # opt-in to aiolabs defaults
|
||||||
|
];
|
||||||
|
|
||||||
|
dev-env = {
|
||||||
|
enable = true;
|
||||||
|
forgejo.org = "aiolabs";
|
||||||
|
github.forkUser = "your-github-username";
|
||||||
|
deploy.flakeInput = "deploy-flake";
|
||||||
|
deploy.targets = {
|
||||||
|
host1 = "root@host1.example";
|
||||||
|
host2 = "root@host2.example";
|
||||||
|
host3 = "root@host3.example";
|
||||||
|
host4 = "root@host4.example";
|
||||||
|
host5 = "root@host5.example";
|
||||||
|
host6 = "root@host6.example";
|
||||||
|
host7 = "root@host7.example";
|
||||||
|
host8 = "root@host8.example";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bootstrap workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Rebuild with dev-env enabled
|
||||||
|
sudo nixos-rebuild switch --flake .#omni
|
||||||
|
|
||||||
|
# 2. Dry-run to see what will be created
|
||||||
|
dev-env-bootstrap --dry-run
|
||||||
|
|
||||||
|
# 3. Materialize bare repos + worktrees
|
||||||
|
dev-env-bootstrap
|
||||||
|
|
||||||
|
# 4. Navigate
|
||||||
|
lb dev # → ~/dev/lnbits/dev
|
||||||
|
bs # → ~/dev/bitspire/bitspire/dev
|
||||||
|
lam lightning-pub # → ~/dev/lamassu-next/lightning-pub/dev
|
||||||
|
prb lnbits fix-x # → ~/dev/upstream-prs/lnbits-fix-x on upstream/main
|
||||||
|
|
||||||
|
# 5. Deploy
|
||||||
|
dev-deploy host5 # uses locked deploy-flake input
|
||||||
|
dev-deploy --local host1 # overrides inputs with local worktrees
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending
|
||||||
|
|
||||||
|
Add a new project in `presets/aiolabs.nix` (or a new preset):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
dev-env.projects.my-new-project = lib.mkProject {
|
||||||
|
category = "shared";
|
||||||
|
upstream = "https://github.com/example/my-project";
|
||||||
|
worktrees.main.branch = "main";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then rebuild and `dev-env-bootstrap`. Existing worktrees are never
|
||||||
|
clobbered — the bootstrap script refuses to touch a worktree whose
|
||||||
|
current branch differs from the declared one.
|
||||||
|
|
||||||
|
## What this module does NOT do
|
||||||
|
|
||||||
|
- Manage bitSpire's internal `flake.nix`/`devenv.nix` — that project
|
||||||
|
has its own dev environment.
|
||||||
|
- Run `git fetch`/`git pull` automatically — use `wts`/`wtu` helpers or
|
||||||
|
a manual `git fetch --all`.
|
||||||
|
- Replace `direnv` per-worktree `.envrc` files — it just makes sure
|
||||||
|
`nix-direnv` is enabled (omni's home.nix already does this).
|
||||||
|
|
||||||
|
## Reference docs (`docs/`)
|
||||||
|
|
||||||
|
LNbits-domain guides that travel with this module — so the `lnbits` pack
|
||||||
|
is a self-contained dev environment whether used on omni or imported
|
||||||
|
standalone (`nixosModules.lnbits`). These were consolidated here from the
|
||||||
|
former standalone `lnbits-sensei` scaffold, which this pack supersedes.
|
||||||
|
|
||||||
|
- [`lnbits-workspace-notes.md`](docs/lnbits-workspace-notes.md) — practical
|
||||||
|
day-to-day gotchas: ports, `LNBITS_SRC` build-context traps,
|
||||||
|
extension-folder-upgrade wiping forks, settings precedence (`.env` vs
|
||||||
|
DB), Nostr keys, CLINK scope, fork versioning.
|
||||||
|
- [`lnbits-extension-dev.md`](docs/lnbits-extension-dev.md) — building
|
||||||
|
extensions: auth-decorator distinctions, FakeWallet vs regtest testing,
|
||||||
|
the `migrations_fork.py` pattern.
|
||||||
|
- [`lnbits-frontend-gotchas.md`](docs/lnbits-frontend-gotchas.md) —
|
||||||
|
Vue/Quasar UMD traps (no self-closing tags, CSS specificity vs Quasar
|
||||||
|
`!important`, cache busting, dark-mode discipline).
|
||||||
|
- [`lnbits-upstream-flow.md`](docs/lnbits-upstream-flow.md) — how
|
||||||
|
`lnbits/lnbits` itself moves (dev/main branch model, squash-merge,
|
||||||
|
release tagging).
|
||||||
|
- [`upstream-prs.md`](docs/upstream-prs.md) — sending a PR upstream via the
|
||||||
|
`~/dev/upstream-prs/` worktree flow (with a fork/PR primer).
|
||||||
|
- [`remotes.md`](docs/remotes.md) — the three remote-topology patterns
|
||||||
|
(upstream-only / github-fork / multi-remote with a private host).
|
||||||
|
- [`secrets-management.md`](docs/secrets-management.md) — getting secrets
|
||||||
|
out of `.env` into sops-encrypted YAML.
|
||||||
|
- [`stack-overview.md`](docs/stack-overview.md) — the aiolabs stack map
|
||||||
|
(which repo is what, how the pieces fit).
|
||||||
355
modules/dev-env/config.nix
Normal file
355
modules/dev-env/config.nix
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
inputs ? { },
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkIf
|
||||||
|
mkDefault
|
||||||
|
mkMerge
|
||||||
|
optional
|
||||||
|
;
|
||||||
|
cfg = config.dev-env;
|
||||||
|
helpers = cfg.lib;
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Auto-derive projects from the deploy flake's inputs.
|
||||||
|
#
|
||||||
|
# Reads the deploy flake's flake.lock at eval time, walks every
|
||||||
|
# direct input declared by the deploy flake, filters to URLs under
|
||||||
|
# our configured forgejo host, and constructs project entries.
|
||||||
|
# Multiple inputs pointing at the same repo with different refs are
|
||||||
|
# collapsed into a single project with multiple worktrees via
|
||||||
|
# groupDerivedProjects (e.g., webapp + webapp-dev → one `webapp`
|
||||||
|
# project with two worktrees, main and dev).
|
||||||
|
#
|
||||||
|
# Returns {} when:
|
||||||
|
# - deriveProjectsFromInputs = false
|
||||||
|
# - flakeInput = null
|
||||||
|
# - the input isn't in scope (e.g. smoke test running without
|
||||||
|
# specialArgs.inputs)
|
||||||
|
#
|
||||||
|
# Reading the lock file directly is more robust than walking
|
||||||
|
# inputs.<flake>.inputs because the lock file has a stable,
|
||||||
|
# documented shape regardless of flake-compat quirks.
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
derivedProjects =
|
||||||
|
if
|
||||||
|
cfg.deploy.deriveProjectsFromInputs
|
||||||
|
&& cfg.deploy.flakeInput != null
|
||||||
|
&& (inputs ? ${cfg.deploy.flakeInput})
|
||||||
|
then
|
||||||
|
let
|
||||||
|
deployFlake = inputs.${cfg.deploy.flakeInput};
|
||||||
|
lockData = builtins.fromJSON (builtins.readFile "${deployFlake}/flake.lock");
|
||||||
|
|
||||||
|
# Root node enumerates the deploy flake's direct inputs as
|
||||||
|
# { inputName = nodeName; ... }. A few inputs reference shared
|
||||||
|
# nodes via a list (inherited inputs); we drop those since
|
||||||
|
# they aren't the direct declarations we care about.
|
||||||
|
rootInputs = lib.filterAttrs (_: v: builtins.isString v) (lockData.nodes.root.inputs or { });
|
||||||
|
|
||||||
|
# Reconstruct a URL in the format parseForgejoUrl expects
|
||||||
|
# (git+<scheme>://host/org/repo[.git][?ref=branch]) from a
|
||||||
|
# flake.lock node. Lock entries store the URL without the
|
||||||
|
# `git+` prefix and put the ref in a separate field, so we
|
||||||
|
# normalize both.
|
||||||
|
#
|
||||||
|
# The `original` block records what the user declared. When
|
||||||
|
# they declared `?ref=main` explicitly, original.ref is set.
|
||||||
|
# When they declared no ref (implicit default branch), the
|
||||||
|
# `locked` block still has the resolved ref — but in the
|
||||||
|
# form `refs/heads/main`. We fall back to that and strip
|
||||||
|
# the prefix so the output matches the format parseForgejoUrl
|
||||||
|
# expects.
|
||||||
|
normalizeLockEntry =
|
||||||
|
nodeName:
|
||||||
|
let
|
||||||
|
node = lockData.nodes.${nodeName} or { };
|
||||||
|
src = node.original or node.locked or { };
|
||||||
|
url = src.url or "";
|
||||||
|
rawRef =
|
||||||
|
if src ? ref && src.ref != null then
|
||||||
|
src.ref
|
||||||
|
else if node ? locked && node.locked ? ref then
|
||||||
|
node.locked.ref
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
ref = if rawRef == null then null else lib.removePrefix "refs/heads/" rawRef;
|
||||||
|
refSuffix = if ref != null then "?ref=${ref}" else "";
|
||||||
|
in
|
||||||
|
if url == "" then null else "git+${url}${refSuffix}";
|
||||||
|
|
||||||
|
parsed = lib.mapAttrsToList (
|
||||||
|
inputName: nodeName:
|
||||||
|
let
|
||||||
|
url = normalizeLockEntry nodeName;
|
||||||
|
in
|
||||||
|
if url == null then null else helpers.deriveFromFlakeInput { inherit inputName url; }
|
||||||
|
) rootInputs;
|
||||||
|
in
|
||||||
|
helpers.groupDerivedProjects parsed
|
||||||
|
else
|
||||||
|
{ };
|
||||||
|
|
||||||
|
# Final project set — hand-authored entries from cfg.projects
|
||||||
|
# completely override derived ones on key collision (shallow merge
|
||||||
|
# via //). Keys only in derivedProjects get auto-populated; keys in
|
||||||
|
# cfg.projects use the hand-authored version as-is.
|
||||||
|
#
|
||||||
|
# Rationale: deep-merge would give inconsistent behavior because
|
||||||
|
# cfg.projects entries already have every submodule default applied
|
||||||
|
# (e.g. `upstream = null`), and those defaults would silently
|
||||||
|
# replace derived values. Shallow merge forces the user to write
|
||||||
|
# complete entries when overriding — predictable and obvious.
|
||||||
|
mergedProjects = derivedProjects // cfg.projects;
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
|
||||||
|
# Where a non-worktree clone would land
|
||||||
|
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)
|
||||||
|
forgejoRepo
|
||||||
|
upstream
|
||||||
|
githubFork
|
||||||
|
category
|
||||||
|
worktreeRoot
|
||||||
|
isClone
|
||||||
|
deployFlakeInput
|
||||||
|
;
|
||||||
|
barePath = bare;
|
||||||
|
clonePath = clonePath;
|
||||||
|
remotes = remotes;
|
||||||
|
worktrees = resolvedWorktrees;
|
||||||
|
};
|
||||||
|
|
||||||
|
resolvedProjects = lib.mapAttrs resolveProject mergedProjects;
|
||||||
|
|
||||||
|
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 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 WEBAPP_DIR="${cfg.root}/webapp"
|
||||||
|
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 BITSPIRE_DIR="${cfg.root}/bitspire"
|
||||||
|
export LAMASSU_NEXT_DIR="${cfg.root}/lamassu-next"
|
||||||
|
|
||||||
|
export FORGEJO_HOST="${cfg.forgejo.host}"
|
||||||
|
export FORGEJO_SSH="${cfg.forgejo.sshUser}@${cfg.forgejo.host}"
|
||||||
|
export FORGEJO_ORG="${cfg.forgejo.org}"
|
||||||
|
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
|
||||||
|
)}
|
||||||
|
|
||||||
|
# List of all deploy hosts (bash array)
|
||||||
|
export DEPLOY_TARGETS=(${
|
||||||
|
lib.concatStringsSep " " (lib.mapAttrsToList (host: _: ''"${host}"'') 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) that get loaded by
|
||||||
|
# /etc/profile.d/dev-env-functions.sh into every interactive shell.
|
||||||
|
shellFnSources = [
|
||||||
|
./scripts/nav.sh
|
||||||
|
./scripts/worktree.sh
|
||||||
|
./scripts/pr-helpers.sh
|
||||||
|
]
|
||||||
|
++ optional cfg.regtest.enable ./scripts/regtest.sh;
|
||||||
|
|
||||||
|
shellFnLoader = pkgs.writeText "dev-env-functions.sh" ''
|
||||||
|
# Auto-generated by 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;
|
||||||
|
# Shared bash libraries sourced by the scripts at runtime —
|
||||||
|
# config loader (function modules) + colour palette (bins).
|
||||||
|
"dev-env/lib.sh".source = ./scripts/lib.sh;
|
||||||
|
"dev-env/lib-colors.sh".source = ./scripts/lib-colors.sh;
|
||||||
|
};
|
||||||
|
|
||||||
|
# 2) Loader so interactive shells (login OR non-login) get the
|
||||||
|
# functions. We can't rely on /etc/profile.d/*.sh alone
|
||||||
|
# because NixOS only sources that from /etc/profile (login
|
||||||
|
# shells). Hyprland-launched terminals (Alacritty, etc.)
|
||||||
|
# are interactive non-login shells, so they would never see
|
||||||
|
# these functions. `environment.interactiveShellInit` is
|
||||||
|
# sourced by both /etc/bashrc and /etc/zshrc on every
|
||||||
|
# interactive shell, which is what we want. We still install
|
||||||
|
# the file under /etc/profile.d for ssh-without-tty cases
|
||||||
|
# and for users who want to source it explicitly.
|
||||||
|
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 user dirs exist (only the leaf state dir;
|
||||||
|
# everything else is created by dev-env-bootstrap on demand).
|
||||||
|
systemd.tmpfiles.rules = lib.optional (config.dev-env.user or null != null) (
|
||||||
|
let
|
||||||
|
user = config.dev-env.user;
|
||||||
|
in
|
||||||
|
"d /home/${user}/.local/state/dev-env 0755 ${user} users -"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5) regtest implies docker. Set the standard option directly rather
|
||||||
|
# than toggling omni.features.containers, so this module is
|
||||||
|
# importable without omni. On omni the developer preset still
|
||||||
|
# enables features.containers (→ the fuller docker block in
|
||||||
|
# core.nix), and this mkDefault yields to it.
|
||||||
|
(mkIf cfg.regtest.enable {
|
||||||
|
virtualisation.docker.enable = mkDefault true;
|
||||||
|
})
|
||||||
|
|
||||||
|
# 6) 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 && (config.dev-env.user or null) != null) {
|
||||||
|
home-manager.users.${config.dev-env.user} =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
home.file.".local/share/dev-env/git-hooks/pre-commit" = {
|
||||||
|
source = ./scripts/git-hooks/pre-commit;
|
||||||
|
executable = true;
|
||||||
|
};
|
||||||
|
programs.git.settings.core.hooksPath = "/home/${config.dev-env.user}/.local/share/dev-env/git-hooks";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# 7) Optional legacy compat — also write the old .devenv.conf so any
|
||||||
|
# loose bash scripts still reading it keep working during migration.
|
||||||
|
(mkIf (cfg.legacyConfigFile != null && (config.dev-env.user or null) != null) {
|
||||||
|
home-manager.users.${config.dev-env.user} =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
home.file.${
|
||||||
|
# home.file is keyed relative to $HOME, so strip the prefix
|
||||||
|
lib.removePrefix "/home/${config.dev-env.user}/" cfg.legacyConfigFile
|
||||||
|
} =
|
||||||
|
{
|
||||||
|
text = ''
|
||||||
|
# Legacy compat shim — sourced by old .devenv.d/*.sh scripts.
|
||||||
|
# Canonical config is /etc/dev-env/config.sh.
|
||||||
|
source /etc/dev-env/config.sh
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
17
modules/dev-env/default.nix
Normal file
17
modules/dev-env/default.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# dev-env module entry point.
|
||||||
|
#
|
||||||
|
# This module declaratively manages a multi-project development environment
|
||||||
|
# (bare repos, worktrees, navigation helpers, tmux sessions, regtest docker
|
||||||
|
# env, upstream-PR workflow). It sits on top of omni and consumes the
|
||||||
|
# unified deploy flake as a source of truth for deployed refs.
|
||||||
|
#
|
||||||
|
# The module only configures tools; it does not materialize repos on disk.
|
||||||
|
# That is the job of the user-invoked `dev-env-bootstrap` script installed
|
||||||
|
# by config.nix.
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./options.nix
|
||||||
|
./lib.nix
|
||||||
|
./config.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
241
modules/dev-env/lib.nix
Normal file
241
modules/dev-env/lib.nix
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
cfg = config.dev-env;
|
||||||
|
|
||||||
|
# Parse a forgejo URL into { namespace, repo, ref }.
|
||||||
|
#
|
||||||
|
# Accepts:
|
||||||
|
# git+ssh://forgejo@host/org/repo.git?ref=branch
|
||||||
|
# git+ssh://forgejo@host/org/repo?ref=branch
|
||||||
|
# git+https://host/org/repo.git?ref=branch
|
||||||
|
# git+https://host/org/repo?ref=branch
|
||||||
|
#
|
||||||
|
# Returns null if the URL does not match a forgejo host.
|
||||||
|
#
|
||||||
|
# Implementation note: Nix's `builtins.match` uses POSIX ERE, which
|
||||||
|
# does NOT support non-greedy quantifiers (`+?`/`*?`). A naive regex
|
||||||
|
# like `([^?]+?)(\.git)?` lets the `+?` act greedy, producing a repo
|
||||||
|
# name of `webapp.git`. Instead we match everything before `?ref=`
|
||||||
|
# or end-of-string as the raw repo name, then strip `.git` after.
|
||||||
|
parseForgejoUrl =
|
||||||
|
url:
|
||||||
|
let
|
||||||
|
host = cfg.forgejo.host;
|
||||||
|
# One regex covering both schemes. Groups:
|
||||||
|
# 1: namespace
|
||||||
|
# 2: repo (possibly with .git suffix)
|
||||||
|
# 3: optional "?ref=..." (kept for completeness)
|
||||||
|
# 4: optional ref value
|
||||||
|
sshMatch = builtins.match "git\\+ssh://[^@]+@${host}/([^/]+)/([^?]+)(\\?ref=(.+))?" url;
|
||||||
|
httpsMatch = builtins.match "git\\+https://${host}/([^/]+)/([^?]+)(\\?ref=(.+))?" url;
|
||||||
|
m =
|
||||||
|
if sshMatch != null then
|
||||||
|
sshMatch
|
||||||
|
else if httpsMatch != null then
|
||||||
|
httpsMatch
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
in
|
||||||
|
if m == null then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
{
|
||||||
|
namespace = builtins.elemAt m 0;
|
||||||
|
repo = lib.removeSuffix ".git" (builtins.elemAt m 1);
|
||||||
|
ref = builtins.elemAt m 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
# mkProject: shorthand constructor for `dev-env.projects.<name>`.
|
||||||
|
#
|
||||||
|
# Fills in conventional defaults so users write less boilerplate.
|
||||||
|
# The returned attrset matches the `projectType` submodule schema.
|
||||||
|
mkProject =
|
||||||
|
{
|
||||||
|
name ? null,
|
||||||
|
category ? null,
|
||||||
|
upstream ? null,
|
||||||
|
forgejoRepo ? null, # defaults to "${cfg.forgejo.org}/${name}"
|
||||||
|
originUrl ? null, # override origin URL (e.g. GitHub-only repos)
|
||||||
|
githubFork ? null, # defaults from github.forkUser
|
||||||
|
worktrees ? { },
|
||||||
|
isClone ? false,
|
||||||
|
deployFlakeInput ? null,
|
||||||
|
worktreeRoot ? null, # defaults to name
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
forgejoRepo =
|
||||||
|
if forgejoRepo != null then
|
||||||
|
forgejoRepo
|
||||||
|
else if originUrl != null then
|
||||||
|
null
|
||||||
|
else if name != null then
|
||||||
|
"${cfg.forgejo.org}/${name}"
|
||||||
|
else
|
||||||
|
throw "dev-env.lib.mkProject: set forgejoRepo, originUrl, or name";
|
||||||
|
inherit
|
||||||
|
originUrl
|
||||||
|
upstream
|
||||||
|
category
|
||||||
|
isClone
|
||||||
|
deployFlakeInput
|
||||||
|
;
|
||||||
|
githubFork =
|
||||||
|
if githubFork != null then
|
||||||
|
githubFork
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
# deriveFromFlakeInput: parse one flake input url into a project entry
|
||||||
|
# (or null if the URL doesn't belong to our forgejo).
|
||||||
|
#
|
||||||
|
# Input: { name = "webapp-host5"; url = "git+ssh://forgejo@git.atitlan.io/aiolabs/webapp.git?ref=host5"; }
|
||||||
|
# Output: { namespace = "aiolabs"; repo = "webapp"; ref = "host5"; }
|
||||||
|
deriveFromFlakeInput =
|
||||||
|
{ inputName, url }:
|
||||||
|
let
|
||||||
|
parsed = parseForgejoUrl url;
|
||||||
|
in
|
||||||
|
if parsed == null then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inputName = inputName;
|
||||||
|
repo = parsed.repo;
|
||||||
|
namespace = parsed.namespace;
|
||||||
|
ref = parsed.ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Collapse a list of `{ inputName, namespace, repo, ref }` into
|
||||||
|
# `{ <repo> = { forgejoRepo, worktrees = { <ref> = { branch = ref; }; ... }; }; }`.
|
||||||
|
#
|
||||||
|
# Multiple inputs can share the same repo with different refs; this is
|
||||||
|
# the common case for webapp + webapp-dev — both become worktrees of
|
||||||
|
# `webapp` (main and dev respectively).
|
||||||
|
groupDerivedProjects =
|
||||||
|
parsedList:
|
||||||
|
lib.foldl' (
|
||||||
|
acc: entry:
|
||||||
|
if entry == null || entry.ref == null then
|
||||||
|
acc
|
||||||
|
else
|
||||||
|
let
|
||||||
|
key = entry.repo;
|
||||||
|
existing =
|
||||||
|
acc.${key} or {
|
||||||
|
forgejoRepo = "${entry.namespace}/${entry.repo}";
|
||||||
|
originUrl = null;
|
||||||
|
worktreeRoot = entry.repo;
|
||||||
|
worktrees = { };
|
||||||
|
upstream = null;
|
||||||
|
githubFork = null;
|
||||||
|
category = null;
|
||||||
|
isClone = false;
|
||||||
|
deployFlakeInput = null;
|
||||||
|
};
|
||||||
|
newWorktree = {
|
||||||
|
branch = entry.ref;
|
||||||
|
path = null;
|
||||||
|
remote = "origin";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
acc
|
||||||
|
// {
|
||||||
|
${key} = existing // {
|
||||||
|
worktrees = existing.worktrees // {
|
||||||
|
${entry.ref} = newWorktree;
|
||||||
|
};
|
||||||
|
# Record the LAST input name that contributed — good enough
|
||||||
|
# for --override-input wiring.
|
||||||
|
deployFlakeInput = entry.inputName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) { } parsedList;
|
||||||
|
|
||||||
|
# Resolve worktree filesystem paths. Projects with 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).
|
||||||
|
# Falls back to projectName when forgejoRepo is null (GitHub-only projects).
|
||||||
|
bareRepoPath =
|
||||||
|
projectName: project:
|
||||||
|
let
|
||||||
|
basename =
|
||||||
|
if project.forgejoRepo != null then
|
||||||
|
lib.last (lib.splitString "/" project.forgejoRepo)
|
||||||
|
else
|
||||||
|
projectName;
|
||||||
|
in
|
||||||
|
"${cfg.root}/repos/${basename}.git";
|
||||||
|
|
||||||
|
# Construct the remotes for a project. `origin` is `originUrl` if set,
|
||||||
|
# otherwise derived from the forgejo host + forgejoRepo. Projects with
|
||||||
|
# neither end up with no origin (filtered out below).
|
||||||
|
projectRemotes =
|
||||||
|
project:
|
||||||
|
lib.filterAttrs (_: v: v != null) {
|
||||||
|
origin =
|
||||||
|
if project.originUrl or null != null then
|
||||||
|
project.originUrl
|
||||||
|
else if project.forgejoRepo != null then
|
||||||
|
"${cfg.forgejo.sshUser}@${cfg.forgejo.host}:${project.forgejoRepo}.git"
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
upstream = project.upstream;
|
||||||
|
github-fork = project.githubFork;
|
||||||
|
};
|
||||||
|
|
||||||
|
helpers = {
|
||||||
|
inherit
|
||||||
|
mkProject
|
||||||
|
deriveFromFlakeInput
|
||||||
|
parseForgejoUrl
|
||||||
|
groupDerivedProjects
|
||||||
|
worktreePath
|
||||||
|
bareRepoPath
|
||||||
|
projectRemotes
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.dev-env.lib = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
internal = true;
|
||||||
|
description = "dev-env module helper functions (internal).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
dev-env.lib = helpers;
|
||||||
|
};
|
||||||
|
}
|
||||||
369
modules/dev-env/options.nix
Normal file
369
modules/dev-env/options.nix
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) mkEnableOption mkOption types;
|
||||||
|
|
||||||
|
# One entry in dev-env.projects.
|
||||||
|
#
|
||||||
|
# A project is a git repo with one or more worktrees (or a single clone
|
||||||
|
# if `isClone = true`). Worktrees are derived from a bare repo at
|
||||||
|
# `${dev-env.root}/repos/${name}.git` by default; that path is managed
|
||||||
|
# by the bootstrap script.
|
||||||
|
projectType = types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
forgejoRepo = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
<org>/<repo> on the configured forgejo host. Used to construct
|
||||||
|
the `origin` remote. May be null for projects whose origin
|
||||||
|
lives outside forgejo — in that case set `originUrl`.
|
||||||
|
'';
|
||||||
|
example = "aiolabs/lnbits";
|
||||||
|
};
|
||||||
|
|
||||||
|
originUrl = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Override the origin remote URL. By default, origin is derived
|
||||||
|
from the forgejo host + forgejoRepo. Set this to clone from
|
||||||
|
an arbitrary URL (e.g. a GitHub repo that has no forgejo mirror).
|
||||||
|
'';
|
||||||
|
example = "https://github.com/nostr-protocol/nips";
|
||||||
|
};
|
||||||
|
|
||||||
|
upstream = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
HTTPS URL of the upstream OSS repo. Set to null for projects
|
||||||
|
with no upstream (divergent branches, original work).
|
||||||
|
When set, the `upstream` remote is added to the bare repo and
|
||||||
|
the `prb`/`prc`/rebase helpers know how to sync from it.
|
||||||
|
'';
|
||||||
|
example = "https://github.com/lnbits/lnbits";
|
||||||
|
};
|
||||||
|
|
||||||
|
githubFork = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Full SSH URL of the personal github fork used for upstream PRs
|
||||||
|
(the `github-fork` remote). If null, derived from the global
|
||||||
|
`dev-env.github.forkUser` + the basename of `forgejoRepo`.
|
||||||
|
'';
|
||||||
|
example = "git@github.com:your-github-username/lnbits.git";
|
||||||
|
};
|
||||||
|
|
||||||
|
category = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Sub-directory under `dev-env.root` that groups related
|
||||||
|
projects. e.g. "bitspire" groups the bitSpire frontend +
|
||||||
|
atm-tui. null puts the project at the top level.
|
||||||
|
'';
|
||||||
|
example = "bitspire";
|
||||||
|
};
|
||||||
|
|
||||||
|
worktreeRoot = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = if name == null then "" else name;
|
||||||
|
defaultText = "project name";
|
||||||
|
description = ''
|
||||||
|
Directory name (relative to `category` or root) under which
|
||||||
|
worktrees live. Defaults to the project name.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
worktrees = mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
branch = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Branch to check out in this worktree.";
|
||||||
|
};
|
||||||
|
path = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Override the worktree directory name. Defaults to the
|
||||||
|
attribute key.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
remote = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "origin";
|
||||||
|
description = ''
|
||||||
|
Which remote the branch tracks. Usually origin (forgejo)
|
||||||
|
but could be "upstream" for read-only tracking worktrees.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Worktrees to materialize for this project, keyed by worktree
|
||||||
|
name. Each worktree becomes a directory at
|
||||||
|
`''${dev-env.root}/''${category}/''${worktreeRoot}/<key>` (or under
|
||||||
|
root if category is null).
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
dev = { branch = "dev"; };
|
||||||
|
main = { branch = "main"; };
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
isClone = mkEnableOption ''
|
||||||
|
Treat this project as a regular clone rather than a bare-repo
|
||||||
|
worktree set. Used for projects that won't have multiple
|
||||||
|
simultaneous branches checked out (e.g. atm-tui)
|
||||||
|
'';
|
||||||
|
|
||||||
|
deployFlakeInput = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Name of the flake input in the deploy flake that this project
|
||||||
|
corresponds to. Used by `dev-deploy --local` to know which
|
||||||
|
--override-input to pass.
|
||||||
|
'';
|
||||||
|
example = "lnbits";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# One entry in dev-env.tmux.sessions.
|
||||||
|
tmuxSessionType = types.submodule {
|
||||||
|
options = {
|
||||||
|
cwd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Session-default cwd, relative to `dev-env.root`. Windows can
|
||||||
|
override with their own cwd.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
windows = mkOption {
|
||||||
|
type = types.listOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
name = mkOption { type = types.str; };
|
||||||
|
cwd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Window cwd, relative to dev-env.root.";
|
||||||
|
};
|
||||||
|
cmd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Command to run in the window on creation.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.dev-env = {
|
||||||
|
enable = mkEnableOption "dev-env multi-project development environment";
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "user";
|
||||||
|
description = ''
|
||||||
|
Owner of the dev tree. Drives `root`'s default and the per-user
|
||||||
|
git-hooks / state-dir paths. Set this to your login user when
|
||||||
|
importing dev-env standalone; on omni the host wires it to
|
||||||
|
`settings.user`. Kept self-contained (a plain default, no
|
||||||
|
`config.omni.*` read) so this pack is importable without omni.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
root = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/home/${config.dev-env.user}/dev";
|
||||||
|
defaultText = "/home/\${config.dev-env.user}/dev";
|
||||||
|
description = ''
|
||||||
|
Root directory for the dev environment. Bare repos live under
|
||||||
|
`''${root}/repos/`, worktrees under `''${root}/<category>/<project>/`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
forgejo = {
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "git.atitlan.io";
|
||||||
|
description = "Forgejo server hostname.";
|
||||||
|
};
|
||||||
|
sshUser = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "forgejo";
|
||||||
|
description = ''
|
||||||
|
SSH user for git+ssh access to forgejo. Some installations use
|
||||||
|
"git", others use "forgejo". Defaults to "forgejo" because the
|
||||||
|
reference deployment uses that.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
org = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Default forgejo org/user under which your forks live. Used as
|
||||||
|
the namespace when a project's `forgejoRepo` is a bare name.
|
||||||
|
'';
|
||||||
|
example = "aiolabs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
github = {
|
||||||
|
forkUser = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Personal github username used for upstream PR forks (the
|
||||||
|
`github-fork` remote). When set, projects that declare an
|
||||||
|
upstream automatically get a github-fork remote derived from
|
||||||
|
`git@github.com:<forkUser>/<basename>.git`.
|
||||||
|
'';
|
||||||
|
example = "your-github-username";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
projects = mkOption {
|
||||||
|
type = types.attrsOf projectType;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Hand-authored project list. This is merged with projects derived
|
||||||
|
from the deploy flake (when `deploy.deriveProjectsFromInputs` is
|
||||||
|
true). Hand entries win on key collision.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
deploy = {
|
||||||
|
flakeInput = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Name of the omni flake input that points at the unified
|
||||||
|
deploy flake. When set, the bootstrap script clones it to
|
||||||
|
`''${root}/deploy/<flakeInput>` as a working copy, and
|
||||||
|
`dev-deploy` uses it as the --flake source by default.
|
||||||
|
'';
|
||||||
|
example = "deploy-flake";
|
||||||
|
};
|
||||||
|
|
||||||
|
targets = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Map from hostname to SSH target used by `dev-deploy`. Mirrors
|
||||||
|
the TARGETS associative array in optimize-deploys/unified/
|
||||||
|
deploy.sh.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
host1 = "root@host1.example";
|
||||||
|
host5 = "root@host5.example";
|
||||||
|
host7 = "root@host7.example";
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
deriveProjectsFromInputs = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
When true and `flakeInput` is set, derive a default project
|
||||||
|
entry for every flake input of the deploy flake whose URL is
|
||||||
|
under the forgejo host. Hand-authored `projects` win on key
|
||||||
|
collision.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
regtest = {
|
||||||
|
enable = mkEnableOption ''
|
||||||
|
Unified Bitcoin/Lightning regtest docker environment. Enables
|
||||||
|
virtualisation.docker (via mkDefault)
|
||||||
|
'';
|
||||||
|
|
||||||
|
repoUrl = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "forgejo@git.atitlan.io:aiolabs/lnbits-regtest.git";
|
||||||
|
description = ''
|
||||||
|
Git URL of the regtest docker-compose repo. Cloned to
|
||||||
|
`''${root}/local/docker/regtest` by the bootstrap script.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
tmux = {
|
||||||
|
enable = mkEnableOption "declarative tmux session launcher (dev-tm)";
|
||||||
|
|
||||||
|
sessions = mkOption {
|
||||||
|
type = types.attrsOf tmuxSessionType;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Named tmux session layouts. The `dev-tm <name>` script reads
|
||||||
|
these from /etc/dev-env/tmux-sessions.json at runtime and
|
||||||
|
recreates the session.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
gitHooks = {
|
||||||
|
enable =
|
||||||
|
mkEnableOption ''
|
||||||
|
Shared pre-commit hook via core.hooksPath. Installs a single
|
||||||
|
hook that scans for secrets (prv keys, hardcoded passwords,
|
||||||
|
unencrypted sops files) in all repos under dev-env.root
|
||||||
|
''
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
path = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "${config.dev-env.root or "/tmp"}/.git-hooks";
|
||||||
|
defaultText = ''"''${root}/.git-hooks"'';
|
||||||
|
description = "Directory used for git core.hooksPath.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
writeDirenvHints = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
When true, the bootstrap script writes a default `.envrc`
|
||||||
|
containing `use flake` into each worktree that has a flake.nix
|
||||||
|
but no existing .envrc. Never clobbers existing files.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
legacyConfigFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = "${config.dev-env.root or "/tmp"}/.devenv.conf";
|
||||||
|
defaultText = ''"''${root}/.devenv.conf"'';
|
||||||
|
description = ''
|
||||||
|
Path at which to also write a legacy `.devenv.conf` for any
|
||||||
|
loose bash scripts still reading it. The canonical config is at
|
||||||
|
/etc/dev-env/config.sh; this is a transitional aid. Set to null
|
||||||
|
to disable once migration is complete.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
564
modules/dev-env/presets/aiolabs.nix
Normal file
564
modules/dev-env/presets/aiolabs.nix
Normal file
|
|
@ -0,0 +1,564 @@
|
||||||
|
# aiolabs preset — opinionated project list for the aiolabs ecosystem
|
||||||
|
#
|
||||||
|
# Reproduces the current ~/dev tree declaratively. Importing this file
|
||||||
|
# is opt-in; the dev-env module itself ships no projects by default.
|
||||||
|
#
|
||||||
|
# Includes the projects pinned by optimize-deploys/unified/flake.nix
|
||||||
|
# (which dev-env.deploy.deriveProjectsFromInputs would derive
|
||||||
|
# automatically when wired up) plus the dev-only worktrees that don't
|
||||||
|
# have a deploy artifact.
|
||||||
|
#
|
||||||
|
# Tmux sessions mirror the layouts in ~/dev/.devenv.d/20-tmux-sessions.sh
|
||||||
|
# but expressed as a Nix attrset rendered to JSON for dev-tm.
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
mk = config.dev-env.lib.mkProject;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
# Gated: applies only when the aiolabs pack is enabled. Imported (inert)
|
||||||
|
# via modules/packs/aiolabs.nix. Enabling aiolabs implies lnbits.
|
||||||
|
config = lib.mkIf config.omni.packs.aiolabs.enable {
|
||||||
|
omni.packs.lnbits.enable = true;
|
||||||
|
|
||||||
|
dev-env = {
|
||||||
|
forgejo.org = lib.mkDefault "aiolabs";
|
||||||
|
github.forkUser = lib.mkDefault "your-github-username";
|
||||||
|
|
||||||
|
deploy.targets = lib.mkDefault {
|
||||||
|
host1 = "root@host1.example";
|
||||||
|
host2 = "root@host2.example";
|
||||||
|
host3 = "root@host3.example";
|
||||||
|
host4 = "root@host4.example";
|
||||||
|
host5 = "root@host5.example";
|
||||||
|
host6 = "root@host6.example";
|
||||||
|
host7 = "root@host7.example";
|
||||||
|
host8 = "root@host8.example";
|
||||||
|
};
|
||||||
|
|
||||||
|
projects = {
|
||||||
|
|
||||||
|
# ─── lnbits: shared codebase, dev/main branches ────────────────
|
||||||
|
|
||||||
|
lnbits = mk {
|
||||||
|
name = "lnbits";
|
||||||
|
category = null;
|
||||||
|
upstream = "https://github.com/lnbits/lnbits";
|
||||||
|
deployFlakeInput = "lnbits";
|
||||||
|
worktrees = {
|
||||||
|
dev = {
|
||||||
|
branch = "dev";
|
||||||
|
};
|
||||||
|
main = {
|
||||||
|
branch = "main";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── nsecbunkerd: nostr remote signer daemon (NIP-46) ──────────
|
||||||
|
|
||||||
|
nsecbunkerd = mk {
|
||||||
|
name = "nsecbunkerd";
|
||||||
|
category = null;
|
||||||
|
upstream = "https://github.com/kind-0/nsecbunkerd";
|
||||||
|
worktrees = {
|
||||||
|
master = {
|
||||||
|
branch = "master";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── webapp: divergent per-target branches ─────────────────────
|
||||||
|
|
||||||
|
webapp = mk {
|
||||||
|
name = "webapp";
|
||||||
|
category = null;
|
||||||
|
upstream = null; # no upstream — original work
|
||||||
|
deployFlakeInput = "webapp";
|
||||||
|
isClone = true; # single branch — per-target config lives
|
||||||
|
# in the deploy flake's services.webapp.*
|
||||||
|
# options, not in separate branches
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── boilerplate-website: vue+vite starter, spawn worktrees per site ─
|
||||||
|
|
||||||
|
boilerplate-website = mk {
|
||||||
|
name = "boilerplate-website";
|
||||||
|
category = null;
|
||||||
|
upstream = null; # original work
|
||||||
|
worktrees = {
|
||||||
|
main = {
|
||||||
|
branch = "main";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── ATM stack ──────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# bitspire (active) groups the bitSpire ATM frontend + atm-tui.
|
||||||
|
# lamassu-next (legacy, retained until the last machines migrate)
|
||||||
|
# groups the lamassu-next frontend + the lightning-pub fork it
|
||||||
|
# used. nostr-lightning-wallet is a single clone at top level.
|
||||||
|
|
||||||
|
bitspire = mk {
|
||||||
|
name = "bitspire";
|
||||||
|
category = "bitspire";
|
||||||
|
forgejoRepo = "aiolabs/bitspire";
|
||||||
|
upstream = null; # original
|
||||||
|
worktrees = {
|
||||||
|
main = {
|
||||||
|
branch = "main";
|
||||||
|
};
|
||||||
|
dev = {
|
||||||
|
branch = "dev";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
lamassu-next = mk {
|
||||||
|
name = "lamassu-next";
|
||||||
|
category = "lamassu-next";
|
||||||
|
forgejoRepo = "aiolabs/lamassu-next";
|
||||||
|
upstream = null;
|
||||||
|
deployFlakeInput = "lamassu-next";
|
||||||
|
worktrees = {
|
||||||
|
main = {
|
||||||
|
branch = "main";
|
||||||
|
};
|
||||||
|
dev = {
|
||||||
|
branch = "dev";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
lightning-pub = mk {
|
||||||
|
name = "lightning-pub";
|
||||||
|
category = "lamassu-next";
|
||||||
|
forgejoRepo = "aiolabs/lightning-pub";
|
||||||
|
upstream = "https://github.com/shocknet/Lightning.Pub";
|
||||||
|
worktrees = {
|
||||||
|
master = {
|
||||||
|
branch = "master";
|
||||||
|
};
|
||||||
|
dev = {
|
||||||
|
branch = "dev";
|
||||||
|
};
|
||||||
|
nip05 = {
|
||||||
|
branch = "feature/nip05";
|
||||||
|
};
|
||||||
|
marketplace = {
|
||||||
|
branch = "feature/marketplace";
|
||||||
|
};
|
||||||
|
withdraw = {
|
||||||
|
branch = "feature/withdraw";
|
||||||
|
};
|
||||||
|
extension-loader = {
|
||||||
|
branch = "feature/extension-loader";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nostr-lightning-wallet = mk {
|
||||||
|
name = "nostr-lightning-wallet";
|
||||||
|
category = null;
|
||||||
|
forgejoRepo = "aiolabs/shockwallet-vue";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true; # single clone, has its own flake
|
||||||
|
deployFlakeInput = "shockwallet-vue";
|
||||||
|
};
|
||||||
|
|
||||||
|
atm-tui = mk {
|
||||||
|
name = "atm-tui";
|
||||||
|
category = "bitspire";
|
||||||
|
forgejoRepo = "aiolabs/atm-tui";
|
||||||
|
upstream = null; # original
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── shared OSS forks ────────────────────────────────────────────
|
||||||
|
|
||||||
|
lamassu-server = mk {
|
||||||
|
name = "lamassu-server";
|
||||||
|
category = "shared";
|
||||||
|
forgejoRepo = "aiolabs/lamassu-server";
|
||||||
|
upstream = "https://github.com/lamassu/lamassu-server";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
lamassu-machine = mk {
|
||||||
|
name = "lamassu-machine";
|
||||||
|
category = "shared";
|
||||||
|
forgejoRepo = "aiolabs/lamassu-machine";
|
||||||
|
upstream = "https://github.com/lamassu/lamassu-machine";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
nix-bitcoin = mk {
|
||||||
|
name = "nix-bitcoin";
|
||||||
|
category = "shared";
|
||||||
|
forgejoRepo = "aiolabs/nix-bitcoin";
|
||||||
|
upstream = "https://github.com/fort-nix/nix-bitcoin";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── lnbits extensions (forked + original) ───────────────────────
|
||||||
|
|
||||||
|
ext-nostrrelay = mk {
|
||||||
|
name = "nostrrelay";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/nostrrelay";
|
||||||
|
upstream = "https://github.com/lnbits/nostrrelay";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-nostrclient = mk {
|
||||||
|
name = "nostrclient";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/nostrclient";
|
||||||
|
upstream = "https://github.com/lnbits/nostrclient";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-nostrmarket = mk {
|
||||||
|
name = "nostrmarket";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/nostrmarket";
|
||||||
|
upstream = "https://github.com/lnbits/nostrmarket";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-events = mk {
|
||||||
|
name = "events";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/events";
|
||||||
|
upstream = "https://github.com/lnbits/events";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-spirekeeper = mk {
|
||||||
|
name = "spirekeeper";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/spirekeeper";
|
||||||
|
upstream = null; # original — bitSpire operator dashboard
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-libra = mk {
|
||||||
|
name = "libra";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/libra";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-restaurant = mk {
|
||||||
|
name = "restaurant";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/restaurant";
|
||||||
|
upstream = null; # original
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-withdraw = mk {
|
||||||
|
name = "withdraw";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/withdraw";
|
||||||
|
upstream = "https://github.com/lnbits/withdraw";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-lnurlp = mk {
|
||||||
|
name = "lnurlp";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/lnurlp";
|
||||||
|
upstream = "https://github.com/lnbits/lnurlp";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-splitpayments = mk {
|
||||||
|
name = "splitpayments";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/splitpayments";
|
||||||
|
upstream = "https://github.com/lnbits/splitpayments";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ext-tasks = mk {
|
||||||
|
name = "tasks";
|
||||||
|
category = "shared/extensions";
|
||||||
|
forgejoRepo = "aiolabs/tasks";
|
||||||
|
upstream = null; # original
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── community-organizer surfaces ───────────────────────────────
|
||||||
|
#
|
||||||
|
# Matrix-side bot plugins + the foyer e-ink renderer. Both consume
|
||||||
|
# the NIP-52/NIP-72 community-organizer protocol defined in
|
||||||
|
# ~/dev/maubot-plugins/docs/community-organizer-spec.md and feed
|
||||||
|
# into the wider aiolabs Nostr-native stack (see /etc/nixos/docs/
|
||||||
|
# system-map.md for the end-to-end picture).
|
||||||
|
|
||||||
|
maubot-plugins = mk {
|
||||||
|
name = "maubot-plugins";
|
||||||
|
category = null;
|
||||||
|
forgejoRepo = "aiolabs/maubot-plugins";
|
||||||
|
upstream = null; # original
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
inky-impression = mk {
|
||||||
|
name = "inky-impression";
|
||||||
|
category = null;
|
||||||
|
forgejoRepo = "aiolabs/inky-impression";
|
||||||
|
upstream = null; # original
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── auxiliary ──────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# Note: host5-home and atitlanio-pitch are both auto-derived from
|
||||||
|
# the server-deploy flake's inputs, so they don't need preset
|
||||||
|
# entries. See modules/dev-env/config.nix `derivedProjects`.
|
||||||
|
|
||||||
|
# The deploy flake itself (also a project so dev-env-bootstrap
|
||||||
|
# clones it for in-place editing alongside everything else).
|
||||||
|
# Not derived from the flake because a flake can't list itself
|
||||||
|
# as an input — this must be hand-authored.
|
||||||
|
server-deploy = mk {
|
||||||
|
name = "server-deploy";
|
||||||
|
category = "deploy";
|
||||||
|
forgejoRepo = "aiolabs/server-deploy";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Regtest environment (Bitcoin/Lightning local dev)
|
||||||
|
regtest = mk {
|
||||||
|
name = "regtest";
|
||||||
|
category = "local/docker";
|
||||||
|
forgejoRepo = "aiolabs/regtest";
|
||||||
|
upstream = "https://github.com/lnbits/legend-regtest-enviroment";
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Curated upstream-reference catalogue + weekly digests.
|
||||||
|
# Bootstrap clones the repo to ~/dev/refs/; bin/refresh
|
||||||
|
# then materialises repos/<group>/<owner>/<name> from refs.toml.
|
||||||
|
# The home-manager refs-refresh systemd timer runs that weekly;
|
||||||
|
# for first-bootstrap immediacy, run `~/dev/refs/bin/refresh`.
|
||||||
|
refs = mk {
|
||||||
|
name = "refs";
|
||||||
|
category = null;
|
||||||
|
forgejoRepo = "aiolabs/refs";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Documentation (Quartz static site)
|
||||||
|
castle-docs = mk {
|
||||||
|
name = "castle-docs";
|
||||||
|
category = "docs";
|
||||||
|
forgejoRepo = "padreug/castle-docs";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── reference material ─────────────────────────────────────────
|
||||||
|
|
||||||
|
nips = mk {
|
||||||
|
name = "nips";
|
||||||
|
category = "nostr-protocol";
|
||||||
|
originUrl = "https://github.com/nostr-protocol/nips";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
quasar = mk {
|
||||||
|
name = "quasar";
|
||||||
|
category = "vue-framework";
|
||||||
|
originUrl = "https://github.com/quasarframework/quasar";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Lightning.Pub + wallet2 upstreams now live as read-only mirrors
|
||||||
|
# under ~/dev/refs/ (see refs.toml), not as project worktrees.
|
||||||
|
|
||||||
|
extension-builder-stub = mk {
|
||||||
|
name = "extension_builder_stub";
|
||||||
|
category = "lnbits-reference";
|
||||||
|
originUrl = "https://github.com/lnbits/extension_builder_stub";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
regtest-upstream = mk {
|
||||||
|
name = "regtest-upstream";
|
||||||
|
category = "lnbits-upstream";
|
||||||
|
originUrl = "https://github.com/lnbits/legend-regtest-enviroment";
|
||||||
|
upstream = null;
|
||||||
|
isClone = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── tmux sessions (mirror the existing layouts) ──────────────────
|
||||||
|
|
||||||
|
tmux.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
tmux.sessions = {
|
||||||
|
lnbits = {
|
||||||
|
cwd = "lnbits";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "dev";
|
||||||
|
cwd = "lnbits/dev";
|
||||||
|
cmd = "nvim .";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "main";
|
||||||
|
cwd = "lnbits/main";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "extensions";
|
||||||
|
cwd = "shared/extensions";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "term";
|
||||||
|
cwd = "lnbits/dev";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "git";
|
||||||
|
cwd = "lnbits/dev";
|
||||||
|
cmd = "lazygit";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
webapp = {
|
||||||
|
cwd = "webapp";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "edit";
|
||||||
|
cwd = "webapp";
|
||||||
|
cmd = "nvim .";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "term";
|
||||||
|
cwd = "webapp";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "git";
|
||||||
|
cwd = "webapp";
|
||||||
|
cmd = "lazygit";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
shared = {
|
||||||
|
cwd = "shared";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "extensions";
|
||||||
|
cwd = "shared/extensions";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "lamassu";
|
||||||
|
cwd = "shared/lamassu-server";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "nix-bitcoin";
|
||||||
|
cwd = "shared/nix-bitcoin";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "git";
|
||||||
|
cwd = "shared";
|
||||||
|
cmd = "wts";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
local = {
|
||||||
|
cwd = "local";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "docker";
|
||||||
|
cwd = "local/docker";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "lazydocker";
|
||||||
|
cwd = "local/docker";
|
||||||
|
cmd = "lazydocker";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "logs";
|
||||||
|
cwd = "local";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
bitspire = {
|
||||||
|
cwd = "bitspire/bitspire";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "dev";
|
||||||
|
cwd = "bitspire/bitspire/dev";
|
||||||
|
cmd = "nvim .";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "main";
|
||||||
|
cwd = "bitspire/bitspire/main";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "atm-tui";
|
||||||
|
cwd = "bitspire/atm-tui";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "git";
|
||||||
|
cwd = "bitspire/bitspire/dev";
|
||||||
|
cmd = "lazygit";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
lightning-pub = {
|
||||||
|
cwd = "lamassu-next/lightning-pub";
|
||||||
|
windows = [
|
||||||
|
{
|
||||||
|
name = "dev";
|
||||||
|
cwd = "lamassu-next/lightning-pub/dev";
|
||||||
|
cmd = "nvim .";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "master";
|
||||||
|
cwd = "lamassu-next/lightning-pub/master";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "extensions";
|
||||||
|
cwd = "lamassu-next/lightning-pub/extension-loader";
|
||||||
|
cmd = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "git";
|
||||||
|
cwd = "lamassu-next/lightning-pub/dev";
|
||||||
|
cmd = "lazygit";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue