Compare commits

...

7 Commits

Author SHA1 Message Date
4c29259470 rose pine 2026-03-12 19:01:53 +00:00
7b72236b4c yes 2026-03-12 19:01:53 +00:00
60120d46bf win 2026-03-12 19:01:53 +00:00
b64a7416c9 fix 2026-03-12 19:01:53 +00:00
3138f6ce11 the big restyling 2026-03-12 19:01:53 +00:00
6569d7d4d8 tighten service boundaries and clean up config structure 2026-03-12 19:01:53 +00:00
eae286c5ab update oc.nvim 2026-03-12 19:01:53 +00:00
25 changed files with 1017 additions and 643 deletions

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
# NixOS Config
Personal Nix flake for four machines:
- `michael` - x86_64 Linux server
- `tahani` - x86_64 Linux home server / workstation
- `chidi` - aarch64 Darwin work laptop
- `jason` - aarch64 Darwin personal laptop
## Repository Map
- `modules/` - flake-parts modules, auto-imported via `import-tree`
- `modules/_hosts/` - host-specific submodules like hardware, disks, and services
- `modules/_lib/` - local helper functions
- `apps/` - Nushell apps exposed through the flake
- `secrets/` - SOPS-encrypted secrets
- `flake.nix` - generated flake entrypoint
- `modules/dendritic.nix` - source of truth for flake inputs and `flake.nix` generation
## How It Is Structured
This repo uses `den` and organizes configuration around aspects instead of putting everything directly in host files.
- shared behavior lives in `den.aspects.<name>.<class>` modules
- hosts are declared in `modules/hosts.nix`
- host composition happens in `modules/<host>.nix`
- user-level config mostly lives in Home Manager aspects
Common examples:
- `modules/core.nix` - shared Nix and shell foundation
- `modules/dev-tools.nix` - VCS, language, and developer tooling
- `modules/network.nix` - SSH, fail2ban, and tailscale aspects
- `modules/michael.nix` - server composition for `michael`
- `modules/tahani.nix` - server/workstation composition for `tahani`
## Common Commands
```bash
nix run .#build
nix run .#build -- michael
nix run .#apply
nix run .#deploy -- .#tahani
nix flake check
alejandra .
```
## Updating The Flake
`flake.nix` is generated. Update inputs in `modules/dendritic.nix`, then regenerate:
```bash
nix run .#write-flake
alejandra .
```

66
flake.lock generated
View File

