refactor!: switch to dendritic pattern

BREAKING CHANGE: complete reorganization of the repository
This commit is contained in:
2026-09-30 18:36:06 +02:00
parent 12684b4e9c
commit 5f4a7a4a42
323 changed files with 7117 additions and 7380 deletions
+50
View File
@@ -0,0 +1,50 @@
{config, ...}: let
m = config.flake.modules.homeManager;
in {
flake.modules.homeManager.ai = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.home.dev.ai;
jsonFormat = pkgs.formats.json {};
in {
imports = [
m.claude
m.ollama
m.opencode
];
options.home.dev.ai = {
enable = mkEnableOption "Enables AI features";
lmStudio = mkOption {
default = cfg.enable;
example = true;
description = "Enables LM Studio. Enabled by default when AI is enabled.";
};
mcpServers = mkOption {
inherit (jsonFormat) type;
default = {};
};
};
config = {
home = mkIf cfg.enable {
dev.ai = {
claude = {
enable = mkDefault cfg.enable;
mcpServers = mkDefault cfg.mcpServers;
};
ollama.enable = mkDefault cfg.enable;
opencode.enable = mkDefault cfg.enable;
};
packages = lists.optional cfg.lmStudio pkgs.lmstudio;
};
programs.mcp = mkIf (cfg.mcpServers != {}) {
enable = true;
servers = cfg.mcpServers;
};
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{
flake.modules.homeManager.claude = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.home.dev.ai.claude;
jsonFormat = pkgs.formats.json {};
in {
options.home.dev.ai.claude = {
enable = mkEnableOption "Enables Claude-related packages";
mcpServers = mkOption {
inherit (jsonFormat) type;
default = {};
};
};
config = mkIf cfg.enable {
home.packages = let
claude-jj = pkgs.writeShellScriptBin "claude-jj" ''
${pkgs.claude-code}/bin/claude --append-system-prompt 'CRITICAL: This repository uses Jujutsu (jj), NOT git. Never use git commands. Use jj equivalents.' "$@"
'';
in [claude-jj pkgs.sox];
programs.claude-code = {
inherit (cfg) enable mcpServers;
};
};
};
}
+34
View File
@@ -0,0 +1,34 @@
{
flake.modules.homeManager.ollama = {
config,
lib,
...
}:
with lib; let
cfg = config.home.dev.ai.ollama;
in {
options.home.dev.ai.ollama = {
enable = mkEnableOption "Enables Ollama";
gpu = mkOption {
type = types.nullOr (types.enum [false "rocm" "cuda"]);
example = "rocm";
default = null;
description = "Which type of GPU should be used for hardware acceleration";
};
};
config = {
services.ollama = mkIf cfg.enable {
inherit (cfg) enable;
acceleration = cfg.gpu;
host = "0.0.0.0";
environmentVariables = {
OLLAMA_CONTEXT_LENGTH = "8192";
OLLAMA_MAX_LOADED_MODELS = "1";
OLLAMA_KEEP_ALIVE = "10m";
};
};
home.sessionVariables.OLLAMA_API_BASE = "http://${config.services.ollama.host}:11434/";
};
};
}
+102
View File
@@ -0,0 +1,102 @@
{
flake.modules.homeManager.opencode = {
config,
lib,
pkgs,
bunBaseline,
...
}:
with lib; let
cfg = config.home.dev.ai.opencode;
# On x86_64-linux CPUs without AVX2, use the baseline bun variant
# when building opencode - the regular `bun` from nixpkgs requires
# AVX2 and crashes on older hardware, such as my Thinkpad x220’s
# Intel Core i5 2640M.
needsBaselineBun =
pkgs.stdenv.hostPlatform.isx86_64 && !pkgs.stdenv.hostPlatform.avx2Support;
defaultPackageCli =
if needsBaselineBun
then pkgs.opencode.override {bun = bunBaseline;}
else pkgs.opencode;
defaultPackageDesktop =
if needsBaselineBun
then pkgs.opencode-desktop.override {bun = bunBaseline;}
else pkgs.opencode-desktop;
corsList = domains: lists.remove "" (lists.forEach (strings.splitString "," domains) trim);
in {
options.home.dev.ai.opencode = {
enable = mkEnableOption "Enables OpenCode";
package = mkOption {
type = types.package;
default = defaultPackageCli;
description = "The CLI package for OpenCode";
};
settings = mkOption {
type = types.json;
default = {};
};
tui = mkOption {
type = types.json;
default = {};
};
desktop = {
enable = mkEnableOption "Enables the desktop app";
package = mkOption {
type = types.package;
default = defaultPackageDesktop;
description = "The desktop package for OpenCode";
};
};
web = {
enable = mkEnableOption "Enables OpenCode web";
extraArgs = mkOption {
type = types.listOf types.str;
default = [];
example = [
"--hostname"
"0.0.0.0"
];
};
cors = mkOption {
type = types.nullOr (types.either types.str types.path);
default = null;
example = "opencode.example.com,code.example.com";
description = ''
Either a string containing the domain allowed to connect to OpenCode’s web instance, or a file containing the target value. The latter is useful when using secret management if you don’t want to publicize the hostname of your OpenCode instance.
If you want to use multiple domains, you can separate them with a comma.
'';
};
mdns = {
enable = mkEnableOption "Enables mDNS with OpenCode";
hostname = mkOption {
type = types.nullOr types.str;
default = null;
example = "opencode.local";
};
};
};
};
config.programs.opencode = mkIf cfg.enable {
inherit (cfg) enable tui package;
enableMcpIntegration = true;
extraPackages = with pkgs; [uv] ++ lists.optional cfg.desktop.enable cfg.desktop.package;
settings =
{
server = mkIf cfg.web.mdns.enable {
mdns = true;
mdnsDomain = mkIf (cfg.web.mdns.hostname != null) cfg.web.mdns.hostname;
cors = corsList cfg.web.cors;
};
}
// cfg.settings;
web = {
inherit (cfg.web) enable extraArgs;
};
};
};
}
+24
View File
@@ -0,0 +1,24 @@
{config, ...}: let
m = config.flake.modules.homeManager;
in {
flake.modules.homeManager.dev = {
config,
lib,
...
}:
with lib; let
cfg = config.home.dev;
in {
imports = [
m.ai
m.editors
m.vcs
];
options.home.dev.fullDesktop = mkEnableOption "Enables everything except AI";
config.home.dev = {
vcs.fullDesktop = mkDefault cfg.fullDesktop;
editors.fullDesktop = mkDefault cfg.fullDesktop;
};
};
}
+24
View File
@@ -0,0 +1,24 @@
{config, ...}: let
m = config.flake.modules.homeManager;
in {
flake.modules.homeManager.editors = {
config,
lib,
...
}:
with lib; let
cfg = config.home.dev.editors;
in {
imports = [
m.emacs
];
options.home.dev.editors.fullDesktop = mkEnableOption "Enable all editors";
config.home.dev.editors.emacs = {
enable = mkDefault cfg.fullDesktop;
service = mkDefault cfg.fullDesktop;
mu4eMime = mkDefault cfg.fullDesktop;
org-protocol = mkDefault cfg.fullDesktop;
};
};
}
+70
View File
@@ -0,0 +1,70 @@
{
flake.modules.homeManager.emacs = {
pkgs,
config,
lib,
...
}:
with lib; let
emacsDefaultPackage = with pkgs; ((emacsPackagesFor emacs).emacsWithPackages (
epkgs:
with epkgs; [
mu4e
pdf-tools
]
));
cfg = config.home.dev.editors.emacs;
in {
options.home.dev.editors.emacs = {
enable = mkEnableOption "enables Emacs";
package = mkOption {
type = types.package;
default = emacsDefaultPackage;
};
service = mkEnableOption "enables Emacs service";
mu4eMime = mkEnableOption "Enables mu4e to handle mailto scheme";
org-protocol = mkEnableOption "Enables org-protocol";
};
config = {
home.packages = with pkgs; [
emacs-all-the-icons-fonts
emacs-lsp-booster
];
programs.emacs = mkIf cfg.enable {
enable = true;
inherit (cfg) package;
};
services.emacs = mkIf cfg.service {
enable = true;
inherit (cfg) package;
defaultEditor = true;
startWithUserSession = "graphical";
client = {
enable = true;
arguments = ["-c" "-a" "${cfg.package}/bin/emacs"];
};
};
xdg.desktopEntries.mu4e = mkIf cfg.mu4eMime {
name = "mu4e";
genericName = "mu4e";
comment = "Maildir Utils for Emacs";
mimeType = ["x-scheme-handler/mailto"];
noDisplay = true;
exec = "${cfg.package}/bin/emacsclient -c -n -a ${cfg.package}/bin/emacs -e \"(browse-url-mail \\\"\\$*\\\")\"";
terminal = false;
categories = ["Network" "Email" "TextEditor" "Utility"];
};
xdg.desktopEntries.org-protocol = mkIf cfg.org-protocol {
name = "org-protocol";
exec = "${cfg.package}/bin/emacsclient -c -n -a ${cfg.package}/bin/emacs %u";
terminal = false;
noDisplay = true;
categories = ["System"];
mimeType = ["x-scheme-handler/org-protocol"];
};
};
};
}
+287
View File
@@ -0,0 +1,287 @@
{
flake.modules.homeManager.git = {
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.home.dev.vcs.git;
in {
options.home.dev.vcs.git = {
enable = mkEnableOption "enables git";
email = mkOption {
type = types.str;
default = "lucien@phundrak.com";
};
name = mkOption {
type = types.str;
default = "Lucien Cartier-Tilet";
};
cliff = mkEnableOption "enables git-cliff support";
sendmail = {
enable = mkOption {
type = types.bool;
default = true;
};
server = mkOption {
type = types.nullOr types.str;
default = "mail.phundrak.com";
};
user = mkOption {
type = types.nullOr types.str;
default = null;
};
encryption = mkOption {
type = types.enum ["tls" "ssl" "none"];
default = "none";
};
port = mkOption {
type = types.nullOr types.int;
default = 587;
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the password necessary for authenticating
against the mailserver.
This file should contain the password only, with no newline.
'';
};
};
browser = mkOption {
type = types.nullOr types.str;
example = "${pkgs.firefox}/bin/firefox";
default = null;
};
completeConfig = mkEnableOption "Complete configuration for workstations";
emacs = {
integration = mkOption {
description = "enables Emacs integration";
type = types.bool;
default = config.home.dev.editors.emacs.enable;
};
pkg = mkOption {
type = types.package;
default = pkgs.emacs;
};
};
mergeTool = mkOption {
type = types.str;
default = "ediff";
};
editor = mkOption {
type = types.str;
default = "${pkgs.emacs}/bin/emacsclient -c -a ${pkgs.emacs}/bin/emacs";
};
publicKeyFile = mkOption {
type = types.nullOr types.str;
default = null;
};
};
config = lib.mkIf cfg.enable {
programs.git-cliff.enable = cfg.cliff;
programs.git = let
smtpEmail =
if (cfg.sendmail.user == null)
then cfg.email
else cfg.sendmail.user;
in {
enable = true;
ignores = [
".env"
".direnv/"
"*~"
"\#*\#"
"*.elc"
"auto-save-list"
".\#*"
"*_flymake.*"
"/auto/"
".projectile"
".dir-locals.el"
"# Org mode files"
".org-id-locations"
"*_archive"
"*.out"
"*.o"
"*.so"
"# Archives"
"*.7zz"
"*.dmg"
"*.gz"
"*.iso"
"*.jar"
"*.rar"
"*.tar"
"*.zip"
"*.log"
"*.sqlite"
"dist/"
];
signing = {
format =
if cfg.publicKeyFile != null
then "ssh"
else "openpgp";
key = cfg.publicKeyFile;
signByDefault = true;
};
settings = {
user = {
inherit (cfg) name email;
};
color.ui = "auto";
column.ui = "auto";
tag.sort = "version:refname";
core = mkIf cfg.completeConfig {
compression = 9;
inherit (cfg) editor;
whitespace = "fix,-indent-with-non-tab,trailing-space";
preloadindex = true;
};
status = {
branch = true;
showStash = true;
};
diff = {
algorithm = "histogram";
colorMoved = "plain";
mnemonicPrefix = true;
renames = "copy";
interHunkContext = 10;
};
gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";
init.defaultBranch = "main";
pull.rebase = true;
push = {
default = "simple";
autoSetupRemote = true;
followTags = true;
};
rebase = {
autoSquash = true;
autoStash = true;
missingCommitsCheck = "warn";
updateRefs = true;
};
help.autocorrect = "prompt";
web.browser = mkIf (cfg.browser != null) cfg.browser;
sendemail = mkIf cfg.sendmail.enable {
smtpserver = cfg.sendmail.server;
smtpuser = smtpEmail;
smtpencryption = cfg.sendmail.encryption;
smtpserverport = cfg.sendmail.port;
};
credentials = mkIf (cfg.sendmail.passwordFile != null) {
"smtp://${smtpEmail}@${cfg.sendmail.server}:${toString cfg.sendmail.port}" = {
helper = "cat ${cfg.sendmail.passwordFile}";
};
};
magithub = mkIf cfg.emacs.integration {
online = true;
"status" = {
includeStatusHeader = true;
includePullRequestsSection = true;
includeIssuesSection = true;
};
};
merge = {
tool = mkIf cfg.completeConfig cfg.mergeTool;
conflictstyle = "zdiff3";
};
mergetool.ediff.cmd = mkIf (cfg.emacs.integration && cfg.completeConfig) "\"${cfg.emacs.pkg} --eval \" (progn (defun ediff-write-merge-buffer () (let ((file ediff-merge-store-file)) (set-buffer ediff-buffer-C) (write-region (point-min) (point-max) file) (message \\\"Merge buffer saved in: %s\\\" file) (set-buffer-modified-p nil) (sit-for 1))) (setq ediff-quit-hook 'kill-emacs ediff-quit-merge-hook 'ediff-write-merge-buffer) (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" nil \\\"$MERGED\\\"))\"\"";
github.user = "phundrak";
url = {
"https://phundrak@github.com" = {
insteadOf = "https://github.com";
};
"https://phundrak@labs.phundrak.com" = {
insteadOf = "https://labs.phundrak.com";
};
"https://github.com/RustSec/advisory-db" = {
insteadOf = "https://github.com/RustSec/advisory-db";
};
"git@github.com:Phundrak/" = {
insteadOf = "pg:";
};
"git@labs.phundrak.com/phundrak:" = {
insteadOf = "p:";
};
"git@github.com" = {
insteadOf = "gh:";
};
"git@labs.phundrak.com" = {
insteadOf = "labs:";
};
};
alias = {
a = "add --all";
aca = "!git add --all && git commit --amend";
acan = "!git add --all && git commit --amend --no-edit";
ap = "add --patch";
b = "branch";
bd = "branch -d";
bdd = "branch -D";
c = "commit -S";
ca = "commit -Sa";
can = "commit -Sa --no-edit";
cm = "commit -Sm";
cam = "commit -Sam";
co = "checkout";
cob = "checkout -b";
cod = "checkout develop";
cl = "clone";
cl1 = "clone --depth 1";
f = "fetch";
fp = "fetch --prune";
ps = "push";
psf = "push --force-with-lease";
pso = "push origin";
psfo = "push --force-with-lease origin";
pushall = "!git remote \vert{} xargs -L1 git push";
psl = "!git remote \vert{} xargs -L1 git push";
pullall = "!git remote \vert{} xargs -L1 git pull";
pll = "!git remote \vert{} xargs -L1 git pull";
pl = "pull";
pb = "pull --rebase";
r = "rebase";
ra = "rebase --abort";
rc = "rebase --continue";
rd = "rebase develop";
ri = "rebase -i";
rmf = "rm -f";
rmd = "rm -r";
rmdf = "rm -rf";
sm = "submodule";
sms = "submodule status";
sma = "submodule add";
smu = "submodule update";
smui = "submodule update --init";
smuir = "submodule update --init --recursive";
st = "stash";
stc = "stash clear";
stp = "stash pop";
stw = "stash show";
u = "reset --";
d = "diff -w";
l = "log --all --oneline --graph --decorate --pretty=format':%C(magenta)%h %C(white) %an %ar%C(auto) %D%n%s%n'";
s = "status";
staged = "diff --cached";
upstream = "!git push -u origin HEAD";
unstage = "reset --";
};
};
};
};
};
}
+141
View File
@@ -0,0 +1,141 @@
{inputs, ...}: {
flake-file.inputs.jj-cz = {
url = "git+https://labs.phundrak.com/phundrak/jj-cz?ref=develop";
inputs.nixpkgs.follows = "nixpkgs";
};
flake.modules.homeManager.jujutsu = {
lib,
config,
pkgs,
...
}:
with lib; let
inherit (pkgs.stdenv.hostPlatform) system;
cfg = config.home.dev.vcs.jj;
jj-cz = inputs.jj-cz.packages.${system}.default;
in {
options.home.dev.vcs.jj = {
enable = mkEnableOption "enables jj";
name = mkOption {
type = types.str;
default = "Lucien Cartier-Tilet";
};
email = mkOption {
type = types.str;
default = "lucien@phundrak.com";
};
editor = mkOption {
type = types.str;
default =
if config.home.dev.editors.emacs.enable
then "${pkgs.emacs}/bin/emacsclient -c -a ${pkgs.emacs}/bin/emacs"
else "${pkgs.nano}/bin/nano";
};
signing = {
enable = mkEnableOption "enables signing jj commits";
sshKey = mkOption {
type = with types; nullOr (either path str);
example = "~/.ssh/id_ed25519.pub";
default = "${config.home.homeDirectory}/.ssh/id_ed25519.pub";
description = "Path to the private SSH key for signing.";
};
};
cz = {
enable = mkEnableOption "Enables jj-cz";
alias = mkEnableOption "Enable `jj cz` as an alias to `jj-cz`";
};
# used in my shell .nix files
shellCompletion = mkOption {
description = "Enable Jujutsu shell completion";
type = types.bool;
default = true;
};
};
config = mkIf cfg.enable {
home.packages = mkIf cfg.cz.enable [jj-cz];
programs.jujutsu = {
enable = true;
settings = {
user = {
inherit (cfg) name email;
};
ui = {
inherit (cfg) editor;
default-command = "st";
pager = ":builtin";
show-cryptographic-signatures = true;
diff-editor = ":builtin";
merge-editort = ":builtin";
# These are for vc-jj.el in Emacs
diff-formatter = ":git";
conflict-marker-style = "git";
};
signing = mkIf cfg.signing.enable {
behavior = "own";
backend = "ssh";
key = cfg.signing.sshKey;
backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers";
backends.ssh.program = "${pkgs.openssh}/bin/ssh-keygen";
};
aliases = {
blame = ["file" "annotate"];
consume = ["squash" "--into" "@" "--from"];
cz = mkIf cfg.cz.alias ["util" "exec" "--" "${jj-cz}/bin/jj-cz"];
eject = ["squash" "--from" "@" "--into"];
d = ["diff"];
dm = ["desc" "-m"];
gc = ["git" "clone"];
gcc = ["git" "clone" "--colocate"];
l = ["log"];
la = ["log" "-r" "::"];
lc = ["log" "-r" "(remote_bookmarks()..@)::"];
ll = ["log" "-T" "builtin_log_detailed"];
open = ["log" "-r" "open()"];
n = ["new"];
nd = ["new" "dev()"];
nt = ["new" "trunk()"];
revlog = ["evolog"];
s = ["show"];
tug = ["bookmark" "move" "--from" "heads(::@- & bookmarks())" "--to" "@-"];
};
colors.working_copy.underline = true;
git = {
private-commits = "blacklist()";
colocate = true;
subprocess = true;
};
revset-aliases = {
"immutable_heads()" = "present(trunk()) | tags()";
# Resolves by default to latest main/master remote bookmarks
"trunk()" = "latest((present(main) | present(master)) & remote_bookmarks())";
# Same as trunk() but for `dev` or `develop` bookmarks
"dev()" = "latest((present(dev) | present(develop)) & remote_bookmarks())";
"user(x)" = "author(x) | committer(x)";
"gh_pages()" = "ancestors(remote_bookmarks(exact:\"gh-pages\"))";
#Private and WIP commits that should never be pushed
"wip()" = "description(glob:\"wip:*\")";
"private()" = "description(glob:\"private:*\")";
"blacklist()" = "wip() | private()";
# stack(x, n) is the set of mutable commits reachable from
# 'x', with 'n' parents. 'n' is often useful to customize the
# display and return set for certain operations. 'x' can be
# used to target the set of 'roots' to traverse, e.g. @ is the
# current stack.
"stack()" = "ancestors(reachable(@, mutable()), 2)";
"stack(x)" = "ancestors(reachable(x, mutable()), 2)";
"stack(x, n)" = "ancestors(reachable(x, mutable()), n)";
"open()" = "stack(dev().. & mine(), 1)";
"ready()" = "open() ~ blacklist()::";
};
remotes.origin.auto-track-bookmarks = "*";
};
};
};
};
}
+61
View File
@@ -0,0 +1,61 @@
{config, ...}: let
m = config.flake.modules.homeManager;
in {
flake.modules.homeManager.vcs = {
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.home.dev.vcs;
in {
imports = [m.git m.jujutsu];
options.home.dev.vcs = {
fullDesktop = mkEnableOption "Enable all optional values";
name = mkOption {
type = types.str;
default = "Lucien Cartier-Tilet";
};
email = mkOption {
type = types.str;
default = "lucien@phundrak.com";
};
editor = mkOption {
type = types.str;
default = "${pkgs.emacs}/bin/emacsclient -c -a ${pkgs.emacs}/bin/emacs";
};
publicKey = {
content = mkOption {
type = types.nullOr types.str;
example = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5h";
default = null;
};
file = mkOption {
type = with types; nullOr path;
default = "/home/phundrak/.ssh/id_ed25519.pub";
};
};
};
config.home.dev.vcs = {
git = {
enable = mkDefault true;
inherit (cfg) name email editor;
publicKeyFile = cfg.publicKey.file;
cliff = mkDefault cfg.fullDesktop;
completeConfig = mkDefault cfg.fullDesktop;
};
jj = {
enable = mkDefault true;
inherit (cfg) name email editor;
signing.sshKey = mkDefault (cfg.publicKey.file or cfg.publicKey.content);
cz = {
enable = mkDefault true;
alias = mkDefault true;
};
};
};
};
}