@@ -114,11 +114,11 @@
},
"den": {
"locked": {
"lastModified": 1773203359,
"narHash": "sha256-CU+bk7KxCggu9MNFs6Yp4K8e24RrRYOOnqT0ZPT82+U=",
"lastModified": 1773295801,
"narHash": "sha256-Wzk6tz4K8lID2El0BG3F3JLFPEx6P74E3UyJwBlE+I0=",
"owner": "vic",
"repo": "den",
"rev": "101b08a257a1ff892901b02f67109ec69f75466f",
"rev": "7c781f55d26fe40d84961557aa2e1e9480cee93e",
"type": "github"
},
"original": {
@@ -237,11 +237,11 @@
},
"flake-aspects": {
"locked": {
"lastModified": 1773185256,
"narHash": "sha256-e3YqMwdQ2EpVTuWRkK2rkrHywp8QCpkj7x2U53SNoIA=",
"lastModified": 1773272797,
"narHash": "sha256-sSdYZiIeo98LmdnCR5GMN8B8bsHFgWA+1l9ZEpwXrFU=",
"owner": "vic",
"repo": "flake-aspects",
"rev": "81a51a8997abe392b9d0794424a4823adc9bd3af",
"rev": "cc5a09d16af05210afe01c22f6b868929a4163b6",
"type": "github"
},
"original": {
@@ -268,11 +268,11 @@
},
"flake-file": {
"locked": {
"lastModified": 1773180483,
"narHash": "sha256-Fz2OYAgaYzmm6KhQWMk18JuyxgVb80pLm7hlXnmXbtg=",
"lastModified": 1773224147,
"narHash": "sha256-w9RQyKZSTfqoZPRzIf7H4qVHy2N6uFk1MUU+c1K4c40=",
"owner": "vic",
"repo": "flake-file",
"rev": "8edcfd6d941a1a936296c89fd70dd686df947be8",
"rev": "97bd69ff570dddccd704077830446ec1ca3a6988",
"type": "github"
},
"original": {
@@ -406,11 +406,11 @@
]
},
"locked": {
"lastModified": 1773179137,
"narHash": "sha256-EdW2bwzlfme0vbMOcStnNmKlOAA05Bp6su2O8VLGT0k=",
"lastModified": 1773286336,
"narHash": "sha256-+yFtmhOHterllxWmV6YbdevTXpJdGS0mS0UmJ0k9fh0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3f98e2bbc661ec0aaf558d8a283d6955f05f1d09",
"rev": "7d06e0cefe6e4a1e85b2b3274dcb0b3da242a557",
"type": "github"
},
"original": {
@@ -422,11 +422,11 @@
"homebrew-cask": {
"flake": false,
"locked": {
"lastModified": 1773204415,
"narHash": "sha256-ayKgBC9e8y1sx2BHstY1OPrKMBIJAJoIQxIX7HGstGo=",
"lastModified": 1773303437,
"narHash": "sha256-grP7e0BRp8GDT28hAV5H4Pu51IDQ34913hxGldlQf70=",
"owner": "homebrew",
"repo": "homebrew-cask",
"rev": "6b75b8e53d71974d5e81dde8efd27b2937516ac8",
"rev": "0300017ecdeaa28c6635c559381ff4d0e1203c86",
"type": "github"
},
"original": {
@@ -438,11 +438,11 @@
"homebrew-core": {
"flake": false,
"locked": {
"lastModified": 1773213089,
"narHash": "sha256-A64kuAmOD7BLz6ESkpNayFNL+gXF6dMYuF2O8ZgGJ/k=",
"lastModified": 1773306422,
"narHash": "sha256-EvboT0Gno/1BZ7OcaDFZ737cQr2J6amdrbpHAFbameU=",
"owner": "homebrew",
"repo": "homebrew-core",
"rev": "a8f0bec4e248fa90aa84a7c4a5b3fbc9c160b6c6",
"rev": "f6d9718a1bee00675755ebfbe80964990bebcfda",
"type": "github"
},
"original": {
@@ -526,11 +526,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1773201098,
"narHash": "sha256-yq35qMKDHyMdVlhGfR5BojbjniY2cY9XYmiILeCf1Xc=",
"lastModified": 1773302483,
"narHash": "sha256-NFnShCTM8fsNF8dRMIgebo8oiTHMKyCEBieYon4K0Ic=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "8578734bf5087a1ca45033c2ec8e1a2228f9b95c",
"rev": "dd92bb728d845be99ebfee69c1ee951dcbceb2b1",
"type": "github"
},
"original": {
@@ -652,11 +652,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1772956932,
"narHash": "sha256-M0yS4AafhKxPPmOHGqIV0iKxgNO8bHDWdl1kOwGBwRY=",
"lastModified": 1773110118,
"narHash": "sha256-mPAG8phMbCReKSiKAijjjd3v7uVcJOQ75gSjGJjt/Rk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "608d0cadfed240589a7eea422407a547ad626a14",
"rev": "e607cb5360ff1234862ac9f8839522becb853bb9",
"type": "github"
},
"original": {
@@ -668,11 +668,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1773212285,
"narHash": "sha256-56OmaHrnvxJA1D78ZDqBME9ptqRMhw3r+vw2/UKfD24=",
"lastModified": 1773305098,
"narHash": "sha256-fm+rOZiAzFivuco5d7T+rE8i213EIEuHvoIuWiVlYto=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "15d31785a653f47d6f8fe017ae244bb5282fa3e5",
"rev": "b9637319b2e3b8061ba416b46cde41bd9f8b4442",
"type": "github"
},
"original": {
@@ -753,11 +753,11 @@
"nono": {
"flake": false,
"locked": {
"lastModified": 1773209960,
"narHash": "sha256-SZ6Qze+sZpP2gqMmhyYSIlMaumJUhzaSIJwKJaOP4Mo=",
"lastModified": 1773270026,
"narHash": "sha256-aLMBK2JQkn07wzho79WwolXRZEqvpILdq5bxp9F6Wug=",
"owner": "always-further",
"repo": "nono",
"rev": "910034e23d37882862e9694f737094b0a3e67711",
"rev": "9f3205847598d04d8b882c8ecb8238e4855d2a41",
"type": "github"
},
"original": {
@@ -1022,11 +1022,11 @@
"utils": "utils_2"
},
"locked": {
"lastModified": 1773180218,
"narHash": "sha256-VEt3lAK7jMVE1mkW2S8RaCd3G19M102pCGR6ge5MBsY=",
"lastModified": 1773260683,
"narHash": "sha256-ZZEX8JD+kiqpNsPOgrksbNrh5w8B63s8ebJplftdqhQ=",
"owner": "agavra",
"repo": "tuicr",
"rev": "544f8b6f3bbef4577f6b79ded2974684d8a43582",
"rev": "392d5e5a7b443cb7bbf234f7f44c6c0352b99c8a",
"type": "github"
},
"original": {

View File

@@ -45,7 +45,7 @@ in {
{path = "/System/Applications/Music.app/";}
{path = "/System/Applications/System Settings.app/";}
{
path = "${config.users.users.cschmatzler.home}/Downloads";
path = "/Users/cschmatzler/Downloads";
section = "others";
options = "--sort name --view grid --display stack";
}

View File

@@ -0,0 +1,58 @@
{
config,
lib,
pkgs,
...
}: {
services.restic.backups.gitea = {
repository = "s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
script = ''
export RESTIC_PASSWORD=$(cat ${config.sops.secrets.michael-gitea-restic-password.path})
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories snapshots &>/dev/null || \
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories init
'';
};
}

View File

@@ -0,0 +1,114 @@
{
config,
lib,
...
}: {
sops.secrets = {
michael-gitea-litestream = {
sopsFile = ../../../secrets/michael-gitea-litestream;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-litestream";
};
michael-gitea-restic-password = {
sopsFile = ../../../secrets/michael-gitea-restic-password;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-restic-password";
};
michael-gitea-restic-env = {
sopsFile = ../../../secrets/michael-gitea-restic-env;
format = "binary";
owner = "gitea";
group = "gitea";
path = "/run/secrets/michael-gitea-restic-env";
};
};
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = config.sops.secrets.michael-gitea-litestream.path;
settings = {
dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = "michael-gitea-litestream";
path = "gitea";
endpoint = "s3.eu-central-003.backblazeb2.com";
}
];
}
];
};
};
systemd.services.litestream.serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
}

View File

@@ -1,7 +1,7 @@
{
{config, ...}: {
services.adguardhome = {
enable = true;
host = "0.0.0.0";
host = "127.0.0.1";
port = 10000;
settings = {
dhcp = {
@@ -57,4 +57,13 @@
];
};
};
services.caddy.virtualHosts."adguard.manticore-hippocampus.ts.net" = {
extraConfig = ''
tls {
get_certificate tailscale
}
reverse_proxy localhost:${toString config.services.adguardhome.port}
'';
};
}

View File

@@ -1,6 +1,7 @@
{config, ...}: {
services.caddy = {
enable = true;
enableReload = false;
globalConfig = ''
admin off
'';
@@ -25,8 +26,11 @@
virtualisation.oci-containers = {
backend = "docker";
containers.paperless-ai = {
image = "clusterzx/paperless-ai:latest";
image = "clusterzx/paperless-ai:v3.0.9";
autoStart = true;
ports = [
"127.0.0.1:3000:3000"
];
volumes = [
"paperless-ai-data:/app/data"
];
@@ -36,11 +40,10 @@
PAPERLESS_AI_PORT = "3000";
# Initial setup wizard will configure the rest
PAPERLESS_AI_INITIAL_SETUP = "yes";
# Paperless-ngx API URL accessible from container (using host network)
PAPERLESS_API_URL = "http://127.0.0.1:${toString config.services.paperless.port}/api";
PAPERLESS_API_URL = "http://host.docker.internal:${toString config.services.paperless.port}/api";
};
extraOptions = [
"--network=host"
"--add-host=host.docker.internal:host-gateway"
];
};
};
@@ -57,7 +60,7 @@
services.paperless = {
enable = true;
address = "0.0.0.0";
address = "127.0.0.1";
passwordFile = config.sops.secrets.tahani-paperless-password.path;
settings = {
PAPERLESS_DBENGINE = "sqlite";

View File

@@ -1,14 +0,0 @@
{
user = "cschmatzler";
sshKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHfRZQ+7ejD3YHbyMTrV0gN1Gc0DxtGgl5CVZSupo5ws"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL/I+/2QT47raegzMIyhwMEPKarJP/+Ox9ewA4ZFJwk/"
];
stateVersions = {
darwin = 6;
nixos = "25.11";
homeManager = "25.11";
};
}

View File

@@ -1,10 +0,0 @@
{pkgs}:
pkgs.writeShellScriptBin "open-project" ''
TARGET=$(fd -t d --exact-depth 1 . $HOME/Projects |
sed "s~$HOME/Projects/~~" |
fzf --prompt "project > ")
if [ -n "$TARGET" ]; then
echo "$HOME/Projects/$TARGET"
fi
''

View File

@@ -25,10 +25,10 @@
enable = true;
defaultEditor = true;
luaLoader.enable = true;
colorschemes.catppuccin = {
colorschemes.rose-pine = {
enable = true;
settings = {
flavour = "latte";
variant = "dawn";
};
};
extraConfigLua = ''

View File

@@ -19,17 +19,6 @@ in {
];
extraConfigLua = ''
require('code-review').setup({
comment = {
storage = {
backend = "file",
file = {
dir = ".code-review",
},
},
},
output = {
format = "minimal",
},
keymaps = false,
})
'';

View File

@@ -1,173 +1,351 @@
{
programs.nixvim.plugins.mini = {
enable = true;
modules = {
ai = {
custom_textobjects = {
B.__raw = "require('mini.extra').gen_ai_spec.buffer()";
F.__raw = "require('mini.ai').gen_spec.treesitter({ a = '@function.outer', i = '@function.inner' })";
};
};
align = {};
basics = {
options = {
basic = true;
extra_ui = true;
};
mappings = {
basic = false;
};
autocommands = {
basic = true;
};
};
bracketed = {};
clue = {
clues.__raw = ''
{
{ mode = 'n', keys = '<Leader>e', desc = '+Explore/+Edit' },
{ mode = 'n', keys = '<Leader>f', desc = '+Find' },
{ mode = 'n', keys = '<Leader>v', desc = '+VCS' },
{ mode = 'n', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'x', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'n', keys = '<Leader>o', desc = '+OpenCode' },
{ mode = 'x', keys = '<Leader>o', desc = '+OpenCode' },
{ mode = 'n', keys = '<Leader>r', desc = '+Review' },
{ mode = 'v', keys = '<Leader>r', desc = '+Review' },
require("mini.clue").gen_clues.builtin_completion(),
require("mini.clue").gen_clues.g(),
require("mini.clue").gen_clues.marks(),
require("mini.clue").gen_clues.registers(),
require("mini.clue").gen_clues.windows({ submode_resize = true }),
require("mini.clue").gen_clues.z(),
}
'';
triggers = [
{
mode = "n";
keys = "<Leader>";
}
{
mode = "x";
keys = "<Leader>";
}
{
mode = "n";
keys = "[";
}
{
mode = "n";
keys = "]";
}
{
mode = "x";
keys = "[";
}
{
mode = "x";
keys = "]";
}
{
mode = "i";
keys = "<C-x>";
}
{
mode = "n";
keys = "g";
}
{
mode = "x";
keys = "g";
}
{
mode = "n";
keys = "\"";
}
{
mode = "x";
keys = "\"";
}
{
mode = "i";
keys = "<C-r>";
}
{
mode = "c";
keys = "<C-r>";
}
{
mode = "n";
keys = "<C-w>";
}
{
mode = "n";
keys = "z";
}
{
mode = "x";
keys = "z";
}
{
mode = "n";
keys = "'";
}
{
mode = "n";
keys = "`";
}
{
mode = "x";
keys = "'";
}
{
mode = "x";
keys = "`";
}
];
};
cmdline = {};
comment = {};
diff = {};
extra = {};
git = {};
hipatterns = {
highlighters = {
fixme.__raw = "{ pattern = '%f[%w]()FIXME()%f[%W]', group = 'MiniHipatternsFixme' }";
hack.__raw = "{ pattern = '%f[%w]()HACK()%f[%W]', group = 'MiniHipatternsHack' }";
todo.__raw = "{ pattern = '%f[%w]()TODO()%f[%W]', group = 'MiniHipatternsTodo' }";
note.__raw = "{ pattern = '%f[%w]()NOTE()%f[%W]', group = 'MiniHipatternsNote' }";
hex_color.__raw = "require('mini.hipatterns').gen_highlighter.hex_color()";
};
};
icons = {};
indentscope = {
settings = {
symbol = "|";
};
};
jump = {};
jump2d = {
settings = {
spotter.__raw = "require('mini.jump2d').gen_spotter.pattern('[^%s%p]+')";
labels = "asdfghjkl";
view = {
dim = true;
n_steps_ahead = 2;
programs.nixvim = {
plugins.mini = {
enable = true;
modules = {
ai = {
custom_textobjects = {
B.__raw = "require('mini.extra').gen_ai_spec.buffer()";
F.__raw = "require('mini.ai').gen_spec.treesitter({ a = '@function.outer', i = '@function.inner' })";
};
};
align = {};
basics = {
options = {
basic = true;
extra_ui = true;
};
mappings = {
basic = false;
};
autocommands = {
basic = true;
};
};
bracketed = {};
clue = {
clues.__raw = ''
{
{ mode = 'n', keys = '<Leader>e', desc = '+Explore/+Edit' },
{ mode = 'n', keys = '<Leader>f', desc = '+Find' },
{ mode = 'n', keys = '<Leader>v', desc = '+VCS' },
{ mode = 'n', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'x', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'n', keys = '<Leader>o', desc = '+OpenCode' },
{ mode = 'x', keys = '<Leader>o', desc = '+OpenCode' },
{ mode = 'n', keys = '<Leader>r', desc = '+Review' },
{ mode = 'v', keys = '<Leader>r', desc = '+Review' },
require("mini.clue").gen_clues.builtin_completion(),
require("mini.clue").gen_clues.g(),
require("mini.clue").gen_clues.marks(),
require("mini.clue").gen_clues.registers(),
require("mini.clue").gen_clues.windows({ submode_resize = true }),
require("mini.clue").gen_clues.z(),
}
'';
triggers = [
{
mode = "n";
keys = "<Leader>";
}
{
mode = "x";
keys = "<Leader>";
}
{
mode = "n";
keys = "[";
}
{
mode = "n";
keys = "]";
}
{
mode = "x";
keys = "[";
}
{
mode = "x";
keys = "]";
}
{
mode = "i";
keys = "<C-x>";
}
{
mode = "n";
keys = "g";
}
{
mode = "x";
keys = "g";
}
{
mode = "n";
keys = "\"";
}
{
mode = "x";
keys = "\"";
}
{
mode = "i";
keys = "<C-r>";
}
{
mode = "c";
keys = "<C-r>";
}
{
mode = "n";
keys = "<C-w>";
}
{
mode = "n";
keys = "z";
}
{
mode = "x";
keys = "z";
}
{
mode = "n";
keys = "'";
}
{
mode = "n";
keys = "`";
}
{
mode = "x";
keys = "'";
}
{
mode = "x";
keys = "`";
}
];
};
cmdline = {};
comment = {};
diff = {};
extra = {};
git = {};
hipatterns = {
highlighters = {
fixme.__raw = "{ pattern = '%f[%w]()FIXME()%f[%W]', group = 'MiniHipatternsFixme' }";
hack.__raw = "{ pattern = '%f[%w]()HACK()%f[%W]', group = 'MiniHipatternsHack' }";
todo.__raw = "{ pattern = '%f[%w]()TODO()%f[%W]', group = 'MiniHipatternsTodo' }";
note.__raw = "{ pattern = '%f[%w]()NOTE()%f[%W]', group = 'MiniHipatternsNote' }";
hex_color.__raw = "require('mini.hipatterns').gen_highlighter.hex_color()";
};
};
icons = {};
indentscope = {
settings = {
symbol = "|";
};
};
jump = {};
jump2d = {
settings = {
spotter.__raw = "require('mini.jump2d').gen_spotter.pattern('[^%s%p]+')";
labels = "asdfghjkl";
view = {
dim = true;
n_steps_ahead = 2;
};
};
};
move = {};
notify = {
content.format.__raw = ''
function(notif)
local formatted = MiniNotify.default_format(notif)
return '\n ' .. formatted:gsub('\n', ' \n ') .. ' \n'
end
'';
window.config = {
border = "none";
title = "";
};
};
pairs = {};
pick = {};
splitjoin = {};
starter = {};
statusline = {
content.active.__raw = ''
function()
local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
local diff = MiniStatusline.section_diff({ trunc_width = 75 })
local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 })
local lsp = MiniStatusline.section_lsp({ trunc_width = 75 })
local filename = MiniStatusline.section_filename({ trunc_width = 140 })
local search = MiniStatusline.section_searchcount({ trunc_width = 75 })
return _G.CschStatusline.active({
mode = mode,
mode_hl = mode_hl,
diff = diff,
diagnostics = diagnostics,
lsp = lsp,
filename = filename,
search = search,
})
end
'';
};
surround = {};
trailspace = {};
visits = {};
};
move = {};
notify = {};
pairs = {};
pick = {};
splitjoin = {};
starter = {};
statusline = {};
surround = {};
trailspace = {};
visits = {};
mockDevIcons = true;
};
mockDevIcons = true;
extraConfigLua = ''
local mini_notify_group = vim.api.nvim_create_augroup('MiniNotifyDesign', { clear = true })
_G.CschStatusline = _G.CschStatusline or {}
local function to_hex(value)
return value and string.format('#%06x', value) or nil
end
local function get_hl(name)
return vim.api.nvim_get_hl(0, { name = name, link = false })
end
local function get_fg(name, fallback)
return to_hex(get_hl(name).fg) or fallback
end
local function get_bg(name, fallback)
return to_hex(get_hl(name).bg) or fallback
end
local function set_statusline_highlights()
local block_bg = get_bg('CursorLine', get_bg('Visual', '#373b41'))
local block_fg = get_fg('StatusLine', get_fg('Normal', '#c5c8c6'))
vim.api.nvim_set_hl(0, 'CschStatuslineBlock', { fg = block_fg, bg = block_bg })
end
local function statusline_group(hl, strings)
local parts = vim.tbl_filter(function(x)
return type(x) == 'string' and x ~= ""
end, strings or {})
if #parts == 0 then
return ""
end
return string.format('%%#%s# %s ', hl, table.concat(parts, ' '))
end
local function statusline_block(text, hl)
if text == nil or text == "" then
return ""
end
return string.format('%%#%s# %s ', hl, text)
end
local function statusline_filesize()
local size = math.max(vim.fn.line2byte(vim.fn.line('$') + 1) - 1, 0)
if size < 1024 then
return string.format('%dB', size)
elseif size < 1048576 then
return string.format('%.2fKiB', size / 1024)
end
return string.format('%.2fMiB', size / 1048576)
end
local function statusline_filetype()
local filetype = vim.bo.filetype
if filetype == "" then
return vim.bo.buftype ~= "" and vim.bo.buftype or 'text'
end
local icon = ""
if _G.MiniIcons ~= nil then
icon = _G.MiniIcons.get('filetype', filetype) or ""
end
return (icon ~= "" and (icon .. ' ') or "") .. filetype
end
local function statusline_fileinfo()
local label = statusline_filetype()
if MiniStatusline.is_truncated(120) or vim.bo.buftype ~= "" then
return label
end
return string.format('%s · %s', label, statusline_filesize())
end
local function statusline_location()
local line = vim.fn.line('.')
local total_lines = vim.fn.line('$')
local column = vim.fn.virtcol('.')
if MiniStatusline.is_truncated(90) then
return string.format('Ln %d Col %d', line, column)
end
return string.format('Ln %d/%d · Col %d', line, total_lines, column)
end
function _G.CschStatusline.active(parts)
local left = vim.tbl_filter(function(x)
return x ~= ""
end, {
statusline_block(parts.mode, parts.mode_hl),
statusline_group('MiniStatuslineDevinfo', { parts.diff, parts.diagnostics, parts.lsp }),
'%<',
statusline_group('MiniStatuslineFilename', { parts.filename }),
})
local right = vim.tbl_filter(function(x)
return x ~= ""
end, {
statusline_block(statusline_fileinfo(), 'CschStatuslineBlock'),
statusline_block(parts.search, 'CschStatuslineBlock'),
statusline_block(statusline_location(), parts.mode_hl),
})
return table.concat(left, "") .. '%=%#StatusLine#' .. table.concat(right, "")
end
local function set_mini_notify_highlights()
local border = vim.api.nvim_get_hl(0, { name = 'FloatBorder' })
local normal = vim.api.nvim_get_hl(0, { name = 'NormalFloat' })
local popup_bg = get_bg('Pmenu', get_bg('CursorLine', get_bg('NormalFloat', '#303446')))
local title = vim.api.nvim_get_hl(0, { name = 'FloatTitle' })
border.bg = 'NONE'
normal.bg = popup_bg
normal.bold = true
title.bg = 'NONE'
vim.api.nvim_set_hl(0, 'MiniNotifyBorder', border)
vim.api.nvim_set_hl(0, 'MiniNotifyNormal', normal)
vim.api.nvim_set_hl(0, 'MiniNotifyTitle', title)
end
vim.api.nvim_create_autocmd('ColorScheme', {
group = mini_notify_group,
callback = function()
set_mini_notify_highlights()
set_statusline_highlights()
end,
})
set_mini_notify_highlights()
set_statusline_highlights()
'';
};
}

View File

@@ -20,6 +20,36 @@ in {
render-markdown-nvim
];
extraConfigLua = ''
local api = vim.api
local opencode_output_filetype = 'opencode_output'
local opencode_window_filetypes = {
opencode = true,
opencode_output = true,
}
local palette = {
base = '#faf4ed',
surface = '#fffaf3',
overlay = '#f2e9e1',
highlight_med = '#dfdad9',
text = '#575279',
subtle = '#797593',
muted = '#9893a5',
pine = '#286983',
iris = '#907aa9',
foam = '#56949f',
leaf = '#6d8f89',
gold = '#ea9d34',
rose = '#d7827e',
love = '#b4637a',
}
local function set_highlights(highlights)
for group, spec in pairs(highlights) do
api.nvim_set_hl(0, group, spec)
end
end
local opencode_markdown_conceal_query = vim.treesitter.query.parse('markdown_inline', [[
[
(emphasis_delimiter)
@@ -66,8 +96,28 @@ in {
] @conceal)
]])
local function collect_conceal_marks(ctx)
local marks = {}
for _, node in opencode_markdown_conceal_query:iter_captures(ctx.root, ctx.buf) do
local start_row, start_col, end_row, end_col = node:range()
marks[#marks + 1] = {
conceal = true,
start_row = start_row,
start_col = start_col,
opts = {
end_row = end_row,
end_col = end_col,
conceal = "",
},
}
end
return marks
end
local function set_opencode_output_conceal()
if vim.bo.filetype ~= 'opencode_output' then
if vim.bo.filetype ~= opencode_output_filetype then
return
end
@@ -75,101 +125,172 @@ in {
vim.wo.concealcursor = 'nvic'
end
vim.treesitter.language.register('markdown', 'opencode_output')
vim.treesitter.language.register('markdown_inline', 'opencode_output')
local function hide_opencode_statusline()
if not opencode_window_filetypes[vim.bo.filetype] then
return
end
vim.api.nvim_create_autocmd({ 'FileType', 'BufWinEnter', 'WinEnter' }, {
vim.wo.statusline = ' '
end
vim.treesitter.language.register('markdown', opencode_output_filetype)
vim.treesitter.language.register('markdown_inline', opencode_output_filetype)
api.nvim_create_autocmd({ 'FileType', 'BufWinEnter', 'WinEnter' }, {
callback = set_opencode_output_conceal,
})
api.nvim_create_autocmd({ 'FileType', 'BufWinEnter', 'WinEnter', 'BufEnter' }, {
pattern = '*',
callback = hide_opencode_statusline,
})
require('render-markdown').setup({
set_highlights({
RenderMarkdownCode = { bg = palette.surface },
RenderMarkdownCodeBorder = { fg = palette.highlight_med, bg = palette.surface },
RenderMarkdownCodeInline = { bg = palette.surface },
RenderMarkdownH1 = { fg = palette.pine, bold = true },
RenderMarkdownH2 = { fg = palette.iris, bold = true },
RenderMarkdownH3 = { fg = palette.foam, bold = true },
RenderMarkdownH4 = { fg = palette.gold, bold = true },
OpencodeInputLegend = { fg = palette.subtle, bold = true },
OpencodeAgentBuild = { bg = palette.muted, fg = palette.base, bold = true },
})
local render_markdown_config = {
anti_conceal = { enabled = false },
heading = {
icons = { ' ', ' ', ' ', '· ', '· ', '· ' },
backgrounds = {},
position = 'inline',
width = 'block',
left_pad = 0,
right_pad = 2,
border = false,
sign = false,
},
code = {
sign = false,
width = 'full',
left_pad = 2,
right_pad = 0,
border = 'thin',
language_icon = false,
language_name = true,
},
bullet = {
icons = { '·', '', '·', '' },
},
custom_handlers = {
markdown_inline = {
extends = true,
parse = function(ctx)
local marks = {}
for _, node in opencode_markdown_conceal_query:iter_captures(ctx.root, ctx.buf) do
local start_row, start_col, end_row, end_col = node:range()
marks[#marks + 1] = {
conceal = true,
start_row = start_row,
start_col = start_col,
opts = {
end_row = end_row,
end_col = end_col,
conceal = "",
},
}
end
return marks
end,
parse = collect_conceal_marks,
},
},
file_types = { 'opencode_output' },
file_types = { opencode_output_filetype },
win_options = {
conceallevel = { rendered = 3 },
concealcursor = { rendered = "nvic" },
concealcursor = { rendered = 'nvic' },
},
}
require('render-markdown').setup(render_markdown_config)
local opencode_icon_overrides = {
header_user = '',
header_assistant = '',
run = '',
task = '',
read = '',
edit = '',
write = '',
plan = '',
search = '',
web = '',
list = '',
tool = '',
snapshot = '',
restore_point = '',
file = '·',
folder = '·',
attached_file = '·',
agent = '·',
reference = '·',
reasoning = '',
question = '?',
border = '',
}
require('opencode').setup({
server = {
url = 'http://127.0.0.1',
port = 18822,
auto_kill = false,
},
input = {
text = {
wrap = true,
},
},
debug = {
show_ids = false,
},
ui = {
icons = {
preset = 'nerdfonts',
overrides = opencode_icon_overrides,
},
},
})
require('opencode').setup({
server = {
url = 'http://127.0.0.1',
port = 18822,
auto_kill = false,
},
input = {
text = {
wrap = true,
},
},
ui = {
icons = {
preset = 'nerdfonts',
},
},
})
do
local config = require('opencode.config')
local formatter = require('opencode.ui.formatter')
local format_utils = require('opencode.ui.formatter.utils')
local icons = require('opencode.ui.icons')
local util = require('opencode.util')
do
local config = require('opencode.config')
local formatter = require('opencode.ui.formatter')
local format_utils = require('opencode.ui.formatter.utils')
local icons = require('opencode.ui.icons')
local util = require('opencode.util')
formatter._format_reasoning = function(output, part)
local text = vim.trim(part.text or "")
local start_line = output:get_line_count() + 1
local function format_reasoning_title(part)
local title = 'Reasoning'
local time = part.time
local title = 'Reasoning'
local time = part.time
if time and type(time) == 'table' and time.start then
local duration_text = util.format_duration_seconds(time.start, time['end'])
if duration_text then
title = string.format('%s %s', title, duration_text)
end
end
format_utils.format_action(output, icons.get('reasoning'), title, "")
if config.ui.output.tools.show_reasoning_output and text ~= "" then
output:add_empty_line()
output:add_lines(vim.split(text, '\n'), ' ')
output:add_empty_line()
end
local end_line = output:get_line_count()
if end_line - start_line > 1 then
formatter.add_vertical_border(output, start_line, end_line, 'OpencodeToolBorder', -1, 'OpencodeReasoningText')
else
output:add_extmark(start_line - 1, {
line_hl_group = 'OpencodeReasoningText',
})
if time and type(time) == 'table' and time.start then
local duration_text = util.format_duration_seconds(time.start, time['end'])
if duration_text then
title = string.format('%s %s', title, duration_text)
end
end
return title
end
local function highlight_reasoning_block(output, start_line)
local end_line = output:get_line_count()
if end_line - start_line > 1 then
formatter.add_vertical_border(output, start_line, end_line, 'OpencodeToolBorder', -1, 'OpencodeReasoningText')
return
end
output:add_extmark(start_line - 1, {
line_hl_group = 'OpencodeReasoningText',
})
end
formatter._format_reasoning = function(output, part)
local text = vim.trim(part.text or "")
local start_line = output:get_line_count() + 1
format_utils.format_action(output, icons.get('reasoning'), format_reasoning_title(part), "")
if config.ui.output.tools.show_reasoning_output and text ~= "" then
output:add_empty_line()
output:add_lines(vim.split(text, '\n'), ' ')
output:add_empty_line()
end
highlight_reasoning_block(output, start_line)
end
end
'';
};
}

View File

@@ -35,6 +35,26 @@
end, { force = true, all = true })
end
end
-- Fix grammar-bundled treesitter queries that use #match? with Lua pattern
-- syntax (e.g. %d) instead of Vim regex. Neovim 0.11 picks the first
-- non-extending query file in the rtp as the base, so the grammar-bundled
-- (buggy) queries take precedence over the corrected site-level queries.
-- Override affected languages with the site-level version.
do
local langs = { "sql" }
for _, lang in ipairs(langs) do
local files = vim.api.nvim_get_runtime_file(
"queries/" .. lang .. "/highlights.scm", true)
if #files > 1 then
local f = io.open(files[#files])
if f then
vim.treesitter.query.set(lang, "highlights", f:read("*all"))
f:close()
end
end
end
end
'';
};
}

View File

@@ -11,7 +11,7 @@
settings = {
model = "anthropic/claude-opus-4-6";
small_model = "anthropic/claude-haiku-4-5";
theme = "catppuccin";
theme = "rosepine";
plugin = ["opencode-anthropic-auth@latest"];
permission = {
read = {

View File

@@ -105,7 +105,7 @@
};
nix = {
settings.trusted-users = ["@admin" "cschmatzler"];
settings.trusted-users = ["cschmatzler"];
gc.interval = {
Weekday = 0;
Hour = 2;

View File

@@ -23,16 +23,19 @@
config = {
flake.flakeModules = {
# Shared system foundations
core = ./core.nix;
network = ./network.nix;
nixos-system = ./nixos-system.nix;
# User environment
ai-tools = ./ai-tools.nix;
atuin = ./atuin.nix;
core = ./core.nix;
desktop = ./desktop.nix;
dev-tools = ./dev-tools.nix;
email = ./email.nix;
finance = ./finance.nix;
neovim = ./neovim.nix;
network = ./network.nix;
nixos-system = ./nixos-system.nix;
shell = ./shell.nix;
ssh-client = ./ssh-client.nix;
terminal = ./terminal.nix;

View File

@@ -316,36 +316,36 @@
gui = {
authorColors = {
"*" = "#7287fd";
"*" = "#907aa9";
};
theme = {
activeBorderColor = [
"#8839ef"
"#907aa9"
"bold"
];
inactiveBorderColor = [
"#6c6f85"
"#9893a5"
];
optionsTextColor = [
"#1e66f5"
"#286983"
];
selectedLineBgColor = [
"#ccd0da"
"#f2e9e1"
];
cherryPickedCommitBgColor = [
"#bcc0cc"
"#dfdad9"
];
cherryPickedCommitFgColor = [
"#8839ef"
"#907aa9"
];
defaultFgColor = [
"#4c4f69"
"#575279"
];
searchingActiveBorderColor = [
"#df8e1d"
"#ea9d34"
];
unstagedChangesColor = [
"#d20f39"
"#b4637a"
];
};
};
@@ -378,21 +378,23 @@
ast-grep
bun
delta
deadnix
devenv
docker
docker-compose
gh
git
gnumake
hyperfine
jj-ryu
jj-starship
nil
nodejs_24
nurl
pnpm
postgresql_17
serie
sqlite
statix
tea
tokei
tree-sitter

View File

@@ -25,7 +25,7 @@
address = "christoph@schmatzler.com";
userName = "christoph.schmatzler@icloud.com";
realName = "Christoph Schmatzler";
passwordCommand = ["cat" "/run/secrets/tahani-email-password"];
passwordCommand = ["${pkgs.coreutils}/bin/cat" "/run/secrets/tahani-email-password"];
folders = {
inbox = "INBOX";
drafts = "Drafts";

View File

@@ -11,180 +11,16 @@
den.aspects.tailscale
];
den.aspects.michael.nixos = {
config,
pkgs,
lib,
modulesPath,
...
}: {
den.aspects.michael.nixos = {modulesPath, ...}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
./_hosts/michael/backups.nix
./_hosts/michael/disk-config.nix
./_hosts/michael/gitea.nix
./_hosts/michael/hardware-configuration.nix
inputs.disko.nixosModules.default
];
networking.hostName = "michael";
sops.secrets = {
michael-gitea-litestream = {
sopsFile = ../secrets/michael-gitea-litestream;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-password = {
sopsFile = ../secrets/michael-gitea-restic-password;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-env = {
sopsFile = ../secrets/michael-gitea-restic-env;
format = "binary";
owner = "gitea";
group = "gitea";
};
};
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = config.sops.secrets.michael-gitea-litestream.path;
settings = {
dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = "michael-gitea-litestream";
path = "gitea";
endpoint = "s3.eu-central-003.backblazeb2.com";
}
];
}
];
};
};
systemd.services.litestream = {
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
services.restic.backups.gitea = {
repository = "s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
script = ''
export RESTIC_PASSWORD=$(cat ${config.sops.secrets.michael-gitea-restic-password.path})
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories snapshots &>/dev/null || \
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories init
'';
};
};
}

View File

@@ -27,7 +27,7 @@
extraEnv =
''
$env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate catppuccin-latte)
$env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate rose-pine-dawn)
''
+ lib.optionalString pkgs.stdenv.isDarwin ''
# Nushell on Darwin doesn't source /etc/zprofile or path_helper,
@@ -36,65 +36,55 @@
'';
extraConfig = ''
# --- Catppuccin Latte Theme ---
# --- Rosé Pine Dawn Theme ---
let theme = {
rosewater: "#dc8a78"
flamingo: "#dd7878"
pink: "#ea76cb"
mauve: "#8839ef"
red: "#d20f39"
maroon: "#e64553"
peach: "#fe640b"
yellow: "#df8e1d"
green: "#40a02b"
teal: "#179299"
sky: "#04a5e5"
sapphire: "#209fb5"
blue: "#1e66f5"
lavender: "#7287fd"
text: "#4c4f69"
subtext1: "#5c5f77"
subtext0: "#6c6f85"
overlay2: "#7c7f93"
overlay1: "#8c8fa1"
overlay0: "#9ca0b0"
surface2: "#acb0be"
surface1: "#bcc0cc"
surface0: "#ccd0da"
base: "#eff1f5"
mantle: "#e6e9ef"
crust: "#dce0e8"
love: "#b4637a"
gold: "#ea9d34"
rose: "#d7827e"
pine: "#286983"
foam: "#56949f"
iris: "#907aa9"
leaf: "#6d8f89"
text: "#575279"
subtle: "#797593"
muted: "#9893a5"
highlight_high: "#cecacd"
highlight_med: "#dfdad9"
highlight_low: "#f4ede8"
overlay: "#f2e9e1"
surface: "#fffaf3"
base: "#faf4ed"
}
let scheme = {
recognized_command: $theme.blue
recognized_command: $theme.pine
unrecognized_command: $theme.text
constant: $theme.peach
punctuation: $theme.overlay2
operator: $theme.sky
string: $theme.green
virtual_text: $theme.surface2
variable: { fg: $theme.flamingo attr: i }
filepath: $theme.yellow
constant: $theme.gold
punctuation: $theme.muted
operator: $theme.subtle
string: $theme.gold
virtual_text: $theme.highlight_high
variable: { fg: $theme.rose attr: i }
filepath: $theme.iris
}
$env.config.color_config = {
separator: { fg: $theme.surface2 attr: b }
leading_trailing_space_bg: { fg: $theme.lavender attr: u }
separator: { fg: $theme.highlight_high attr: b }
leading_trailing_space_bg: { fg: $theme.iris attr: u }
header: { fg: $theme.text attr: b }
row_index: $scheme.virtual_text
record: $theme.text
list: $theme.text
hints: $scheme.virtual_text
search_result: { fg: $theme.base bg: $theme.yellow }
shape_closure: $theme.teal
closure: $theme.teal
shape_flag: { fg: $theme.maroon attr: i }
search_result: { fg: $theme.base bg: $theme.gold }
shape_closure: $theme.foam
closure: $theme.foam
shape_flag: { fg: $theme.love attr: i }
shape_matching_brackets: { attr: u }
shape_garbage: $theme.red
shape_keyword: $theme.mauve
shape_match_pattern: $theme.green
shape_signature: $theme.teal
shape_garbage: $theme.love
shape_keyword: $theme.iris
shape_match_pattern: $theme.leaf
shape_signature: $theme.foam
shape_table: $scheme.punctuation
cell-path: $scheme.punctuation
shape_list: $scheme.punctuation
@@ -104,53 +94,53 @@
empty: { attr: n }
filesize: {||
if $in < 1kb {
$theme.teal
$theme.foam
} else if $in < 10kb {
$theme.green
$theme.leaf
} else if $in < 100kb {
$theme.yellow
$theme.gold
} else if $in < 10mb {
$theme.peach
$theme.rose
} else if $in < 100mb {
$theme.maroon
$theme.love
} else if $in < 1gb {
$theme.red
$theme.love
} else {
$theme.mauve
$theme.iris
}
}
duration: {||
if $in < 1day {
$theme.teal
$theme.foam
} else if $in < 1wk {
$theme.green
$theme.leaf
} else if $in < 4wk {
$theme.yellow
$theme.gold
} else if $in < 12wk {
$theme.peach
$theme.rose
} else if $in < 24wk {
$theme.maroon
$theme.love
} else if $in < 52wk {
$theme.red
$theme.love
} else {
$theme.mauve
$theme.iris
}
}
datetime: {|| (date now) - $in |
if $in < 1day {
$theme.teal
$theme.foam
} else if $in < 1wk {
$theme.green
$theme.leaf
} else if $in < 4wk {
$theme.yellow
$theme.gold
} else if $in < 12wk {
$theme.peach
$theme.rose
} else if $in < 24wk {
$theme.maroon
$theme.love
} else if $in < 52wk {
$theme.red
$theme.love
} else {
$theme.mauve
$theme.iris
}
}
shape_external: $scheme.unrecognized_command
@@ -158,11 +148,11 @@
shape_external_resolved: $scheme.recognized_command
shape_block: $scheme.recognized_command
block: $scheme.recognized_command
shape_custom: $theme.pink
custom: $theme.pink
shape_custom: $theme.rose
custom: $theme.rose
background: $theme.base
foreground: $theme.text
cursor: { bg: $theme.rosewater fg: $theme.base }
cursor: { bg: $theme.text fg: $theme.base }
shape_range: $scheme.operator
range: $scheme.operator
shape_pipe: $scheme.operator
@@ -187,51 +177,51 @@
shape_literal: $scheme.constant
string: $scheme.string
shape_string: $scheme.string
shape_string_interpolation: $theme.flamingo
shape_string_interpolation: $theme.rose
shape_raw_string: $scheme.string
shape_externalarg: $scheme.string
}
$env.config.highlight_resolved_externals = true
$env.config.explore = {
status_bar_background: { fg: $theme.text, bg: $theme.mantle },
status_bar_background: { fg: $theme.text, bg: $theme.surface },
command_bar_text: { fg: $theme.text },
highlight: { fg: $theme.base, bg: $theme.yellow },
highlight: { fg: $theme.base, bg: $theme.gold },
status: {
error: $theme.red,
warn: $theme.yellow,
info: $theme.blue,
error: $theme.love,
warn: $theme.gold,
info: $theme.pine,
},
selected_cell: { bg: $theme.blue fg: $theme.base },
selected_cell: { bg: $theme.pine fg: $theme.base },
}
# --- Custom Commands ---
def --env open_project [] {
let base = ($env.HOME | path join "Projects")
let choice = (
${pkgs.fd}/bin/fd -t d -d 1 -a . ($base | path join "Personal") ($base | path join "Work")
| lines
| each {|p| $p | str replace $"($base)/" "" }
| str join "\n"
| ${pkgs.fzf}/bin/fzf --prompt "project > "
)
if ($choice | str trim | is-not-empty) {
cd ($base | path join ($choice | str trim))
}
}
# --- Keybinding: Ctrl+O for open_project ---
$env.config.keybindings = ($env.config.keybindings | append [
{
name: open_project
modifier: control
keycode: char_o
mode: [emacs vi_insert vi_normal]
event: {
send: executehostcommand
cmd: "open_project"
# --- Custom Commands ---
def --env open_project [] {
let base = ($env.HOME | path join "Projects")
let choice = (
${pkgs.fd}/bin/fd -t d -d 1 -a . ($base | path join "Personal") ($base | path join "Work")
| lines
| each {|p| $p | str replace $"($base)/" "" }
| str join "\n"
| ${pkgs.fzf}/bin/fzf --prompt "project > "
)
if ($choice | str trim | is-not-empty) {
cd ($base | path join ($choice | str trim))
}
}
])
# --- Keybinding: Ctrl+O for open_project ---
$env.config.keybindings = ($env.config.keybindings | append [
{
name: open_project
modifier: control
keycode: char_o
mode: [emacs vi_insert vi_normal]
event: {
send: executehostcommand
cmd: "open_project"
}
}
])
'';
};
@@ -284,7 +274,6 @@
home.packages = with pkgs; [
vivid
(callPackage ./_lib/open-project.nix {})
];
};
}

View File

@@ -1,29 +1,30 @@
{...}: {
den.aspects.ssh-client.homeManager = {
config,
lib,
pkgs,
...
}: {
}: let
homeDir = "${
if pkgs.stdenv.hostPlatform.isDarwin
then "/Users"
else "/home"
}/${config.home.username}";
in {
programs.ssh = {
enable = true;
enableDefaultConfig = false;
includes = [
(lib.mkIf pkgs.stdenv.hostPlatform.isLinux "/home/${config.home.username}/.ssh/config_external")
(lib.mkIf pkgs.stdenv.hostPlatform.isDarwin "/Users/${config.home.username}/.ssh/config_external")
"${homeDir}/.ssh/config_external"
];
matchBlocks = {
"*" = {};
"github.com" = {
identitiesOnly = true;
identityFile = [
(lib.mkIf pkgs.stdenv.hostPlatform.isLinux "/home/${config.home.username}/.ssh/id_ed25519")
(lib.mkIf pkgs.stdenv.hostPlatform.isDarwin "/Users/${config.home.username}/.ssh/id_ed25519")
"${homeDir}/.ssh/id_ed25519"
];
};
};
};
home.packages = [pkgs.openssh];
};
}

View File

@@ -28,11 +28,13 @@
tahani-paperless-password = {
sopsFile = ../secrets/tahani-paperless-password;
format = "binary";
path = "/run/secrets/tahani-paperless-password";
};
tahani-email-password = {
sopsFile = ../secrets/tahani-email-password;
format = "binary";
owner = "cschmatzler";
path = "/run/secrets/tahani-email-password";
};
};
virtualisation.docker.enable = true;

View File

@@ -6,7 +6,7 @@
}: {
xdg.configFile."ghostty/config".text = ''
command = ${pkgs.nushell}/bin/nu
theme = Catppuccin Latte
theme = Rose Pine Dawn
window-padding-x = 12
window-padding-y = 3
window-padding-balance = true
@@ -24,19 +24,19 @@
programs.bat = {
enable = true;
config = {
theme = "Catppuccin Latte";
theme = "Rosé Pine Dawn";
pager = "ov";
};
themes = {
"Catppuccin Latte" = {
"Rosé Pine Dawn" = {
src =
pkgs.fetchFromGitHub {
owner = "catppuccin";
repo = "bat";
rev = "6810349b28055dce54076712fc05fc68da4b8ec0";
sha256 = "lJapSgRVENTrbmpVyn+UQabC9fpV1G1e+CdlJ090uvg=";
owner = "rose-pine";
repo = "tm-theme";
rev = "23bb25b9c421cdc9ea89ff3ad3825840cd19d65d";
hash = "sha256-GUFdv5V5OZ2PG+gfsbiohMT23LWsrZda34ReHBr2Xy0=";
};
file = "themes/Catppuccin Latte.tmTheme";
file = "dist/rose-pine-dawn.tmTheme";
};
};
};
@@ -55,11 +55,11 @@
--preview-window='border-rounded' --prompt=' ' --marker=' ' --pointer=' '
--separator='' --scrollbar='' --layout='reverse'
--color=bg+:#CCD0DA,bg:#EFF1F5,spinner:#DC8A78,hl:#D20F39
--color=fg:#4C4F69,header:#D20F39,info:#8839EF,pointer:#DC8A78
--color=marker:#7287FD,fg+:#4C4F69,prompt:#8839EF,hl+:#D20F39
--color=selected-bg:#BCC0CC
--color=border:#9CA0B0,label:#4C4F69
--color=bg+:#f2e9e1,bg:#faf4ed,spinner:#ea9d34,hl:#d7827e
--color=fg:#797593,header:#286983,info:#56949f,pointer:#907aa9
--color=marker:#b4637a,fg+:#575279,prompt:#797593,hl+:#d7827e
--color=selected-bg:#f2e9e1
--color=border:#dfdad9,label:#575279
'';
};

View File

@@ -3,7 +3,7 @@
programs.zellij = {
enable = true;
settings = {
theme = "catppuccin-latte";
theme = "rose-pine-dawn";
default_layout = "default";
default_shell = "${pkgs.nushell}/bin/nu";
pane_frames = false;
@@ -12,6 +12,24 @@
};
};
xdg.configFile."zellij/themes/rose-pine-dawn.kdl".text = ''
themes {
rose-pine-dawn {
fg "#575279"
bg "#f2e9e1"
black "#faf4ed"
red "#b4637a"
green "#6d8f89"
yellow "#ea9d34"
blue "#286983"
magenta "#907aa9"
cyan "#56949f"
white "#575279"
orange "#d7827e"
}
}
'';
xdg.configFile."zellij/layouts/default.kdl".text = ''
layout {
default_tab_template {
@@ -23,26 +41,26 @@
plugin location="file:${pkgs.zjstatus}/bin/zjstatus.wasm" {
hide_frame_for_single_pane "true"
format_left "{mode}#[fg=#1e66f5,bg=#eff1f5,bold] {session}#[bg=#eff1f5] {tabs}"
format_left "{mode}#[fg=#286983,bg=#faf4ed,bold] {session}#[bg=#faf4ed] {tabs}"
format_right "{datetime}"
format_space "#[bg=#eff1f5]"
format_space "#[bg=#faf4ed]"
mode_normal "#[fg=#eff1f5,bg=#1e66f5] "
mode_locked "#[fg=#eff1f5,bg=#fe640b] L "
mode_tab "#[fg=#eff1f5,bg=#40a02b] T "
mode_pane "#[fg=#eff1f5,bg=#8839ef] P "
mode_session "#[fg=#eff1f5,bg=#04a5e5] S "
mode_resize "#[fg=#eff1f5,bg=#df8e1d] R "
mode_move "#[fg=#eff1f5,bg=#ea76cb] M "
mode_search "#[fg=#eff1f5,bg=#d20f39] S "
mode_normal "#[fg=#faf4ed,bg=#286983] "
mode_locked "#[fg=#faf4ed,bg=#ea9d34] L "
mode_tab "#[fg=#faf4ed,bg=#6d8f89] T "
mode_pane "#[fg=#faf4ed,bg=#907aa9] P "
mode_session "#[fg=#faf4ed,bg=#56949f] S "
mode_resize "#[fg=#faf4ed,bg=#ea9d34] R "
mode_move "#[fg=#faf4ed,bg=#d7827e] M "
mode_search "#[fg=#faf4ed,bg=#b4637a] S "
tab_normal "#[fg=#acb0be,bg=#eff1f5] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_active "#[fg=#eff1f5,bg=#1e66f5,bold,underline] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_normal "#[fg=#9893a5,bg=#faf4ed] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_active "#[fg=#faf4ed,bg=#286983,bold,underline] {index} {name} {fullscreen_indicator}{sync_indicator}{floating_indicator}"
tab_fullscreen_indicator "󰊓 "
tab_sync_indicator "󰓦 "
tab_floating_indicator "󰉈 "
datetime "#[fg=#4c4f69,bg=#eff1f5] {format} "
datetime "#[fg=#575279,bg=#faf4ed] {format} "
datetime_format "%A, %d %b %Y %H:%M"
datetime_timezone "Europe/Berlin"
}