277 Commits

Author SHA1 Message Date
Joakim Repomaa
1fd39f6c3f remove unused inputs
Some checks failed
Build Images / build (push) Failing after 11s
Check / check (push) Successful in 4m21s
2026-03-07 13:33:52 +02:00
Joakim Repomaa
86ebfee8e4 setup podman 2026-03-07 13:33:52 +02:00
Joakim Repomaa
182459dd24 fix gitea image registry 2026-03-07 13:33:52 +02:00
Joakim Repomaa
a833701fdb remove home assistant 2026-03-07 13:33:52 +02:00
Joakim Repomaa
8aa474ff08 fix gitea runner 2026-03-07 13:33:52 +02:00
Joakim Repomaa
9db214a1c5 fix invidious 2026-03-07 13:33:52 +02:00
Joakim Repomaa
76b7065493 allow x86 emulation on freun-dev 2026-03-07 13:33:52 +02:00
Joakim Repomaa
efe6863358 fix synthing 2026-03-07 13:33:52 +02:00
Joakim Repomaa
e0c1a457b0 update flake inputs 2026-03-07 13:33:51 +02:00
Joakim Repomaa
e3dfbf251f rekey secrets 2026-03-07 13:33:51 +02:00
Joakim Repomaa
2358ea6dcd apu local dhcp lease dns and invidious 2026-03-07 13:33:51 +02:00
Joakim Repomaa
88246d0b0a setup niri 2026-03-07 13:33:51 +02:00
Joakim Repomaa
06778795b2 spawn opencode from zed with keybinding 2026-03-07 13:28:03 +02:00
Joakim Repomaa
7a0dbb214b setup walker and elephant 2026-03-07 13:28:03 +02:00
Joakim Repomaa
9fbe748aa1 add voxtype 2026-03-07 13:28:02 +02:00
Joakim Repomaa
a05fd03aa8 opencode config update 2026-03-07 13:04:16 +02:00
Joakim Repomaa
167d446144 don't build apu on target 2026-03-07 12:35:56 +02:00
Joakim Repomaa
855f19a19b add badge for image builds [skip ci] 2026-02-22 18:51:36 +02:00
Joakim Repomaa
6878efd7bb update workflow status badge
All checks were successful
Build Images / build (push) Successful in 1m11s
Check / check (push) Successful in 3m51s
2026-02-22 18:48:38 +02:00
Joakim Repomaa
7f6fc155c5 fix push image step
All checks were successful
Build Images / build (push) Successful in 1m12s
Check / check (push) Successful in 3m45s
2026-02-22 18:29:30 +02:00
Joakim Repomaa
b1ebf5aff2 fix image build 2026-02-22 17:43:49 +02:00
Joakim Repomaa
5bff4d9713 don't try building x86_64 image
Some checks failed
Build Images / build (push) Failing after 1m30s
Check / check (push) Successful in 3m38s
2026-02-22 16:31:43 +02:00
Joakim Repomaa
13e119a6c3 apu: setup dynamic dns record based on DHCP leases
Some checks failed
Build Images / build (push) Failing after 1m21s
Check / check (push) Successful in 3m36s
2026-02-21 13:05:22 +02:00
Joakim Repomaa
6690b5c1ea try fixing docker-image build
Some checks failed
Check / check (push) Successful in 3m47s
Build Images / build (push) Failing after 1m24s
2026-02-20 21:50:30 +02:00
Joakim Repomaa
c8c5de288f add build images workflow
Some checks failed
Check / check (push) Successful in 3m46s
Build Images / check (push) Failing after 45s
2026-02-20 21:21:28 +02:00
Joakim Repomaa
c450f29437 check all systems 2026-02-20 21:20:23 +02:00
Joakim Repomaa
abfc978a51 make sure node owns /nix
Some checks failed
Check / check (push) Failing after 14s
2026-02-20 21:09:17 +02:00
Joakim Repomaa
bd6132ede5 add bash to docker image
Some checks failed
Check / check (push) Failing after 11s
2026-02-20 20:56:36 +02:00
Joakim Repomaa
e1f21c2a2a add ca certs
Some checks failed
Check / check (push) Failing after 43s
2026-02-20 20:48:42 +02:00
Joakim Repomaa
b3bd943175 remove unused input
All checks were successful
Check / check (push) Successful in 3m20s
2026-02-20 18:59:08 +02:00
Joakim Repomaa
a41c885bb2 fix check workflow 2026-02-20 18:58:55 +02:00
Joakim Repomaa
45ce478dd4 move to gitea
Some checks failed
Check / build (push) Failing after 19s
2026-02-20 17:11:28 +02:00
Joakim Repomaa
49cabcad7a fix deprecation warnings 2026-02-20 16:56:19 +02:00
Joakim Repomaa
5fec78b813 update flakes 2026-02-20 16:55:48 +02:00
Joakim Repomaa
2351971fd4 freun.dev: add gitea 2026-02-20 15:33:15 +02:00
Joakim Repomaa
7f7b1ffe08 home: use zen for zed 2026-02-20 15:32:50 +02:00
Joakim Repomaa
2e53fe0931 home: update opencode and lazygit configs 2026-02-20 15:32:50 +02:00
Joakim Repomaa
07f3668d5e add AGENTS.md 2026-02-09 15:13:08 +02:00
Joakim Repomaa
8d7c0cd01d home/zed: update configuration and remove userTasks option 2026-02-09 15:11:14 +02:00
Joakim Repomaa
ffbe877dff custom-pkgs: remove flameshot package 2026-02-09 15:11:14 +02:00
Joakim Repomaa
a1325b49c1 secrets: add actual and voidauth secrets, update hetzner 2026-02-09 15:11:14 +02:00
Joakim Repomaa
6aa4abeead modules/services: update octodns, sillytavern, webserver, and workout-sync 2026-02-09 15:11:13 +02:00
Joakim Repomaa
8823354606 home: migrate git config to settings format and add new packages 2026-02-09 15:11:13 +02:00
Joakim Repomaa
ac61399a18 hosts: update configurations for 25.11 and add new services 2026-02-09 15:11:10 +02:00
Joakim Repomaa
4d09b3546f modules: add actual, nqptp, and voidauth service modules 2026-02-09 15:11:10 +02:00
Joakim Repomaa
a88da9b269 flake: update inputs to 25.11 and add tonearm and voidauth 2026-02-09 15:11:10 +02:00
Joakim Repomaa
ea04ee3517 update zed config 2026-01-07 13:16:21 +02:00
Joakim Repomaa
832fe0c589 force lazygit to allow gpg signing 2026-01-07 13:15:54 +02:00
Joakim Repomaa
fae8bd5b05 add nr alias for easier nix run 2026-01-07 13:15:54 +02:00
Joakim Repomaa
48ec6fac7c add opencode 2026-01-07 13:15:54 +02:00
Joakim Repomaa
a6e4461954 add font Tajawal 2026-01-07 13:10:34 +02:00
Joakim Repomaa
7e909dbc21 use rpi imager from unstable 2026-01-07 13:10:15 +02:00
Joakim Repomaa
81bc564228 remap PR keybinding for lazygit 2025-10-30 14:30:33 +02:00
Joakim Repomaa
80da9e35cd remove obsolete packages 2025-10-30 14:29:39 +02:00
Joakim Repomaa
9818e2c876 add chromium to moco packages 2025-10-22 14:48:11 +03:00
Joakim Repomaa
8e4aea6ccf remove unused desktop apps 2025-10-20 11:08:56 +03:00
Joakim Repomaa
2c5b597b59 update flake inputs 2025-10-20 11:06:09 +03:00
Joakim Repomaa
0746057d8c fix ruby-lsp and rubocop for zed 2025-10-20 11:05:54 +03:00
Joakim Repomaa
5ee300fea7 use adwaita cursor 2025-10-20 11:05:31 +03:00
Joakim Repomaa
0c988ec5ac use latest devbox 2025-10-20 11:05:03 +03:00
Joakim Repomaa
f7f3f393de use rose pine cursor theme 2025-10-09 20:25:59 +03:00
Joakim Repomaa
157efae198 improve otp menu 2025-10-09 19:33:48 +03:00
Joakim Repomaa
6fdde36bd8 add rclone 2025-10-09 19:33:07 +03:00
Joakim Repomaa
7750db3cbf add dmenu style password menu using walker 2025-10-09 19:32:47 +03:00
Joakim Repomaa
8f85e5662a enable docker autoprune 2025-10-09 19:30:50 +03:00
Joakim Repomaa
71ff276be9 use unstable package for tailscale 2025-10-09 19:30:29 +03:00
Joakim Repomaa
d4004bcf8e add vivaldi to allowed 1password browsers 2025-10-09 19:30:17 +03:00
Joakim Repomaa
33253ab0da add protonmail bridge 2025-10-09 19:28:09 +03:00
Joakim Repomaa
6c1a8d059f update flake inputs 2025-10-09 19:27:07 +03:00
Joakim Repomaa
dd4fa5b258 use nh instead of colmena for locally applying 2025-10-09 19:26:57 +03:00
Joakim Repomaa
14b23c5463 update zed config to fix deprecation warnings 2025-08-08 09:07:35 +03:00
Joakim Repomaa
52a8dbede0 radish: inhibit sleep for borgbackup job 2025-07-23 20:07:16 +03:00
Joakim Repomaa
8774faa201 freun.dev: add glance 2025-07-23 20:04:03 +03:00
Joakim Repomaa
fa02a302dc remove unused symbols 2025-07-23 14:46:30 +03:00
Joakim Repomaa
fac2055afc ci: add manifest for turny 2025-07-23 14:43:22 +03:00
Joakim Repomaa
91d5d73002 maxJobs should equal number of cores on host 2025-07-23 14:37:48 +03:00
Joakim Repomaa
4233c3dabf simplify mkConfiguration 2025-07-23 14:37:31 +03:00
Joakim Repomaa
511f6caad1 ci: use badge for repository, not for user 2025-07-23 14:37:06 +03:00
Joakim Repomaa
c6e4f675d5 ci: try building freun-dev with remote builder 2025-07-23 14:12:31 +03:00
Joakim Repomaa
bea16d9a2f remove radish-vm 2025-07-23 13:22:38 +03:00
Joakim Repomaa
f73db13392 always build freun-dev on target 2025-07-23 13:21:54 +03:00
Joakim Repomaa
4a9dcd098f add turny 2025-07-23 13:21:54 +03:00
Joakim Repomaa
e317f9b0e4 setup distributed builds 2025-07-23 13:21:00 +03:00
Joakim Repomaa
40598d20c7 apu: add avahi 2025-07-23 13:20:59 +03:00
Joakim Repomaa
5b3ceb239d add radish-vm for testing 2025-07-11 11:37:28 +03:00
Joakim Repomaa
3e37b4bcbc freun.dev: fix dmarc record 2025-07-08 16:13:30 +03:00
Joakim Repomaa
3a03103fc7 cleanup 2025-07-08 15:53:39 +03:00
Joakim Repomaa
6fb5c1ffa5 remove auto-cpufreq 2025-07-08 14:53:45 +03:00
Joakim Repomaa
5be24a850a radish: use ppd instead of tlp 2025-07-08 13:18:43 +03:00
Joakim Repomaa
a0f38794b1 fix apu build 2025-07-03 13:33:23 +03:00
Joakim Repomaa
7f3d6f79d2 fix freun-dev CI build 2025-07-03 13:33:07 +03:00
Joakim Repomaa
6e2f424384 move openscad to common hm packages and add bambu slicer 2025-07-03 13:00:42 +03:00
Joakim Repomaa
ebc2976c31 fix deprecated zed settings 2025-07-03 12:59:41 +03:00
Joakim Repomaa
405817fa89 fix uberspace mail records 2025-06-23 00:30:44 +03:00
Joakim Repomaa
3e7e960b8a fix deprecated settings 2025-06-17 11:53:49 +03:00
Joakim Repomaa
4d267dbde8 update flake inputs 2025-06-16 19:39:20 +03:00
Joakim Repomaa
82298cfb4d simplify hetzner api key handling on freun-dev 2025-06-16 19:39:10 +03:00
Joakim Repomaa
8acda612dc fix weechat 2025-06-16 19:38:48 +03:00
Joakim Repomaa
d1e1ef60d9 update cargo hash for syntax renderer 2025-06-16 19:38:37 +03:00
Joakim Repomaa
02c14d0f0f fix crystal support for zed 2025-06-16 19:38:14 +03:00
Joakim Repomaa
de0dc03840 use feishin from nixpkgs-unstable 2025-06-16 19:38:02 +03:00
Joakim Repomaa
e2c8ba9c3a update to 25.05 2025-06-11 00:26:49 +03:00
Joakim Repomaa
584380ef7f update flake inputs 2025-06-10 23:26:42 +03:00
Joakim Repomaa
269bb6ac6a use tailscale auth for hledger 2025-06-10 23:26:27 +03:00
Joakim Repomaa
4d91990ea1 update zed config 2025-06-10 23:25:34 +03:00
Joakim Repomaa
0462a0fde9 add feishin to home packages 2025-06-10 23:25:21 +03:00
Joakim Repomaa
88237acf7c fix glowing bear 2025-05-26 09:38:56 +03:00
Joakim Repomaa
2e2f4e5fdc deploy isa's portfolio 2025-05-26 09:38:25 +03:00
Joakim Repomaa
027ae0bef8 add hledger 2025-05-26 09:36:55 +03:00
Joakim Repomaa
d625368bd0 update flake inputs 2025-05-26 09:35:12 +03:00
Joakim Repomaa
68aa04eff3 remove deprecated --experimental-flake-eval flag from colmena commands 2025-05-26 09:34:49 +03:00
Joakim Repomaa
70935eea43 update flake inputs 2025-05-12 09:32:43 +03:00
Joakim Repomaa
ef097ca283 home: fix rubocop for zed 2025-04-02 09:19:40 +03:00
Joakim Repomaa
d0cb28b8b1 home: use claude 3.7 in zed 2025-04-02 09:19:07 +03:00
Joakim Repomaa
e44dba355a home: zed and lazygit config 2025-03-30 20:15:57 +03:00
Joakim Repomaa
9bac7b2da9 freun.dev: add weechat 2025-03-30 20:15:57 +03:00
Joakim Repomaa
0ecfaf4d3c apu: add invidious 2025-03-30 20:14:27 +03:00
Joakim Repomaa
5c5f3dd964 add home assistant 2025-03-30 20:09:02 +03:00
Joakim Repomaa
8586351606 add mosquitto 2025-03-30 20:09:02 +03:00
Joakim Repomaa
0629466bf2 add gitlab runner 2025-03-30 20:09:01 +03:00
Joakim Repomaa
d61c0f96c6 use latest node for zed 2025-03-30 20:09:01 +03:00
Joakim Repomaa
ab560e41f3 update flake inputs 2025-03-19 10:01:22 +02:00
Joakim Repomaa
06acd91781 pass base branch to lazygit pr command 2025-03-19 10:00:58 +02:00
Joakim Repomaa
5c61ebf9ff radish: use regular instead of rootless docker 2025-03-14 14:07:25 +02:00
Joakim Repomaa
a1dee56cce radish: switch uids of jokke and moco users 2025-03-14 14:05:51 +02:00
Joakim Repomaa
177b24d9ce remove obsolete makefile 2025-03-14 14:05:05 +02:00
Joakim Repomaa
61cacdc817 add devbox 2025-03-09 22:24:42 +02:00
Joakim Repomaa
d99a714a41 add ncspot 2025-03-09 22:23:54 +02:00
Joakim Repomaa
a52861aa40 fix rufo 2025-03-03 21:58:44 +02:00
Joakim Repomaa
a1f5e2acf7 allow building with nix build 2025-03-03 11:29:51 +02:00
Joakim Repomaa
abd970755a add zed tasks 2025-03-03 10:00:13 +02:00
Joakim Repomaa
7c477722fc fix lint 2025-03-03 10:00:08 +02:00
Joakim Repomaa
f9cb771979 move commit keybindings conflicted with zed keybindings 2025-03-03 09:59:57 +02:00
Joakim Repomaa
4b4464a024 enable edit predictions in zed 2025-03-03 09:59:30 +02:00
Joakim Repomaa
173b234f10 update syntax-renderer 2025-03-03 09:59:05 +02:00
Joakim Repomaa
2248547f92 fix crystalline and rust-analyzer language servers 2025-03-03 09:03:10 +02:00
Joakim Repomaa
e65c450464 fix yaml ls 2025-03-03 09:02:46 +02:00
Joakim Repomaa
62d0963413 add rustfmt as external formatter 2025-03-03 09:02:17 +02:00
Joakim Repomaa
6cd354885a freun-dev: use mealie from nixpkgs-unstable 2025-03-01 13:30:05 +02:00
Joakim Repomaa
d41b3757ac update flake inputs 2025-03-01 13:26:33 +02:00
Joakim Repomaa
f4993fc6b2 update flake.lock 2025-03-01 13:23:58 +02:00
Joakim Repomaa
8dfc9b5a6d run nixfmt 2025-03-01 13:23:01 +02:00
Joakim Repomaa
a93d4afbcf radish: add zed 2025-03-01 13:20:50 +02:00
Joakim Repomaa
5c4e161076 radish: add zenbrowser 2025-03-01 13:19:05 +02:00
Joakim Repomaa
2df6c1e7a2 radish: install nerd fonts globally 2025-03-01 13:18:20 +02:00
Joakim Repomaa
5f8e14768f radish: enable ollama 2025-03-01 13:17:01 +02:00
Joakim Repomaa
af9f07a2fe allow large uploads for immich 2025-03-01 13:16:27 +02:00
Joakim Repomaa
f3c2316b92 add new hastebin token 2025-03-01 13:15:53 +02:00
Joakim Repomaa
83594e09a9 add syntax renderer to hastebin 2025-03-01 13:14:58 +02:00
Joakim Repomaa
6a1244821e add hastebin cli home module 2025-02-20 19:54:23 +02:00
Joakim Repomaa
484d68c776 freun-dev: fix hastebin 2025-02-20 11:08:48 +02:00
Joakim Repomaa
208d566e14 add plocate 2025-02-18 16:35:42 +02:00
Joakim Repomaa
f0e93a837d refactor 2025-02-18 16:35:42 +02:00
Joakim Repomaa
41bd91deb1 radish: add borgbackup 2025-02-18 16:35:38 +02:00
Joakim Repomaa
83f9e00416 fix adguardhome config 2025-02-18 15:39:39 +02:00
Joakim Repomaa
18c93e224a freun-dev: add uptime kuma 2025-02-17 15:31:40 +02:00
Joakim Repomaa
da0fab832c setup mealie 2025-02-17 12:09:37 +02:00
Joakim Repomaa
4e09c07522 remove extra hosts from radish (adguardhome overwrites the dns record now) 2025-02-17 12:08:11 +02:00
Joakim Repomaa
48be030ac7 setup postgres db for readeck 2025-02-17 12:07:21 +02:00
Joakim Repomaa
75186e2f19 add adguardhome 2025-02-15 16:50:47 +02:00
Joakim Repomaa
c15d518e4a setup octodns for automatic dns records 2025-02-15 11:16:40 +02:00
Joakim Repomaa
459cae639d make sure postgres is running before starting dnote service 2025-02-15 03:46:53 +02:00
Joakim Repomaa
ecc02f4b59 setup colmena for deployment 2025-02-14 01:49:38 +02:00
Joakim Repomaa
6a10625d4e set correct flake path for nh 2025-02-13 16:03:54 +02:00
Joakim Repomaa
1cf3c7d8dd add dnote 2025-02-13 16:03:10 +02:00
Joakim Repomaa
ddc6e6d575 fix local tailscale 2025-02-13 14:29:12 +02:00
Joakim Repomaa
309f43a276 fix duplicate port 2025-02-13 01:29:54 +02:00
Joakim Repomaa
93a518a6d5 immich settings 2025-02-13 01:18:59 +02:00
Joakim Repomaa
b42eae0788 fix duplicate port 2025-02-13 01:13:20 +02:00
Joakim Repomaa
94eb995ee7 use native immich module 2025-02-13 01:10:52 +02:00
Joakim Repomaa
2fd8d559c9 fix duplicate port 2025-02-13 00:16:04 +02:00
Joakim Repomaa
8f4c08f1bc downgrade postgres to 16 2025-02-13 00:04:40 +02:00
Joakim Repomaa
c519f8d83e refactor 2025-02-12 23:54:07 +02:00
Joakim Repomaa
dd4e5c63e3 add donetick 2025-02-12 20:58:10 +02:00
Joakim Repomaa
0f79298aa2 rekey secrets 2025-02-12 17:27:42 +02:00
Joakim Repomaa
cfa04ddc31 add readeck 2025-02-11 23:32:41 +02:00
Joakim Repomaa
0bc01cd2b1 use agenix 2025-02-11 22:40:39 +02:00
Joakim Repomaa
8a1f2c4968 add back pcscd 2025-02-11 11:52:41 +02:00
Joakim Repomaa
227319b662 refactor 2025-02-11 10:31:10 +02:00
Joakim Repomaa
21f3f9e390 update hastebin 2025-02-11 10:19:56 +02:00
Joakim Repomaa
43f44ad06d fix yubikey 2025-02-11 10:10:28 +02:00
Joakim Repomaa
9337d0e14c update hastebin 2025-02-10 14:12:15 +02:00
Joakim Repomaa
4d28bc0f6b fix charset 2025-02-10 13:59:32 +02:00
Joakim Repomaa
00783c9fca update hastebin 2025-02-10 13:36:29 +02:00
Joakim Repomaa
e809cdc57c use hastebin nixos module 2025-02-10 10:53:28 +02:00
Joakim Repomaa
70cfdfaed2 update hastebin 2025-02-10 10:53:28 +02:00
Joakim Repomaa
1e3abcaeb2 use ip for binding 2025-02-10 03:25:28 +02:00
Joakim Repomaa
fa252d1334 fix nginx config 2025-02-10 03:21:11 +02:00
Joakim Repomaa
70ef355d28 update hastebin 2025-02-10 03:20:28 +02:00
Joakim Repomaa
27da7f97df fix unit name 2025-02-10 03:09:05 +02:00
Joakim Repomaa
dbe1b3109c set max size for nginx 2025-02-10 03:07:00 +02:00
Joakim Repomaa
0f8491a18e replace rustypaste with hastebin 2025-02-10 03:04:01 +02:00
Joakim Repomaa
222d4b9527 update workout-sync 2025-02-09 23:07:01 +02:00
Joakim Repomaa
7aad5b5bf0 fix yubikey and gtrackmap 2025-02-09 22:11:42 +02:00
Joakim Repomaa
a17b0b46b5 update workout-tracker 2025-02-09 11:10:31 +02:00
Joakim Repomaa
d078df43ad increase max body size for workout-sync 2025-02-09 00:48:18 +02:00
Joakim Repomaa
f81f8b2a47 fix workout tracker url 2025-02-09 00:27:28 +02:00
Joakim Repomaa
7d63777170 update workout-tracker 2025-02-08 22:44:56 +02:00
Joakim Repomaa
913d3d1238 refactor 2025-02-08 21:17:11 +02:00
Joakim Repomaa
271d9e8f88 use nix devshell for helpers 2025-02-08 15:42:02 +02:00
Joakim Repomaa
0cf0753e5b refactor 2025-02-08 15:42:02 +02:00
Joakim Repomaa
d32a5dc147 add workout-sync 2025-02-08 15:42:02 +02:00
Joakim Repomaa
0b4e55e0b4 replace snips.sh with rustypaste 2025-02-08 15:42:02 +02:00
Joakim Repomaa
6a5789d45b refactor 2025-02-08 15:42:02 +02:00
Joakim Repomaa
e0d0c12a8e update immich 2025-02-08 15:42:02 +02:00
Joakim Repomaa
953d2b5c5d fix syncthing proxy
fix syncthing proxy
2025-02-08 15:42:01 +02:00
Joakim Repomaa
2d6e8607f3 vaultwarden serves websockets on the same port as http 2025-02-08 15:42:01 +02:00
Joakim Repomaa
599ca53f1d explicitly use v4 localhost 2025-02-08 15:42:01 +02:00
Joakim Repomaa
39134ba640 fix syncthing service 2025-02-08 15:42:01 +02:00
Joakim Repomaa
71e65bf687 remove webroot 2025-02-08 15:42:01 +02:00
Joakim Repomaa
eb3682f19a fix snips service 2025-02-08 15:42:01 +02:00
Joakim Repomaa
5c456d4a50 make sure web ports are allowed 2025-02-08 15:42:01 +02:00
Joakim Repomaa
e2f8d1eeff add authorized key for root 2025-02-08 15:42:01 +02:00
Joakim Repomaa
9f446bc1f6 migrate freun.dev to arm server 2025-02-08 15:41:58 +02:00
Joakim Repomaa
8664b2c976 disable autoupgrade 2025-02-08 15:41:14 +02:00
Joakim Repomaa
185e9b4f03 update gtrackmap 2025-02-08 15:38:51 +02:00
Joakim Repomaa
65a7bc1720 add evaluation jobs 2025-02-08 15:38:51 +02:00
Joakim Repomaa
0e83d6538c add devshell 2025-02-08 15:38:51 +02:00
Joakim Repomaa
6aa0640684 move ssh key to variable for DRYness 2025-02-08 15:38:51 +02:00
Joakim Repomaa
7a24ac5fe6 refactor 2025-02-08 15:38:51 +02:00
Joakim Repomaa
983e313e11 freun.dev add snips 2025-02-08 15:38:51 +02:00
Joakim Repomaa
f1d5a4b2f2 ignore /result 2025-02-08 15:38:51 +02:00
Joakim Repomaa
2a36964fcc refactor vlan and firewall config into modules
fix bug with vlans module

fix vlans
2025-02-08 15:38:50 +02:00
Joakim Repomaa
6026e064fd freun.dev: setup transport layout offloads for tailscale 2025-02-02 22:48:09 +02:00
Joakim Repomaa
6e041b2d99 apu: setup transport layout offloads for tailscale 2025-02-02 22:41:56 +02:00
Joakim Repomaa
c5faa5e2f4 add makefile for convenient rebuilding 2025-02-02 21:55:54 +02:00
Joakim Repomaa
e215c05588 update flake inputs 2025-02-02 21:55:30 +02:00
Joakim Repomaa
e6cee6d299 apu: add support for nix-command and flakes, enable gc 2025-02-02 21:45:21 +02:00
Joakim Repomaa
ea0738bcea use tailscale to connect to apu 2025-02-02 21:14:41 +02:00
Joakim Repomaa
9a22cca0ea add nh to apu and freun.dev 2025-02-02 21:14:05 +02:00
Joakim Repomaa
73119f5df3 add readme with build status badge 2025-02-02 20:29:10 +02:00
Joakim Repomaa
a201aa2de0 refactor file structure 2025-02-02 20:26:12 +02:00
Joakim Repomaa
908b26449d dry run in ci 2025-02-02 18:56:46 +02:00
Joakim Repomaa
636326f00f fix deprecation issues in apu config 2025-02-02 18:56:25 +02:00
Joakim Repomaa
89474fb479 add apu configuration 2025-02-02 18:39:51 +02:00
Joakim Repomaa
94d9a45a38 fix locale and font troubles 2025-02-02 15:34:28 +02:00
Joakim Repomaa
c757f0892d refactor 2025-01-30 21:15:02 +02:00
Joakim Repomaa
12647a2e77 move home configs to root 2025-01-30 20:47:28 +02:00
Joakim Repomaa
ea7c4cbf31 global pkgs 2025-01-30 19:33:33 +02:00
Joakim Repomaa
6b8c9d6abf fix fontconfig 2025-01-30 18:34:22 +02:00
Joakim Repomaa
1050e28b9f switch to stable
Revert "radish: fix deprecation warning"

This reverts commit 2d1d2c5839.
2025-01-30 15:35:14 +02:00
Joakim Repomaa
98a0a95166 don't try building radish config (running out of disk space) 2025-01-30 09:44:15 +02:00
Joakim Repomaa
4c697a041f radish: remove meilisearch 2025-01-30 09:43:28 +02:00
Joakim Repomaa
079f6518ae fix build 2025-01-29 14:21:31 +02:00
Joakim Repomaa
1cf76a05af disable ollama 2025-01-29 14:17:00 +02:00
Joakim Repomaa
7d005ea27d add build manifests 2025-01-29 14:13:25 +02:00
Joakim Repomaa
1241c82bd7 add home manager configs 2025-01-29 13:59:14 +02:00
Joakim Repomaa
ae4f9c2825 freun.dev: update workout tracker 2025-01-29 13:59:13 +02:00
Joakim Repomaa
2d1d2c5839 radish: fix deprecation warning 2025-01-25 19:33:29 +02:00
Joakim Repomaa
c769fdfd0c update flake inputs 2025-01-25 16:02:05 +02:00
Joakim Repomaa
4739b9316e freun.dev: update immich 2025-01-25 16:00:15 +02:00
Joakim Repomaa
25fa204428 freun.dev: update postgres to 17 2025-01-05 16:55:19 +02:00
Joakim Repomaa
4900d460d0 freun.dev: update immich 2025-01-05 16:41:58 +02:00
Joakim Repomaa
f3420f4072 lock gtrackmap and update flake inputs 2025-01-05 16:29:15 +02:00
Joakim Repomaa
c51affcc6b freun.dev: disable hydra 2025-01-05 16:07:07 +02:00
Joakim Repomaa
6ea8b4df03 update flake input 2024-12-27 18:20:10 +02:00
Joakim Repomaa
be2c73e78f update flake inputs 2024-11-22 16:49:08 +01:00
Joakim Repomaa
439a130163 radish: add jack support 2024-11-21 19:43:03 +01:00
Joakim Repomaa
c441b1cb2c gotosocial: allow registration and expose public timeline 2024-11-04 22:52:26 +02:00
Joakim Repomaa
79b598cd25 fix autoupdate 2024-11-04 16:30:17 +02:00
Joakim Repomaa
0c95824992 add gotosocial to freun.dev 2024-11-04 16:13:03 +02:00
Joakim Repomaa
f54eda2815 update flake inputs 2024-11-04 13:27:32 +02:00
Joakim Repomaa
3558aeecda add radish configuration 2024-11-04 12:43:09 +02:00
Joakim Repomaa
89d7e656f9 update nixpkgs and immich 2024-10-25 11:54:01 +03:00
Joakim Repomaa
ee2fd183f3 update nixpkgs 2024-09-30 11:34:03 +03:00
Joakim Repomaa
c38f454595 update immich 2024-09-30 11:31:31 +03:00
Joakim Repomaa
eabe62bd5f use upstream binary for workout-tracker 2024-08-26 11:17:20 +03:00
Joakim Repomaa
87e7e2fbe4 add workout tracker service 2024-08-26 10:30:06 +03:00
Joakim Repomaa
47d18621b2 update nixpkgs 2024-08-26 10:29:47 +03:00
Joakim Repomaa
de862b1ba0 use nftables 2024-08-06 23:13:09 +03:00
Joakim Repomaa
4c4597af0e remove wireguard 2024-08-06 22:37:41 +03:00
Joakim Repomaa
08d97b1c07 update flakes 2024-08-06 22:35:24 +03:00
Joakim Repomaa
939f5be8d6 add tailscale 2024-08-06 22:30:21 +03:00
Joakim Repomaa
629079b45e pin immich version 2024-07-16 11:16:08 +03:00
Joakim Repomaa
fdd310863a update nixpkgs 2024-07-16 11:04:48 +03:00
Joakim Repomaa
07e6220808 add git 2024-07-16 11:00:02 +03:00
Joakim Repomaa
6cb5322e7b update immich 2024-07-16 10:58:58 +03:00
141 changed files with 11292 additions and 885 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake .

View File

@@ -0,0 +1,38 @@
name: Build Images
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: nixos-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Build aarch64 Image
run: nix build .#dockerImages.aarch64-linux.node --out-link ./image-aarch64.tar.gz --option build-hook ""
- name: Push to Gitea Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
REGISTRY="${{ github.server_url }}"
REGISTRY="${REGISTRY#https://}"
REGISTRY="${REGISTRY#http://}"
# Push aarch64 image
skopeo copy \
--insecure-policy \
--dest-creds "${{ github.repository_owner }}:${{ secrets.REGISTRY_TOKEN }}" \
"docker-archive:./image-aarch64.tar.gz" \
"docker://${REGISTRY}/${{ github.repository }}/node:latest"
skopeo copy \
--insecure-policy \
--dest-creds "${{ github.repository_owner }}:${{ secrets.REGISTRY_TOKEN }}" \
"docker-archive:./image-aarch64.tar.gz" \
"docker://${REGISTRY}/${{ github.repository }}/node:latest-arm64"

View File

@@ -0,0 +1,18 @@
name: Check
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: nixos-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check
run: nix flake check --all-systems

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/result
/.direnv
/.gcroots
*.qcow2

41
.zed/tasks.json Normal file
View File

@@ -0,0 +1,41 @@
[
{
"label": "Apply local",
"command": "apply-local",
"use_new_terminal": false,
"allow_concurrent_runs": true,
"reveal": "no_focus",
"reveal_target": "dock",
"hide": "on_success",
},
{
"label": "Apply remote",
"command": "apply",
"args": ["--build-on-target"],
"use_new_terminal": false,
"allow_concurrent_runs": true,
"reveal": "no_focus",
"reveal_target": "dock",
"hide": "on_success",
},
{
"label": "Apply on freun-dev",
"command": "apply",
"args": ["--on", "freun-dev"],
"use_new_terminal": false,
"allow_concurrent_runs": true,
"reveal": "no_focus",
"reveal_target": "dock",
"hide": "on_success",
},
{
"label": "Apply on apu",
"command": "apply",
"args": ["--on", "apu"],
"use_new_terminal": false,
"allow_concurrent_runs": true,
"reveal": "no_focus",
"reveal_target": "dock",
"hide": "on_success",
},
]

23
AGENTS.md Normal file
View File

@@ -0,0 +1,23 @@
# Agent Guidelines for NixOS Configuration
## Build & Deploy Commands
- `nix flake check` - Validate flake configuration
- `build <host>` - Build configuration for specific host (e.g., `build radish`)
- `apply <host>` - Deploy to remote host via colmena
- `apply-local` - Apply configuration locally using nh
- `nix build .#nixosConfigurations.<host>.config.system.build.toplevel` - Build specific host
## Code Style
- **Format**: Use `nixfmt-rfc-style` for all .nix files
- **Imports**: List imports first, group by: local modules, external inputs (inputs.*)
- **Function args**: Multi-line with trailing comma when >3 args: `{ config, lib, pkgs, ... }:`
- **Options**: Define in `options.<service>` block, implement in `config = lib.mkIf cfg.enable { ... }`
- **Types**: Use `lib.types.*` for option types (str, bool, int, etc.)
- **String interpolation**: Use `"${}"` for Nix expressions, prefer lib.getExe for binaries
- **Conditionals**: Use `lib.mkIf`, `lib.mkDefault`, `lib.mkMerge` for NixOS options
## Structure
- Host configs in `hosts/<name>/` with configuration.nix, default.nix, hardware-configuration.nix
- Reusable modules in `modules/` and `modules/services/`
- Custom packages in `custom-pkgs/` using util.package pattern
- Secrets managed via agenix in `secrets/` directory

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# My NixOS Configurations
[![check workflow status](https://git.freun.dev/repomaa/nixos/actions/workflows/check.yml/badge.svg)](https://git.freun.dev/repomaa/nixos/actions?workflow=check.yml) [![build images workflow status](https://git.freun.dev/repomaa/nixos/actions/workflows/build-images.yml/badge.svg)](https://git.freun.dev/repomaa/nixos/actions?workflow=build-images.yml)

View File

@@ -0,0 +1 @@
{ util, ... }: util.package { }

View File

@@ -0,0 +1,14 @@
{ pkgs, ... }:
let
inherit (pkgs) crystal libffi;
in
crystal.overrideAttrs (oldAttrs: {
makeFlags = oldAttrs.makeFlags ++ [
"interpreter=1"
];
buildInputs = oldAttrs.buildInputs ++ [
libffi
];
FLAGS = [ "--single-module" ];
doCheck = false;
})

24
custom-pkgs/default.nix Normal file
View File

@@ -0,0 +1,24 @@
{
lib,
inputs,
pkgs-unstable,
...
}:
let
inherit (builtins) attrNames filter readDir;
dirs =
let
files = readDir ./.;
in
filter (name: files."${name}" == "directory") (attrNames files);
util = import ./util.nix;
in
lib.composeManyExtensions (
map (
dir:
import ./${dir} {
util = util dir;
inherit inputs lib pkgs-unstable;
}
) dirs
)

View File

@@ -0,0 +1,7 @@
{
util,
pkgs-unstable,
lib,
...
}:
util.package { inherit pkgs-unstable lib; }

96
custom-pkgs/otp/otp.cr Normal file
View File

@@ -0,0 +1,96 @@
require "option_parser"
NOTIFY_ICON = {{env("NOTIFY_ICON")}}
NOTIFY_SEND_COMMAND = {{env("NOTIFY_SEND")}}
WL_COPY_COMMAND = {{env("WL_COPY")}}
YKMAN_COMMAND = {{env("YKMAN")}}
SKIM_COMMAND = {{env("SKIM")}}
WALKER_COMMAND = {{env("WALKER")}}
class OTP
private property dmenu_command : String = SKIM_COMMAND
private property dmenu_args = [] of String
def run
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{PROGRAM_NAME} [options]"
opts.on("-h", "--help", "Display this help message") do
puts opts
exit
end
opts.on("-w", "--walker", "Use walker as dmenu") do
self.dmenu_command = WALKER_COMMAND
self.dmenu_args << "--dmenu" << "-p" << "Select account:"
end
end
parser.parse
self.dmenu_args += ["-q", *ARGV] unless ARGV.empty?
account = select_account
code = code_for(account)
copy(code)
end
private def notify_send(message, urgency = :normal)
return if message.chomp.empty?
Process.run(NOTIFY_SEND_COMMAND, ["-i", NOTIFY_ICON, "-u", urgency.to_s, "OTP", message.chomp])
end
private def list_accounts
error = IO::Memory.new
output = IO::Memory.new
status = Process.run(YKMAN_COMMAND, %w[oath accounts list], output: output, error: error)
errors = error.rewind.gets_to_end.try(&.chomp)
notify_send(errors, urgency: status.success? ? :normal : :critical) unless errors.empty?
exit status.exit_code unless status.success?
output.rewind.gets_to_end
end
private def select_account
output = IO::Memory.new
accounts = list_accounts
puts accounts
input = IO::Memory.new(accounts)
Process.run(dmenu_command, dmenu_args, output: output, input: input)
account = output.rewind.gets(chomp: true)
return account if account
notify_send("No account selected", urgency: :critical)
exit 1
end
private def code_for(account)
output = IO::Memory.new
Process.run(YKMAN_COMMAND, ["oath", "accounts", "code", "-s", account], output: output) do |process|
spawn do
while line = process.error.gets(chomp: true)
notify_send(line)
end
end
end
exit $?.exit_code unless $?.success?
code = output.rewind.gets(chomp: true)
return code if code
notify_send("No code generated", urgency: :critical)
exit 1
end
private def copy(code)
input = IO::Memory.new(code)
Process.run(WL_COPY_COMMAND, %w[-n], input: input)
notify_send("Copied to clipboard")
end
end
OTP.new.run

View File

@@ -0,0 +1,22 @@
{
pkgs,
lib,
pkgs-unstable,
}:
pkgs.runCommand "otp"
{
code = ./otp.cr;
env = {
YKMAN = lib.getExe pkgs-unstable.yubikey-manager;
SKIM = lib.getExe pkgs.skim;
WALKER = lib.getExe pkgs-unstable.walker;
WL_COPY = lib.getExe' pkgs.wl-clipboard "wl-copy";
NOTIFY_SEND = lib.getExe pkgs.libnotify;
NOTIFY_ICON = "${pkgs-unstable.yubioath-flutter}/share/pixmaps/com.yubico.yubioath.png";
};
nativeBuildInputs = [ pkgs.crystal ];
}
''
mkdir -p $out/bin
crystal build $code --release -o $out/bin/otp
''

5
custom-pkgs/util.nix Normal file
View File

@@ -0,0 +1,5 @@
dir: {
package = attrs: final: prev: {
"${dir}" = final.callPackage ./${dir}/package.nix (attrs // { pkgs = prev; });
};
}

View File

@@ -0,0 +1 @@
{ util, ... }: util.package { }

View File

@@ -0,0 +1,14 @@
{ pkgs }:
pkgs.writeShellScriptBin "wl-copy" ''
set -euo pipefail
copy="${pkgs.wl-clipboard}/bin/wl-copy"
paste="${pkgs.wl-clipboard}/bin/wl-paste"
"$copy" "$@"
args="$*"
if [[ "$args" =~ /(^| )(-p|--primary)($| )/ ]]; then
exit
fi
"$paste" -n | "$copy" -p "$@"
''

749
flake.lock generated
View File

@@ -1,15 +1,147 @@
{
"nodes": {
"agenix": {
"inputs": {
"darwin": "darwin",
"home-manager": "home-manager",
"nixpkgs": [
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1770165109,
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
"owner": "ryantm",
"repo": "agenix",
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
"type": "github"
},
"original": {
"owner": "ryantm",
"repo": "agenix",
"type": "github"
}
},
"colmena": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nixpkgs"
],
"stable": "stable"
},
"locked": {
"lastModified": 1762034856,
"narHash": "sha256-QVey3iP3UEoiFVXgypyjTvCrsIlA4ecx6Acaz5C8/PQ=",
"owner": "zhaofengli",
"repo": "colmena",
"rev": "349b035a5027f23d88eeb3bc41085d7ee29f18ed",
"type": "github"
},
"original": {
"owner": "zhaofengli",
"repo": "colmena",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1771796463,
"narHash": "sha256-9bCDuUzpwJXcHMQYMS1yNuzYMmKO/CCwCexpjWOl62I=",
"owner": "ipetkov",
"repo": "crane",
"rev": "3d3de3313e263e04894f284ac18177bd26169bad",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"darwin": {
"inputs": {
"nixpkgs": [
"agenix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1744478979,
"narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "43975d782b418ebf4969e9ccba82466728c2851b",
"type": "github"
},
"original": {
"owner": "lnl7",
"ref": "master",
"repo": "nix-darwin",
"type": "github"
}
},
"dnote": {
"flake": false,
"locked": {
"lastModified": 1772691105,
"narHash": "sha256-RC18Gi3/dagBitZIRIuPwIokk6pwwv+ZpawLTXSJ18c=",
"owner": "dnote",
"repo": "dnote",
"rev": "f34a96abbe47e8b516ea7cac2bdec06c64c01493",
"type": "github"
},
"original": {
"owner": "dnote",
"repo": "dnote",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1719877454,
"narHash": "sha256-g5N1yyOSsPNiOlFfkuI/wcUjmtah+nxdImJqrSATjOU=",
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "4e3583423212f9303aa1a6337f8dffb415920e4f",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
@@ -36,9 +168,42 @@
"type": "github"
}
},
"flake-utils": {
"flake-parts_3": {
"inputs": {
"systems": "systems"
"nixpkgs-lib": "nixpkgs-lib_3"
},
"locked": {
"lastModified": 1738453229,
"narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
@@ -54,9 +219,9 @@
"type": "github"
}
},
"flake-utils_2": {
"flake-utils_3": {
"inputs": {
"systems": "systems_2"
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
@@ -72,9 +237,63 @@
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_4"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_5": {
"inputs": {
"systems": "systems_5"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"inputs": {
"systems": "systems_6"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"frontend": {
"inputs": {
"flake-utils": "flake-utils",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs",
"pnpm2nix": "pnpm2nix"
},
@@ -92,6 +311,28 @@
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"lanzaboote",
"pre-commit",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gtrackmap": {
"inputs": {
"flake-parts": "flake-parts_2",
@@ -101,11 +342,11 @@
]
},
"locked": {
"lastModified": 1719992292,
"narHash": "sha256-bMKzt/rKkBsI8NbtsmfGG0tHsn/UilZqgOYwHRk45KQ=",
"lastModified": 1739016132,
"narHash": "sha256-7utkc6OSf4K2PFktxPPPZdBMnl0LoACEOeifOoAkTjM=",
"owner": "gtrackmap",
"repo": "gtrackmap",
"rev": "84df36e004cda9bd802e46ed0c814c7b08031807",
"rev": "1db7bb76a486f5ca4f8ce055a3bbee08d9f63775",
"type": "github"
},
"original": {
@@ -114,6 +355,164 @@
"type": "github"
}
},
"hastebin": {
"inputs": {
"flake-parts": "flake-parts_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1740944656,
"narHash": "sha256-SLFmhOqOly5Uz4MiIDLfDUQnwM0FDjthoZPvdFGvOSY=",
"owner": "~repomaa",
"repo": "hastebin",
"rev": "087b64789ebf11a9874c8777c0aee88675a9ceaf",
"type": "sourcehut"
},
"original": {
"owner": "~repomaa",
"repo": "hastebin",
"type": "sourcehut"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"agenix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1745494811,
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"home-manager_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772633058,
"narHash": "sha256-SO7JapRy2HPhgmqiLbfnW1kMx5rakPMKZ9z3wtRLQjI=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "080657a04188aca25f8a6c70a0fb2ea7e37f1865",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
},
"ketchup": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1718020591,
"narHash": "sha256-nNkMX4Fifj4Z1n2ldWAeu1AujaW41ugsEokPrKioNy0=",
"owner": "repomaa",
"repo": "ketchup",
"rev": "7cbff916ad4ebfd983402842569730e63d105de9",
"type": "github"
},
"original": {
"owner": "repomaa",
"repo": "ketchup",
"type": "github"
}
},
"ksoloti-pr": {
"locked": {
"lastModified": 1730719587,
"narHash": "sha256-aeQjG4hFCAqnen296tC8XuX2w2gbaorcosP7jde0cqQ=",
"owner": "repomaa",
"repo": "nixpkgs",
"rev": "ea615cc6ccb36c050290e09a5f90a91b78900a81",
"type": "github"
},
"original": {
"owner": "repomaa",
"ref": "pkg/ksoloti",
"repo": "nixpkgs",
"type": "github"
}
},
"lanzaboote": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs_3",
"pre-commit": "pre-commit",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1772216104,
"narHash": "sha256-1TnGN26vnCEQk5m4AavJZxGZTb/6aZyphemRPRwFUfs=",
"owner": "nix-community",
"repo": "lanzaboote",
"rev": "dbe5112de965bbbbff9f0729a9789c20a65ab047",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "lanzaboote",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"colmena",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixos-hardware": {
"locked": {
"lastModified": 1771969195,
"narHash": "sha256-qwcDBtrRvJbrrnv1lf/pREQi8t2hWZxVAyeMo7/E9sw=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "41c6b421bdc301b2624486e11905c9af7b8ec68e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"repo": "nixos-hardware",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1717196966,
@@ -132,14 +531,17 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1719876945,
"narHash": "sha256-Fm2rDDs86sHy0/1jxTOKB1118Q0O3Uc7EC0iXvXKpbI=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-lib_2": {
@@ -154,6 +556,34 @@
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
}
},
"nixpkgs-lib_3": {
"locked": {
"lastModified": 1738452942,
"narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1772624091,
"narHash": "sha256-QKyJ0QGWBn6r0invrMAK8dmJoBYWoOWy7lN+UHzW1jc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "80bdc1e5ce51f56b19791b52b2901187931f5353",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1702151865,
@@ -172,11 +602,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1719848872,
"narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=",
"lastModified": 1771848320,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
"type": "github"
},
"original": {
@@ -186,9 +616,25 @@
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1772598333,
"narHash": "sha256-YaHht/C35INEX3DeJQNWjNaTcPjYmBwwjFJ2jdtr+5U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fabb8c9deee281e50b1065002c9828f2cf7b2239",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"pnpm2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"flake-utils": "flake-utils_3",
"nixpkgs": "nixpkgs_2"
},
"locked": {
@@ -205,11 +651,103 @@
"type": "github"
}
},
"pre-commit": {
"inputs": {
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": [
"lanzaboote",
"nixpkgs"
]
},
"locked": {
"lastModified": 1771858127,
"narHash": "sha256-Gtre9YoYl3n25tJH2AoSdjuwcqij5CPxL3U3xysYD08=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "49bbbfc218bf3856dfa631cead3b052d78248b83",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"agenix": "agenix",
"colmena": "colmena",
"dnote": "dnote",
"flake-parts": "flake-parts",
"gtrackmap": "gtrackmap",
"nixpkgs": "nixpkgs_3"
"hastebin": "hastebin",
"home-manager": "home-manager_2",
"ketchup": "ketchup",
"ksoloti-pr": "ksoloti-pr",
"lanzaboote": "lanzaboote",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_4",
"nixpkgs-unstable": "nixpkgs-unstable",
"syntax-renderer": "syntax-renderer",
"tonearm": "tonearm",
"turny": "turny",
"voidauth": "voidauth",
"voxtype": "voxtype",
"workout-sync": "workout-sync"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"lanzaboote",
"nixpkgs"
]
},
"locked": {
"lastModified": 1771988922,
"narHash": "sha256-Fc6FHXtfEkLtuVJzd0B6tFYMhmcPLuxr90rWfb/2jtQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "f4443dc3f0b6c5e6b77d923156943ce816d1fcb9",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"stable": {
"locked": {
"lastModified": 1750133334,
"narHash": "sha256-urV51uWH7fVnhIvsZIELIYalMYsyr2FCalvlRTzqWRw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "36ab78dab7da2e4e27911007033713bab534187b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"syntax-renderer": {
"flake": false,
"locked": {
"lastModified": 1740962877,
"narHash": "sha256-mmGXYerUnxuTqHEWSueLgvkF4pjo1dfIQ+5QtM/JeJY=",
"owner": "~repomaa",
"repo": "syntax-renderer",
"rev": "2ddbeb7583302ae4b2775686c46826fc8a032e6a",
"type": "sourcehut"
},
"original": {
"owner": "~repomaa",
"repo": "syntax-renderer",
"type": "sourcehut"
}
},
"systems": {
@@ -241,6 +779,173 @@
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_4": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_5": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_6": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"tonearm": {
"inputs": {
"flake-utils": "flake-utils_5",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769118914,
"narHash": "sha256-ASM7MVDWxT+BBtcAJ1z8XNjyNdL5E1Kc0miCWIepf+o=",
"rev": "68ea2190d1b37f9030b7586d2030f9dc48915a85",
"type": "tarball",
"url": "https://codeberg.org/api/v1/repos/dergs/Tonearm/archive/68ea2190d1b37f9030b7586d2030f9dc48915a85.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz"
}
},
"turny": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1752874301,
"narHash": "sha256-A6IZz46Lfopm5UhMtFfBDimepEUt9lGwhWoEIEQHsgk=",
"owner": "~repomaa",
"repo": "turny",
"rev": "133c05151e77616c7973c1c1038506b2fdee8eab",
"type": "sourcehut"
},
"original": {
"owner": "~repomaa",
"repo": "turny",
"type": "sourcehut"
}
},
"voidauth": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1771599016,
"narHash": "sha256-dSOaOlixvFB57trGkqqS1MSCEbeBT13S3sfZK3Hz+bs=",
"owner": "repomaa",
"repo": "voidauth",
"rev": "9823e2de56e12f33df8e8eba79c4f9e891d31842",
"type": "github"
},
"original": {
"owner": "repomaa",
"ref": "feat/nix-packaging",
"repo": "voidauth",
"type": "github"
}
},
"voxtype": {
"inputs": {
"flake-utils": "flake-utils_6",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1772443545,
"narHash": "sha256-oD3lameQXilKcgxQORR2l0+iDbnCO61+mjYD3MEVbuQ=",
"owner": "peteonrails",
"repo": "voxtype",
"rev": "d011f3ff074a6a14c14e75fefb375a408e9e8887",
"type": "github"
},
"original": {
"owner": "peteonrails",
"repo": "voxtype",
"type": "github"
}
},
"workout-sync": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1750085642,
"narHash": "sha256-u+A06S7VEMBQskn6XhGCWYTiD6XBOPJ5ZzohDaRskbw=",
"owner": "~repomaa",
"repo": "workout-sync",
"rev": "6d8941e1e798eb326169f30c185289b8ed6b0702",
"type": "sourcehut"
},
"original": {
"owner": "~repomaa",
"repo": "workout-sync",
"type": "sourcehut"
}
}
},
"root": "root",

224
flake.nix
View File

@@ -1,27 +1,217 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
gtrackmap = {
url = "github:gtrackmap/gtrackmap";
inputs.nixpkgs.follows = "nixpkgs";
};
lanzaboote.url = "github:nix-community/lanzaboote";
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs";
};
ketchup = {
url = "github:repomaa/ketchup";
inputs.nixpkgs.follows = "nixpkgs";
};
workout-sync = {
url = "sourcehut:~repomaa/workout-sync";
inputs.nixpkgs.follows = "nixpkgs";
};
hastebin = {
url = "sourcehut:~repomaa/hastebin";
inputs.nixpkgs.follows = "nixpkgs";
};
ksoloti-pr.url = "github:repomaa/nixpkgs/pkg/ksoloti";
agenix = {
url = "github:ryantm/agenix";
inputs.nixpkgs.follows = "nixpkgs";
};
dnote = {
url = "github:dnote/dnote";
flake = false;
};
colmena = {
url = "github:zhaofengli/colmena";
inputs.nixpkgs.follows = "nixpkgs";
};
syntax-renderer = {
url = "sourcehut:~repomaa/syntax-renderer";
flake = false;
};
turny = {
url = "sourcehut:~repomaa/turny";
inputs.flake-parts.follows = "flake-parts";
inputs.nixpkgs.follows = "nixpkgs";
};
tonearm = {
url = "https://codeberg.org/dergs/Tonearm/archive/v1.0.0.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
voidauth = {
url = "github:repomaa/voidauth/feat/nix-packaging";
inputs.nixpkgs.follows = "nixpkgs-unstable";
inputs.flake-parts.follows = "flake-parts";
};
voxtype = {
url = "github:peteonrails/voxtype";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
};
outputs = { flake-parts, nixpkgs, gtrackmap, ... }@inputs: (
flake-parts.lib.mkFlake { inherit inputs; } {
flake.nixosConfigurations = {
freun-dev = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = inputs;
modules = [
./hosts/freun-dev
gtrackmap.nixosModules.default
];
outputs =
{
flake-parts,
agenix,
nixpkgs,
self,
colmena,
...
}@inputs:
flake-parts.lib.mkFlake { inherit inputs; } (
{ withSystem, ... }:
let
ssh.publicKeys = {
yubikey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLIUkESu5NnBi1M0+ZjYrkp6/rIFuwc3aguspf98jmOydNce6l65cnS3GRzc9oWx4lu11ahi87ZuE+pYV+gaHm4=";
builder = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuQaA6JKCOfsfUBI5tzoiYe3tdpLdPfVzeyByx/149C";
};
};
systems = [
"x86_64-linux"
];
}
);
specialArgs = { inherit inputs ssh self; };
in
{
systems = [
"x86_64-linux"
"x86_64-darwin"
"aarch64-linux"
"aarch64-darwin"
];
perSystem =
{ pkgs, system, ... }:
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
agenix.packages.${system}.default
colmena.packages.${system}.colmena
colmena.packages.${system}.manual
(writeShellScriptBin "build" ''
colmena build --keep-result "$@"
'')
(writeShellScriptBin "apply" ''
colmena apply --keep-result "$@"
'')
(writeShellScriptBin "apply-local" ''
nh os switch .
'')
];
};
};
flake = {
nixosConfigurations =
let
mkConfiguration =
name:
nixpkgs.lib.nixosSystem {
inherit specialArgs;
modules = [
./modules
./hosts/${name}
];
};
in
{
radish = mkConfiguration "radish";
freun-dev = mkConfiguration "freun-dev";
apu = mkConfiguration "apu";
turny = mkConfiguration "turny";
};
images.turny = self.nixosConfigurations.turny.config.system.build.sdImage;
dockerImages =
nixpkgs.lib.genAttrs
[
"x86_64-linux"
"aarch64-linux"
]
(
system:
withSystem system (
{ pkgs, ... }:
{
node =
let
setupDirs = pkgs.runCommand "setup-dirs" { } ''
mkdir -p $out/tmp $out/root $out/var/tmp
chmod 1777 $out/tmp $out/var/tmp
'';
in
pkgs.dockerTools.buildLayeredImage {
name = "node";
tag = "latest";
contents = with pkgs; [
nodejs
nix
busybox
bash
skopeo
cacert
git
setupDirs
(writeTextFile {
name = "etc-nix-nix-conf";
destination = "/etc/nix/nix.conf";
text = ''
build-users-group =
experimental-features = nix-command flakes
'';
})
];
config = {
Env = [
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
"NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-bundle.crt"
"HOME=/root"
];
};
};
}
)
);
colmenaHive = colmena.lib.makeHive self.outputs.colmena;
colmena =
let
deploymentOptions = {
freun-dev = {
targetHost = "freun.dev";
buildOnTarget = true;
};
radish = {
allowLocalDeployment = true;
targetHost = null;
};
turny = {
targetHost = "10.10.1.233";
};
};
in
{
meta = {
inherit specialArgs;
nixpkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ ];
};
};
}
// builtins.mapAttrs (name: config: {
imports = config._module.args.modules;
deployment = if builtins.hasAttr name deploymentOptions then deploymentOptions.${name} else { };
}) self.nixosConfigurations;
};
}
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

970
home/common/default.nix Normal file
View File

@@ -0,0 +1,970 @@
{
config,
osConfig,
lib,
pkgs,
pkgs-unstable,
inputs,
self,
...
}:
{
imports = [
../gnome
./dnote.nix
../modules/zed
../modules/voxtype
../modules/elephant
./secrets.nix
inputs.hastebin.nixosModules.hm
inputs.agenix.homeManagerModules.default
inputs.voxtype.homeManagerModules.default
];
# This value determines the Home Manager release that your configuration is
# compatible with. This helps avoid breakage when a new Home Manager release
# introduces backwards incompatible changes.
#
# You should not change this value, even if you update Home Manager. If you do
# want to update the value, then make sure to first check the Home Manager
# release notes.
home.stateVersion = "23.11"; # Please read the comment before changing.
home.packages =
with pkgs;
[
htop
gnupg
pkgs-unstable.yubioath-flutter
pkgs-unstable.yubikey-manager
gcc
sqlite
noto-fonts
noto-fonts-cjk-sans
noto-fonts-color-emoji
source-sans-pro
mosh
docker-compose
(signal-desktop.override {
commandLineArgs = "--password-store=gnome-libsecret";
})
cargo
blanket
wl-clipboard
gnumeric
gh
neovim-remote
pkgs-unstable.gradia
crystal
shards
moreutils
keymapp
gnumake
tig
jq
yt-dlp
ffmpeg
otp
manix
(writeShellScriptBin "fd" ''
${fd}/bin/fd -H "$@"
'')
pkgs-unstable.rpi-imager
picocom
imagemagick
ghostscript
inkscape
jless
scribus
dnscontrol
protonmail-bridge
hydroxide
imapsync
nixfmt-rfc-style
tree
virt-manager
pkgs-unstable.ncspot
pkgs-unstable.devbox
pkgs-unstable.feishin
openscad
pkgs-unstable.shairport-sync-airplay2
(writeShellScriptBin "pw" ''
${lib.getExe rbw} ls --fields 'id,folder,name' | \
${lib.getExe gawk} -F '\t' '{print $1 "\t" ($2 == "" ? "" : $2 "/") $3}' | \
${lib.getExe config.services.walker.package} -d -l 2 | \
xargs ${lib.getExe rbw} get "$@" | ${lib.getExe' wl-clipboard "wl-copy"}
'')
(google-fonts.override { fonts = [ "Tajawal" ]; })
pkgs-unstable.opencode
(writeShellScriptBin "nr" ''
${lib.getExe nix} run "nixpkgs#''${1}" "$@"
'')
pkgs-unstable.tidal-hifi
inputs.tonearm.packages.${pkgs.stdenv.hostPlatform.system}.tonearm
blueman
pavucontrol
(writeShellScriptBin "voxtoggle" ''
status=$(${lib.getExe config.programs.voxtype.package} status)
pid=$(cat ''${XDG_RUNTIME_DIR}/voxtype/pid)
if [[ "$status" == "stopped" ]]; then
exit 1
elif [[ "$status" == "recording" ]]; then
kill -SIGUSR2 "$pid"
else
kill -SIGUSR1 "$pid"
fi
'')
]
++ lib.optional osConfig.programs.niri.enable (
pkgs.writeShellScriptBin "handle-lid-close" ''
alias niri=${lib.getExe osConfig.programs.niri.package}
output_count=$(niri outputs | ${lib.getExe jq} -r 'length')
if [ "$output_count" -eq 1 ]; then
niri msg action spawn hyprlock
systemctl suspend
fi
''
);
programs = {
ashell = {
enable = true;
package = pkgs-unstable.ashell;
systemd = {
enable = true;
target = "graphical-session.target";
};
settings = {
modules = {
left = [ "Workspaces" ];
center = [ "WindowTitle" ];
right = [
"CustomNotifications"
"SystemInfo"
[
"Clock"
"Privacy"
"Settings"
]
];
};
settings = {
lock_cmd = "hyprlock &";
logout_cmd = "niri msg action quit";
audio_sinks_more_cmd = "pavucontrol -t 3";
audio_sources_more_cmd = "pavucontrol -t 4";
bluetooth_more_cmd = "blueman-manager";
CustomButton =
let
isDark = lib.getExe (
pkgs.writeShellScriptBin "is-dark" ''
gsettings get org.gnome.desktop.interface color-scheme | grep -q dark
''
);
toggleDark = lib.getExe (
pkgs.writeShellScriptBin "toggle-dark" ''
if ${isDark}; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
else
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
fi
''
);
in
[
{
name = "Dark Mode";
icon = " ";
command = toggleDark;
status_command = isDark;
}
];
};
CustomModule = [
{
name = "CustomNotifications";
type = "Button";
icon = " ";
command = "swaync-client -t -sw";
listen_cmd = "swaync-client -swb";
icons."dnd.*" = " ";
alert = ".*notification";
}
];
};
};
nh = {
enable = true;
flake = self;
};
dnote.enable = true;
home-manager.enable = true;
bat = {
enable = true;
extraPackages = with pkgs.bat-extras; [
batdiff
batwatch
];
config = {
pager = "less -FR";
};
};
direnv = {
enable = true;
nix-direnv.enable = true;
enableZshIntegration = true;
};
mpv = {
enable = true;
config = {
hwdec = "auto-safe";
vo = "gpu";
profile = "gpu-hq";
gpu-context = "wayland";
};
};
kitty = {
enable = true;
font = {
name = "IosevkaTerm NFM";
size = 14;
};
shellIntegration.enableZshIntegration = true;
themeFile = "Tomorrow_Night";
settings = {
hide_window_decorations = true;
enabled_layouts = "splits,stack";
};
keybindings = {
"kitty_mod+q" = "";
"kitty_mod+w" = "";
"kitty_mod+t" = "new_tab_with_cwd";
"kitty_mod+tab" = "next_tab";
"kitty_mod+enter" = "launch --location=hsplit --cwd=current";
"kitty_mod+space" = "launch --location=vsplit --cwd=current";
"kitty_mod+h" = "neighboring_window left";
"kitty_mod+l" = "neighboring_window right";
"kitty_mod+k" = "neighboring_window up";
"kitty_mod+j" = "neighboring_window down";
"kitty_mod+1" = "goto_tab 1";
"kitty_mod+2" = "goto_tab 2";
"kitty_mod+3" = "goto_tab 3";
"kitty_mod+4" = "goto_tab 4";
"kitty_mod+5" = "goto_tab 5";
"kitty_mod+6" = "goto_tab 6";
"kitty_mod+7" = "goto_tab 7";
"kitty_mod+8" = "goto_tab 8";
"kitty_mod+9" = "goto_tab 9";
"kitty_mod+0" = "goto_tab 10";
"kitty_mod+f" = "toggle_layout stack";
"kitty_mod+/" = "show_scrollback";
"ctrl+alt+h" = "move_window left";
"ctrl+alt+l" = "move_window right";
"ctrl+alt+k" = "move_window up";
"ctrl+alt+j" = "move_window down";
"ctrl+alt+enter" = "layout_action rotate";
};
};
vivaldi.enable = true;
git = {
enable = true;
settings = {
alias = {
st = "status";
co = "checkout";
b = "branch";
};
pull = {
rebase = "true";
};
push = {
autoSetupRemote = true;
};
init = {
defaultBranch = "main";
};
user = {
name = "Joakim Repomaa";
signingKey = "F0AF1CE34733B22317A8937D05557F53CD3C6458";
};
commit.gpgSign = true;
};
};
gpg = {
enable = true;
};
zsh = {
enable = true;
enableVteIntegration = true;
cdpath = [
"${config.xdg.configHome}/home-manager"
];
defaultKeymap = "viins";
dirHashes = {
hm = "${config.xdg.configHome}/home-manager";
};
history = {
path = "${config.home.homeDirectory}/.cache/zsh/history";
save = 100000;
size = 100000;
};
initContent = ''
. "${config.xdg.configHome}/zsh/init"
. "${config.age.secrets.context7.path}"
'';
};
lazygit =
let
getBaseBranch = pkgs.writeShellScriptBin "git-base-branch" ''
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$current_branch" =~ -review$ ]]; then
echo "''${current_branch%-review}"
else
git config get init.defaultBranch
fi
'';
escapeGoTemplate = builtins.replaceStrings [ "{{" "}}" ] [ ''{{"{{"}}'' ''{{"}}"}}'' ];
in
{
enable = true;
settings = {
gui = {
nerdFontsVersion = "3";
};
git = {
showUntrackedFiles = "all";
branchLogCmd = "git log {{branchName}} --first-parent --color=always --pretty=format:'%Cgreen%h%Creset %Cblue%aN%Creset %C(cyan)%<(14)%ar%Creset %s' --abbrev-commit";
overrideGpg = true;
};
os = {
edit = "\${EDITOR} -- {{filename}}";
editAtLine = "\${EDITOR} -- {{filename}}:{{line}}";
editAtLineAndWait = "\${EDITOR} --wait -- {{filename}}:{{line}}";
openDirInEditor = "\${EDITOR} -- {{dir}}";
};
promptToReturnFromSubprocess = false;
keybinding = {
universal = {
nextItem = "<disabled>";
prevItem = "<disabled>";
};
commits = {
moveDownCommit = "<down>";
moveUpCommit = "<up>";
};
};
customCommands = [
{
key = "<c-r>";
context = "global";
command = ''
git push --force-with-lease --set-upstream origin "{{.SelectedLocalBranch.Name}}" &&
gh pr create --title "{{.SelectedLocalBranch.Name}}" -B "$(${lib.getExe getBaseBranch})" --fill ||
(git log --reverse --no-merges --pretty='- %s' master..HEAD | gh pr edit --body-file -);
gh pr view --web
'';
}
{
key = "N";
context = "localBranches";
prompts = [
{
type = "input";
title = "New Branch Name:";
key = "BranchName";
initialValue = "{{.SelectedLocalBranch.Name}}-review";
}
];
command = ''
git checkout -b {{.Form.BranchName | quote}}
'';
}
{
key = "G";
context = "localBranches";
prompts = [
{
type = "menuFromCommand";
title = "PR:";
key = "pr";
command = escapeGoTemplate "gh pr list --search '-author:@me' --json number,title,author,updatedAt -t '{{range .}}{{tablerow .number .title .author.login}}{{end}}'";
filter = "(?<pr_number>[0-9]+)(?<rest>.*)";
labelFormat = "{{ .rest }}";
valueFormat = "{{ .pr_number }}";
}
];
command = ''
gh pr checkout {{.Form.pr | quote}}
'';
}
];
};
};
obs-studio.enable = true;
ssh = {
enable = true;
enableDefaultConfig = false;
matchBlocks = {
"*" = {
controlMaster = "auto";
controlPath = "${config.home.homeDirectory}/.ssh/masters-control/%r@%h:%p";
controlPersist = "1h";
forwardAgent = false;
addKeysToAgent = "no";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
hashKnownHosts = false;
userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts";
setEnv = {
TERM = "screen-256color";
};
};
"apu" = {
user = "root";
};
};
};
spotify-player = {
enable = true;
settings = {
enable_streaming = "Always";
theme = "tomorrow_night";
copy_command = "wl-copy";
};
themes = [
{
name = "tomorrow_night";
palette = {
background = "#1d1f21";
foreground = "#c5c8c6";
black = "#000000";
red = "#cc6666";
green = "#b5bd68";
yellow = "#f0c674";
blue = "#81a2be";
magenta = "#b294bb";
cyan = "#8abeb7";
white = "#ffffff";
bright_black = "#000000";
bright_red = "#cc6666";
bright_green = "#b5bd68";
bright_yellow = "#f0c674";
bright_blue = "#81a2be";
bright_magenta = "#b294bb";
bright_cyan = "#8abeb7";
bright_white = "#ffffff";
};
component_style = {
playback_metadata = {
fg = "#8abeb7";
};
playback_progress_bar = {
bg = "#1d1f21";
fg = "#c5c8c6";
};
};
}
];
};
neovide = {
enable = true;
settings = {
font = {
normal = [ "IosevkaTerm NFM" ];
size = 12.0;
};
fork = true;
};
};
hastebin = {
enable = true;
settings = {
host = "https://bin.freun.dev";
auth_token_file = "${config.xdg.configHome}/hastebin/auth_token";
clipboard_command = "${pkgs.wl-copy-both}/bin/wl-copy";
default_renderers =
let
code = [
"js"
"ts"
"jsx"
"tsx"
"rs"
"nix"
"cr"
"ecr"
"rb"
"erb"
"yaml"
"yml"
"json"
"toml"
"html"
"xml"
"css"
"scss"
"sass"
"less"
"sql"
"sh"
];
in
lib.listToAttrs (
lib.map (name: {
inherit name;
value = "hl";
}) code
);
};
};
zed-editor = {
enable = true;
defaultEditor = true;
};
hyprlock = {
enable = true;
package = pkgs-unstable.hyprlock;
settings = {
general = {
hide_cursor = true;
ignore_empty_input = true;
};
background = {
monitor = "";
path = "screenshot";
blur_passes = 3;
};
input-field = {
size = "20%, 5%";
monitor = "";
dots_center = true;
fade_on_empty = false;
rounding = 15;
shadow_passes = 2;
outline_thickness = 2;
placeholder_text = "Password...";
fail_text = "$PAMFAIL";
dots_spacing = "0.3";
position = "0, -20";
halign = "center";
valign = "center";
};
};
};
voxtype = {
enable = true;
package = inputs.voxtype.packages.${pkgs.stdenv.hostPlatform.system}.vulkan;
model.name = "large-v3-turbo";
service.enable = true;
settings = {
hotkey.enabled = false;
whisper.language = "auto";
output.notification = {
on_recording_start = false;
on_recording_stop = false;
on_transcription = false;
};
};
postProcessing = {
enable = true;
settings = {
model = "qwen3:4b-instruct";
commonInstructions = "no quotes, no emojis, no explanations";
prompts = [
{
title = "Clean up";
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
}
{
title = "Make a title";
instructions = "Make a concise and descriptive title for this dictation. Output ONLY the title";
}
{
title = "Summarize";
instructions = "Summarize this dictation in a few sentences. Output ONLY the summary";
}
{
title = "Commit message";
instructions = "Write a concise and descriptive git commit message for this dictation. Output ONLY the commit message";
}
{
title = "Translate to English";
instructions = "Translate this dictation to English. Remove filler words, fix grammar and punctuation. Output ONLY the translation";
}
];
};
};
};
};
gnome = {
keyboard.sources = [ "us+altgr-intl" ];
keybindings = {
builtin = {
screenshot-window = [ ];
show-screenshot-ui = [ ];
};
custom = [
{
binding = "Print";
command = "gradia --screenshot";
name = "Gradia";
}
{
binding = "<Alt>Print";
command = "kooha";
name = "Kooha";
}
];
};
power = {
powerButton = "hibernate";
};
extensions = {
paperwm = {
enable = true;
winprops = [
{
wm_class = "vivaldi-stable";
preferredWidth = "67%";
}
{
wm_class = "yubioath-flutter";
scratch_layer = true;
}
{
wm_class = "signal";
scratch_layer = true;
}
{
wm_class = "Slack";
scratch_layer = true;
}
{
wm_class = "Supersonic";
scratch_layer = true;
}
{
wm_class = "io.github.seadve.Kooha";
scratch_layer = true;
}
{
wm_class = "/.*/";
preferredWidth = "33";
}
];
keybindings = {
move-down = [
"<Control><Super>Down"
"<Shift><Super>j"
];
move-left = [
"<Control><Super>Left"
"<Shift><Super>h"
];
move-right = [
"<Control><Super>Right"
"<Shift><Super>l"
];
move-up = [
"<Control><Super>Up"
"<Shift><Super>k"
];
switch-down = [
"<Super>Down"
"<Super>j"
];
switch-left = [
"<Super>Left"
"<Super>h"
];
switch-right = [
"<Super>Right"
"<Super>l"
];
switch-up = [
"<Super>Up"
"<Super>k"
];
};
};
freon = {
enable = true;
};
};
nightLight.enable = true;
};
fonts.fontconfig = {
enable = true;
defaultFonts = {
emoji = [ "Noto Color Emoji" ];
monospace = [ "IosevkaTerm NFM" ];
sansSerif = [ "Source Sans Pro" ];
};
};
targets.genericLinux.enable = true;
xdg.mime.enable = true;
xdg = {
enable = true;
configFile = {
tmux.source = dotfiles/tmux;
zsh.source = dotfiles/zsh;
"fd/ignore".text = ''
/.git/
'';
"vivaldi/NativeMessagingHosts/ff2mpv.json".text = builtins.toJSON {
name = "ff2mpv";
description = "ff2mpv's external manifest";
path = "${pkgs.ff2mpv-rust}/bin/ff2mpv-rust";
type = "stdio";
allowed_origins = [
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/"
];
};
};
};
# Home Manager can also manage your environment variables through
# 'home.sessionVariables'. If you don't want to manage your shell through Home
# Manager then you have to manually source 'hm-session-vars.sh' located at
# either
#
# ~/.nix-profile/etc/profile.d/hm-session-vars.sh
#
# or
#
# ~/.local/state/nix/profiles/profile/etc/profile.d/hm-session-vars.sh
#
# or
#
# /etc/profiles/per-user/jokke/etc/profile.d/hm-session-vars.sh
#
home.sessionVariables = {
NIXOS_OZONE_WL = 1;
NVIM_LISTEN_ADDRESS = "$XDG_RUNTIME_DIR/nvimsocket";
PAGER = "bat --paging=always --style=plain";
MANPAGER = "sh -c 'col -bx | bat -l man -p'";
MANROFFOPT = "-c";
DO_NOT_TRACK = 1;
};
systemd.user.sessionVariables = lib.mapAttrs (_: v: toString v) config.home.sessionVariables;
programs.zsh.sessionVariables = config.home.sessionVariables;
home.shellAliases = {
_ = "sudo";
icr = "crystal i";
hm = "home-manager";
chat = "ssh root@ipv4.jokke.space pkill mosh; mosh weechat@ipv4.jokke.space";
wget = " wget --content-disposition";
pj = "jq | bat -l json";
ls = "ls --color=auto";
};
services = {
swaync = {
enable = true;
package = pkgs-unstable.swaynotificationcenter;
settings = {
scripts = {
focus-window =
let
jq = lib.getExe pkgs.jq;
niri = lib.getExe osConfig.programs.niri.package;
script = pkgs.writeShellScriptBin "swaync-focus-window" ''
set -e
APP_NAME="''${SWAYNC_APP_NAME:-}"
DESKTOP_ENTRY="''${SWAYNC_DESKTOP_ENTRY:-}"
APP_ID=""
if [[ -n "$DESKTOP_ENTRY" ]]; then
APP_ID="$DESKTOP_ENTRY"
elif [[ -n "$APP_NAME" ]]; then
APP_ID=$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/ //g')
fi
[[ -z "$APP_ID" ]] && exit
# Find window ID for this app in niri and focus it
${jq} -r --arg app_id "$APP_ID" '.[] | select(.app_id | ascii_downcase | contains($app_id)) | .id' \
<(${niri} msg --json windows 2>/dev/null) | head -n1 | while read -r WINDOW_ID; do
if [[ -n "$WINDOW_ID" && "$WINDOW_ID" != "null" ]]; then
${niri} msg action focus-window --id "$WINDOW_ID"
fi
done
'';
in
{
exec = lib.getExe script;
run-on = "action";
};
};
};
};
gpg-agent = with pkgs; {
enable = true;
enableSshSupport = true;
pinentry.package = pinentry-gnome3;
};
};
services.walker = {
enable = true;
package = pkgs.symlinkJoin {
inherit (pkgs-unstable.walker) name meta;
paths = [ pkgs-unstable.walker ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/walker \
--prefix PATH : ${lib.makeBinPath [ config.services.elephant.package ]}
'';
};
systemd = {
enable = true;
};
settings = {
providers = {
default = [
"desktopapplications"
"calc"
"websearch"
"bitwarden"
];
};
};
};
services.elephant.enable = true;
systemd.user.services.walker.Install.WantedBy = lib.mkForce [ "graphical-session.target" ];
systemd.user.services.shairport-sync = {
Unit = {
Description = "AirPlay audio server";
After = [
"pipewire-pulse.service"
"network.target"
];
};
Service = {
ExecStart = "${lib.getExe pkgs-unstable.shairport-sync-airplay2} -- pw";
Restart = "on-failure";
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
programs.gpg.scdaemonSettings = {
disable-ccid = true;
};
programs.rbw.enable = true;
programs.rclone.enable = true;
programs.firefox.enable = true;
xdg.configFile."opencode/opencode.jsonc".text = builtins.toJSON {
"$schema" = "https://opencode.ai/config.json";
model = "opencode-go/glm-5";
small_model = "opencode-go/kimi-k2.5";
agent = {
explore.model = "opencode-go/kimi-k2.5";
};
theme = "system";
lsp = {
ruby-lsp = {
initialization = {
enabledFeatures = {
codeActions = true;
codeLens = true;
completion = true;
definition = true;
diagnostics = true;
documentHighlights = true;
documentLink = true;
documentSymbols = true;
foldingRanges = true;
formatting = false;
hover = true;
inlayHint = true;
onTypeFormatting = true;
selectionRanges = true;
semanticHighlighting = true;
signatureHelp = true;
typeHierarchy = true;
workspaceSymbol = true;
};
linters = [ "standard" ];
};
extensions = [
".rb"
".erb"
".haml"
];
command = [
"bundle"
"exec"
"ruby-lsp"
];
};
rubocop = {
command = [
"bundle"
"exec"
"rubocop"
"--lsp"
];
extensions = [
".rb"
".erb"
".haml"
];
};
nil = {
command = [
(lib.getExe pkgs.nil)
];
extensions = [ ".nix" ];
};
ameba-ls = {
command = [
(lib.getExe pkgs-unstable.ameba-ls)
];
extensions = [ ".cr" ];
};
};
mcp = {
context7 = {
type = "remote";
url = "https://mcp.context7.com/mcp";
headers = {
CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}";
};
enabled = true;
};
};
};
xdg.configFile."niri/config.kdl".source = ./dotfiles/niri.kdl;
gnome.automaticTimeZone = true;
gtk.enable = true;
home.pointerCursor = {
enable = true;
name = "Adwaita";
package = pkgs.adwaita-icon-theme;
gtk.enable = true;
};
}

44
home/common/dnote.nix Normal file
View File

@@ -0,0 +1,44 @@
{
inputs,
lib,
pkgs,
pkgs-unstable,
config,
...
}:
let
completion = pkgs.stdenv.mkDerivation {
name = "dnote-completion";
phases = [
"unpackPhase"
"installPhase"
];
src = inputs.dnote;
installPhase = ''
mkdir -p $out/lib/dnote/zsh-completion/completions
cp pkg/cli/dnote-completion.zsh $out/lib/dnote/zsh-completion/completions/_dnote
'';
};
client = pkgs.stdenv.mkDerivation {
name = "dnote-client";
phases = [ "installPhase" ];
installPhase = ''
mkdir -p $out/bin
cp ${pkgs-unstable.dnote}/bin/dnote-cli $out/bin/dnote
'';
};
cfg = config.programs.dnote;
in
{
options.programs.dnote = {
enable = pkgs.lib.mkEnableOption "Enable dnote";
};
config = lib.mkIf cfg.enable {
home.packages = [ client ];
home.shellAliases.dn = "dnote";
programs.zsh.initContent = ''
fpath=(${completion}/lib/dnote/zsh-completion/completions $fpath)
'';
};
}

View File

@@ -0,0 +1,641 @@
// This config is in the KDL format: https://kdl.dev
// "/-" comments out the following node.
// Check the wiki for a full description of the configuration:
// https://niri-wm.github.io/niri/Configuration:-Introduction
// Input device configuration.
// Find the full list of options on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Input
input {
keyboard {
xkb {
// You can set rules, model, layout, variant and options.
// For more information, see xkeyboard-config(7).
layout "us"
variant "altgr-intl"
// For example:
// layout "us,ru"
// options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
// If this section is empty, niri will fetch xkb settings
// from org.freedesktop.locale1. You can control these using
// localectl set-x11-keymap.
}
// Enable numlock on startup, omitting this setting disables it.
numlock
}
// Next sections include libinput settings.
// Omitting settings disables them, or leaves them at their default values.
// All commented-out settings here are examples, not defaults.
touchpad {
// off
tap
dwt
dwtp
// drag false
// drag-lock
natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "two-finger"
// disabled-on-external-mouse
}
mouse {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "no-scroll"
}
trackpoint {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "on-button-down"
// scroll-button 273
// scroll-button-lock
// middle-emulation
}
// Uncomment this to make the mouse warp to the center of newly focused windows.
// warp-mouse-to-focus
// Focus windows and outputs automatically when moving the mouse into them.
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
focus-follows-mouse max-scroll-amount="10%"
}
// You can configure outputs by their name, which you can find
// by running `niri msg outputs` while inside a niri instance.
// The built-in laptop monitor is usually called "eDP-1".
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Outputs
// Remember to uncomment the node by removing "/-"!
output "eDP-1" {
// Uncomment this line to disable this output.
// off
// Resolution and, optionally, refresh rate of the output.
// The format is "<width>x<height>" or "<width>x<height>@<refresh rate>".
// If the refresh rate is omitted, niri will pick the highest refresh rate
// for the resolution.
// If the mode is omitted altogether or is invalid, niri will pick one automatically.
// Run `niri msg outputs` while inside a niri instance to list all outputs and their modes.
// mode "1920x1080@120.030"
// You can use integer or fractional scale, for example use 1.5 for 150% scale.
scale 1.5
// Transform allows to rotate the output counter-clockwise, valid values are:
// normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270.
// transform "normal"
// Position of the output in the global coordinate space.
// This affects directional monitor actions like "focus-monitor-left", and cursor movement.
// The cursor can only move between directly adjacent outputs.
// Output scale and rotation has to be taken into account for positioning:
// outputs are sized in logical, or scaled, pixels.
// For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080,
// so to put another output directly adjacent to it on the right, set its x to 1920.
// If the position is unset or results in an overlap, the output is instead placed
// automatically.
// position x=1280 y=0
}
output "DP-5" {
scale 1
}
output "DP-3" {
scale 1.2
}
// Settings that influence how windows are positioned and sized.
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Layout
layout {
// Set gaps around windows in logical pixels.
gaps 5
// When to center a column when changing focus, options are:
// - "never", default behavior, focusing an off-screen column will keep at the left
// or right edge of the screen.
// - "always", the focused column will always be centered.
// - "on-overflow", focusing a column will center it if it doesn't fit
// together with the previously focused column.
center-focused-column "never"
// You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between.
preset-column-widths {
// Proportion sets the width as a fraction of the output width, taking gaps into account.
// For example, you can perfectly fit four windows sized "proportion 0.25" on an output.
// The default preset widths are 1/3, 1/2 and 2/3 of the output.
proportion 0.33333
proportion 0.5
proportion 0.66667
// Fixed sets the width in logical pixels exactly.
// fixed 1920
}
// You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between.
// preset-window-heights { }
// You can change the default width of the new windows.
default-column-width { proportion 0.5; }
// If you leave the brackets empty, the windows themselves will decide their initial width.
// default-column-width {}
// By default focus ring and border are rendered as a solid background rectangle
// behind windows. That is, they will show up through semitransparent windows.
// This is because windows using client-side decorations can have an arbitrary shape.
//
// If you don't like that, you should uncomment `prefer-no-csd` below.
// Niri will draw focus ring and border *around* windows that agree to omit their
// client-side decorations.
//
// Alternatively, you can override it with a window rule called
// `draw-border-with-background`.
// You can change how the focus ring looks.
focus-ring {
// Uncomment this line to disable the focus ring.
// off
// How many logical pixels the ring extends out from the windows.
width 4
// Colors can be set in a variety of ways:
// - CSS named colors: "red"
// - RGB hex: "#rgb", "#rgba", "#rrggbb", "#rrggbbaa"
// - CSS-like notation: "rgb(255, 127, 0)", rgba(), hsl() and a few others.
// Color of the ring on the active monitor.
active-color "#7fc8ff"
// Color of the ring on inactive monitors.
//
// The focus ring only draws around the active window, so the only place
// where you can see its inactive-color is on other monitors.
inactive-color "#505050"
// You can also use gradients. They take precedence over solid colors.
// Gradients are rendered the same as CSS linear-gradient(angle, from, to).
// The angle is the same as in linear-gradient, and is optional,
// defaulting to 180 (top-to-bottom gradient).
// You can use any CSS linear-gradient tool on the web to set these up.
// Changing the color space is also supported, check the wiki for more info.
//
// active-gradient from="#80c8ff" to="#c7ff7f" angle=45
// You can also color the gradient relative to the entire view
// of the workspace, rather than relative to just the window itself.
// To do that, set relative-to="workspace-view".
//
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view"
}
// You can also add a border. It's similar to the focus ring, but always visible.
border {
// The settings are the same as for the focus ring.
// If you enable the border, you probably want to disable the focus ring.
off
width 4
active-color "#ffc87f"
inactive-color "#505050"
// Color of the border around windows that request your attention.
urgent-color "#9b0000"
// Gradients can use a few different interpolation color spaces.
// For example, this is a pastel rainbow gradient via in="oklch longer hue".
//
// active-gradient from="#e5989b" to="#ffb4a2" angle=45 relative-to="workspace-view" in="oklch longer hue"
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view"
}
// You can enable drop shadows for windows.
shadow {
// Uncomment the next line to enable shadows.
// on
// By default, the shadow draws only around its window, and not behind it.
// Uncomment this setting to make the shadow draw behind its window.
//
// Note that niri has no way of knowing about the CSD window corner
// radius. It has to assume that windows have square corners, leading to
// shadow artifacts inside the CSD rounded corners. This setting fixes
// those artifacts.
//
// However, instead you may want to set prefer-no-csd and/or
// geometry-corner-radius. Then, niri will know the corner radius and
// draw the shadow correctly, without having to draw it behind the
// window. These will also remove client-side shadows if the window
// draws any.
//
// draw-behind-window true
// You can change how shadows look. The values below are in logical
// pixels and match the CSS box-shadow properties.
// Softness controls the shadow blur radius.
softness 30
// Spread expands the shadow.
spread 5
// Offset moves the shadow relative to the window.
offset x=0 y=5
// You can also change the shadow color and opacity.
color "#0007"
}
// Struts shrink the area occupied by windows, similarly to layer-shell panels.
// You can think of them as a kind of outer gaps. They are set in logical pixels.
// Left and right struts will cause the next window to the side to always be visible.
// Top and bottom struts will simply add outer gaps in addition to the area occupied by
// layer-shell panels and regular gaps.
struts {
// left 64
// right 64
// top 64
// bottom 64
}
}
// Add lines like this to spawn processes at startup.
// Note that running niri as a session supports xdg-desktop-autostart,
// which may be more convenient to use.
// See the binds section below for more spawn examples.
//spawn-at-startup "systemctl start --user niri-session.target"
// To run a shell command (with variables, pipes, etc.), use spawn-sh-at-startup:
// spawn-sh-at-startup "qs -c ~/source/qs/MyAwesomeShell"
hotkey-overlay {
// Uncomment this line to disable the "Important Hotkeys" pop-up at startup.
skip-at-startup
}
// Uncomment this line to ask the clients to omit their client-side decorations if possible.
// If the client will specifically ask for CSD, the request will be honored.
// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners.
// This option will also fix border/focus ring drawing behind some semitransparent windows.
// After enabling or disabling this, you need to restart the apps for this to take effect.
// prefer-no-csd
// You can change the path where screenshots are saved.
// A ~ at the front will be expanded to the home directory.
// The path is formatted with strftime(3) to give you the screenshot date and time.
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
// You can also set this to null to disable saving screenshots to disk.
// screenshot-path null
// Animation settings.
// The wiki explains how to configure individual animations:
// https://niri-wm.github.io/niri/Configuration:-Animations
animations {
// Uncomment to turn off all animations.
// off
// Slow down all animations by this factor. Values below 1 speed them up instead.
// slowdown 3.0
}
// Window rules let you adjust behavior for individual windows.
// Find more information on the wiki:
// https://niri-wm.github.io/niri/Configuration:-Window-Rules
// Work around WezTerm's initial configure bug
// by setting an empty default-column-width.
window-rule {
// This regular expression is intentionally made as specific as possible,
// since this is the default config, and we want no false positives.
// You can get away with just app-id="wezterm" if you want.
match app-id=r#"^org\.wezfurlong\.wezterm$"#
default-column-width {}
}
// Open the Firefox picture-in-picture player as floating by default.
window-rule {
// This app-id regular expression will work for both:
// - host Firefox (app-id is "firefox")
// - Flatpak Firefox (app-id is "org.mozilla.firefox")
match app-id=r#"firefox$"# title="^Picture-in-Picture$"
open-floating true
}
// Example: block out two password managers from screen capture.
// (This example rule is commented out with a "/-" in front.)
/-window-rule {
match app-id=r#"^org\.keepassxc\.KeePassXC$"#
match app-id=r#"^org\.gnome\.World\.Secrets$"#
block-out-from "screen-capture"
// Use this instead if you want them visible on third-party screenshot tools.
// block-out-from "screencast"
}
// Example: enable rounded corners for all windows.
// (This example rule is commented out with a "/-" in front.)
window-rule {
geometry-corner-radius 14
clip-to-geometry true
}
binds {
// Keys consist of modifiers separated by + signs, followed by an XKB key name
// in the end. To find an XKB name for a particular key, you may use a program
// like wev.
//
// "Mod" is a special modifier equal to Super when running on a TTY, and to Alt
// when running as a winit window.
//
// Most actions that you can bind here can also be invoked programmatically with
// `niri msg action do-something`.
// Mod-Shift-/, which is usually the same as Mod-?,
// shows a list of important hotkeys.
Mod+Shift+Slash { show-hotkey-overlay; }
// Suggested binds for running programs: terminal, app launcher, screen locker.
Mod+Return hotkey-overlay-title="Open a Terminal: kitty" { spawn "kitty"; }
Mod+Z hotkey-overlay-title="Open a launcher: walker" { spawn "walker"; }
Mod+Space { spawn "voxtoggle"; }
Mod+Alt+L hotkey-overlay-title="Lock the Screen: hyprlock" { spawn "hyprlock"; }
// Use spawn-sh to run a shell command. Do this if you need pipes, multiple commands, etc.
// Note: the entire command goes as a single argument. It's passed verbatim to `sh -c`.
// For example, this is a standard bind to toggle the screen reader (orca).
// Super+Alt+S allow-when-locked=true hotkey-overlay-title=null { spawn-sh "pkill orca || exec orca"; }
// Example volume keys mappings for PipeWire & WirePlumber.
// The allow-when-locked=true property makes them work even when the session is locked.
// Using spawn-sh allows to pass multiple arguments together with the command.
// "-l 1.0" limits the volume to 100%.
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ -l 1.0"; }
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1-"; }
XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }
XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; }
// Example media keys mapping using playerctl.
// This will work with any MPRIS-enabled media player.
XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; }
XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; }
XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; }
XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; }
// Example brightness key mappings for brightnessctl.
// You can use regular spawn with multiple arguments too (to avoid going through "sh"),
// but you need to manually put each argument in separate "" quotes.
XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "+10%"; }
XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "10%-"; }
// Open/close the Overview: a zoomed-out view of workspaces and windows.
// You can also move the mouse into the top-left hot corner,
// or do a four-finger swipe up on a touchpad.
Mod+O repeat=false { toggle-overview; }
Mod+Backspace repeat=false { close-window; }
Mod+Left { focus-column-left; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Right { focus-column-right; }
Mod+H { focus-column-left; }
Mod+J { focus-window-or-workspace-down; }
Mod+K { focus-window-or-workspace-up; }
Mod+L { focus-column-right; }
Mod+Shift+Left { move-column-left; }
Mod+Shift+Down { move-window-down; }
Mod+Shift+Up { move-window-up; }
Mod+Shift+Right { move-column-right; }
Mod+Shift+H { move-column-left; }
Mod+Shift+J { move-window-down-or-to-workspace-down; }
Mod+Shift+K { move-window-up-or-to-workspace-up; }
Mod+Shift+L { move-column-right; }
Mod+Home { focus-column-first; }
Mod+End { focus-column-last; }
Mod+Shift+Home { move-column-to-first; }
Mod+Shift+End { move-column-to-last; }
Mod+Ctrl+Left { focus-monitor-left; }
Mod+Ctrl+Down { focus-monitor-down; }
Mod+Ctrl+Up { focus-monitor-up; }
Mod+Ctrl+Right { focus-monitor-right; }
Mod+Ctrl+H { focus-monitor-left; }
Mod+Ctrl+J { focus-monitor-down; }
Mod+Ctrl+K { focus-monitor-up; }
Mod+Ctrl+L { focus-monitor-right; }
Mod+Shift+Ctrl+Left { move-column-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-column-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-column-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-column-to-monitor-right; }
Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
// Alternatively, there are commands to move just a single window:
// Mod+Shift+Ctrl+Left { move-window-to-monitor-left; }
// ...
// And you can also move a whole workspace to another monitor:
// Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; }
// ...
Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Ctrl+Page_Down { move-column-to-workspace-down; }
Mod+Ctrl+Page_Up { move-column-to-workspace-up; }
Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; }
// Alternatively, there are commands to move just a single window:
// Mod+Ctrl+Page_Down { move-window-to-workspace-down; }
// ...
Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; }
Mod+Shift+U { move-workspace-down; }
Mod+Shift+I { move-workspace-up; }
// You can bind mouse wheel scroll ticks using the following syntax.
// These binds will change direction based on the natural-scroll setting.
//
// To avoid scrolling through workspaces really fast, you can use
// the cooldown-ms property. The bind will be rate-limited to this value.
// You can set a cooldown on any bind, but it's most useful for the wheel.
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; }
Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; }
Mod+WheelScrollRight { focus-column-right; }
Mod+WheelScrollLeft { focus-column-left; }
Mod+Ctrl+WheelScrollRight { move-column-right; }
Mod+Ctrl+WheelScrollLeft { move-column-left; }
// Usually scrolling up and down with Shift in applications results in
// horizontal scrolling; these binds replicate that.
Mod+Shift+WheelScrollDown { focus-column-right; }
Mod+Shift+WheelScrollUp { focus-column-left; }
Mod+Ctrl+Shift+WheelScrollDown { move-column-right; }
Mod+Ctrl+Shift+WheelScrollUp { move-column-left; }
// Similarly, you can bind touchpad scroll "ticks".
// Touchpad scrolling is continuous, so for these binds it is split into
// discrete intervals.
// These binds are also affected by touchpad's natural-scroll, so these
// example binds are "inverted", since we have natural-scroll enabled for
// touchpads by default.
// Mod+TouchpadScrollDown { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+"; }
// Mod+TouchpadScrollUp { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02-"; }
// You can refer to workspaces by index. However, keep in mind that
// niri is a dynamic workspace system, so these commands are kind of
// "best effort". Trying to refer to a workspace index bigger than
// the current workspace count will instead refer to the bottommost
// (empty) workspace.
//
// For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on
// will all refer to the 3rd workspace.
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+5 { focus-workspace 5; }
Mod+6 { focus-workspace 6; }
Mod+7 { focus-workspace 7; }
Mod+8 { focus-workspace 8; }
Mod+9 { focus-workspace 9; }
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
Mod+Shift+4 { move-column-to-workspace 4; }
Mod+Shift+5 { move-column-to-workspace 5; }
Mod+Shift+6 { move-column-to-workspace 6; }
Mod+Shift+7 { move-column-to-workspace 7; }
Mod+Shift+8 { move-column-to-workspace 8; }
Mod+Shift+9 { move-column-to-workspace 9; }
// Alternatively, there are commands to move just a single window:
// Mod+Ctrl+1 { move-window-to-workspace 1; }
// Switches focus between the current and the previous workspace.
// Mod+Tab { focus-workspace-previous; }
// The following binds move the focused window in and out of a column.
// If the window is alone, they will consume it into the nearby column to the side.
// If the window is already in a column, they will expel it out.
Mod+BracketLeft { consume-or-expel-window-left; }
Mod+BracketRight { consume-or-expel-window-right; }
// Consume one window from the right to the bottom of the focused column.
Mod+Comma { consume-window-into-column; }
// Expel the bottom window from the focused column to the right.
Mod+Period { expel-window-from-column; }
Mod+R { switch-preset-column-width; }
// Cycling through the presets in reverse order is also possible.
// Mod+R { switch-preset-column-width-back; }
Mod+Ctrl+R { switch-preset-window-height; }
Mod+Shift+R { reset-window-height; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
// While maximize-column leaves gaps and borders around the window,
// maximize-window-to-edges doesn't: the window expands to the edges of the screen.
// This bind corresponds to normal window maximizing,
// e.g. by double-clicking on the titlebar.
Mod+M { maximize-window-to-edges; }
// Expand the focused column to space not taken up by other fully visible columns.
// Makes the column "fill the rest of the space".
Mod+Ctrl+F { expand-column-to-available-width; }
Mod+C { center-column; }
// Center all fully visible columns on screen.
Mod+Ctrl+C { center-visible-columns; }
// Finer width adjustments.
// This command can also:
// * set width in pixels: "1000"
// * adjust width in pixels: "-5" or "+5"
// * set width as a percentage of screen width: "25%"
// * adjust width as a percentage of screen width: "-10%" or "+10%"
// Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0,
// set-column-width "100" will make the column occupy 200 physical screen pixels.
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
// Finer height adjustments when in column with other windows.
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
// Move the focused window between the floating and the tiling layout.
Mod+V { toggle-window-floating; }
Mod+Shift+V { switch-focus-between-floating-and-tiling; }
// Toggle tabbed column display mode.
// Windows in this column will appear as vertical tabs,
// rather than stacked on top of each other.
Mod+W { toggle-column-tabbed-display; }
// Actions to switch layouts.
// Note: if you uncomment these, make sure you do NOT have
// a matching layout switch hotkey configured in xkb options above.
// Having both at once on the same hotkey will break the switching,
// since it will switch twice upon pressing the hotkey (once by xkb, once by niri).
// Mod+Space { switch-layout "next"; }
// Mod+Shift+Space { switch-layout "prev"; }
Print { screenshot; }
Ctrl+Print { screenshot-screen; }
Alt+Print { screenshot-window; }
// Applications such as remote-desktop clients and software KVM switches may
// request that niri stops processing the keyboard shortcuts defined here
// so they may, for example, forward the key presses as-is to a remote machine.
// It's a good idea to bind an escape hatch to toggle the inhibitor,
// so a buggy application can't hold your session hostage.
//
// The allow-inhibiting=false property can be applied to other binds as well,
// which ensures niri always processes them, even when an inhibitor is active.
Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
// The quit action will show a confirmation dialog to avoid accidental exits.
Mod+Shift+E { quit; }
Ctrl+Alt+Delete { quit; }
// Powers off the monitors. To turn them back on, do any input like
// moving the mouse or pressing any other key.
Mod+Shift+P { power-off-monitors; }
}
switch-events {
lid-close {
spawn "handle-lid-close";
}
}

View File

@@ -0,0 +1,69 @@
# vim: ft=tmux
# original from bl1nk
# Styles
setw -g mode-style "fg=yellow,bg=default,bright"
setw -g window-status-style "fg=default,bg=default,dim"
setw -g window-status-current-style "fg=yellow,bg=default,dim"
set -g message-style "fg=red,bg=default,bright"
set -g status-style "fg=default,bg=black,bright"
set -g pane-active-border-style "fg=blue,bg=black"
set -g allow-passthrough 1
set-option -g set-titles on
set-option -g focus-events on
# set set-titles-string "#{session_name}"
# Options
set -g bell-action any
set -g history-limit 50000
set -g status on
#set -g status-keys vi
#set -g status-utf8 on
set -g set-titles on
set -g default-terminal "tmux-256color"
set -as terminal-features ",foot*:RGB,alacritty*:RGB,gnome*:RGB"
set -g set-titles-string "tmux:#I [ #W ]"
set -g base-index 1
set -g pane-base-index 1
set -g @plugin 'base16-project/base16-tmux'
set -g update-environment "DISPLAY SSH_ASKPASS SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY BASE16_SHELL"
set -g status-left-length 30
set -g status-left '[#[fg=white]#S]#[fg=black,bright] #[default]'
set -sg escape-time 0
#setw -g utf8 on
setw -g automatic-rename on
setw -g clock-mode-style 24
#setw -g mode-keys vi
setw -g mouse on
bind-key q confirm kill-pane
bind-key Q confirm kill-session
bind-key Tab next-window
bind-key > switchc -n
bind-key > switchc -p
bind-key C-n new-window -c "#{pane_current_path}"
bind-key C-t new-window -c "#{pane_current_path}" "~/bin/share-tty $(tmux display -p '#S')"
bind-key u run tmux-url-select
bind-key o split-window -h -c '#{pane_current_path}'
bind-key u split-window -v -c '#{pane_current_path}'
bind-key C-o split-window -h -c '#{pane_current_path}'
bind-key C-u split-window -v -c '#{pane_current_path}'
bind-key -r C-h select-pane -L
bind-key -r C-j select-pane -D
bind-key -r C-k select-pane -U
bind-key -r C-l select-pane -R
bind-key f resize-pane -Z
bind-key r source-file ~/.config/tmux/tmux.conf
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "xclip -i"
bind -T root MouseDown2Pane select-pane -t =\; run -b "xclip -o | tmux load-buffer - && tmux paste-buffer -s ' '"
run '~/.tmux/plugins/tpm/tpm'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
#!/bin/zsh
# The following lines were added by compinstall
zstyle ':completion:*' completer _expand _complete _ignored _approximate
zstyle ':completion:*' completions 1
zstyle ':completion:*' expand prefix
zstyle ':completion:*' format '%BCompleting %d%b'
zstyle ':completion:*' glob 1
zstyle ':completion:*' group-name ''
zstyle ':completion:*' ignore-parents parent pwd
zstyle ':completion:*' insert-unambiguous true
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
zstyle ':completion:*' list-suffixes true
zstyle ':completion:*' matcher-list '' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' 'r:|[._-]=** r:|=**' 'l:|=* r:|=*'
zstyle ':completion:*' max-errors 1
zstyle ':completion:*' menu select=3
zstyle ':completion:*' original true
zstyle ':completion:*' preserve-prefix '//[^/]##/'
zstyle ':completion:*' prompt 'Did you mean...'
zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
zstyle ':completion:*' squeeze-slashes true
zstyle ':completion:*' substitute 1
zstyle ':completion:*' verbose true
zstyle ':completion:*' word true
zstyle :compinstall filename "$ZDOTDIR/completion"
autoload -Uz compinit
compinit
# End of lines added by compinstall

View File

@@ -0,0 +1,64 @@
#!/usr/bin/zsh
scratch () {
nvim -c ":set ft=$1" $(mktemp)
}
tsessions() {
tmux list-sessions -F '#{session_name} #{?session_attached,,not_attached}' | \
awk '/not_attached/{print $1}'
}
tkill() {
tsessions | fzf | xargs tmux kill-session -t
}
tswitch() {
tsessions | fzf | xargs tmux switch-client -t
}
tnew() {
tmux new -d -s "$@"
tmux switch-client -t $1
}
asciirec() {
local tempfile=$(mktemp /tmp/asciirec.XXXXX.asciinema.json)
asciinema rec "$tempfile"
fb "$tempfile"
rm "$tempfile"
}
fbs() {
local url=$1
local time=${2-5}
fb <<-HTML | rev | cut -c 2- | rev | xclip
<html>
<head>
<meta http-equiv="refresh" content="${time}; url=${url}" />
</head>
<body>
<h3>Redirect</h3>
<p>Redirecting you to <a href="${url}">${url}</a></p>
</body>
</html>
HTML
xclip -o
}
qrshow() {
local url=$1
[[ -z "$url" ]] && read url
qrencode -o - "$url" | feh -
}
envy() {
env $(cat .env | sed '/^\s*#/d') "$@"
}
add-hash() {
hash -d "$@=$PWD"
echo "hash -d '$@=$PWD'" >> "$ZDOTDIR/hashes"
}

View File

@@ -0,0 +1,79 @@
#!/bin/zsh
# History {{{
HISTFILE="$XDG_CACHE_HOME/zsh/history"
HISTSIZE=100000
SAVEHIST=100000
setopt hist_ignore_dups
setopt hist_ignore_space
setopt share_history
setopt autopushd
mkdir -p $(dirname "$HISTFILE")
# }}}
# Various options {{{
setopt autocd extendedglob nomatch notify histfindnodups
unsetopt beep
setopt interactive_comments
# }}}
# Vim mode
bindkey -v
# Keybindings {{{
bindkey '^?' backward-delete-char
bindkey '^h' backward-delete-char
bindkey '^w' backward-kill-word
bindkey '^r' history-incremental-pattern-search-backward
bindkey '^k' history-beginning-search-backward
bindkey '^j' history-beginning-search-forward
bindkey '^p' push-input
bindkey '^ ' fzf-cd-widget
# }}}
# Prompt {{{
autoload -Uz vcs_info
precmd_functions+=( vcs_info )
setopt prompt_subst
PROMPT="%#$([ -n "$IN_NIX_SHELL" ] && echo '%F{blue}ns%f') %1~%(0?..%F{red})>%f "
RPROMPT=\$vcs_info_msg_0_
zstyle ':vcs_info:git:*' check-for-changes true
zstyle ':vcs_info:git:*' formats '<%b> [%u%c]'
# }}}
# Safe rm {{{
alias rm='rm -I '
setopt rm_star_silent
# }}}
for file in completion functions; do
[ -f "$XDG_CONFIG_HOME/zsh/$file" ] || touch "$XDG_CONFIG_HOME/zsh/$file"
. "$XDG_CONFIG_HOME/zsh/$file"
done
# GPG Agent {{{
export GPG_TTY=$(tty)
gpg-connect-agent updatestartuptty /bye >/dev/null
# }}}
# Base16 {{{
export BASE16_SHELL="$HOME/.config/base16-shell/"
[ -n "$PS1" ] && \
[ -s "$BASE16_SHELL/profile_helper.sh" ] && \
eval "$("$BASE16_SHELL/profile_helper.sh")"
# }}}
# NVM {{{
#if [ -f /usr/share/nvm/init-nvm.sh ]; then
# source /usr/share/nvm/init-nvm.sh
#fi
# }}}
# ENVS {{{
#eval "$(rbenv init -)"
#. /usr/share/nvm/nvm.sh
# }}}
# vim: set foldmethod=marker

15
home/common/secrets.nix Normal file
View File

@@ -0,0 +1,15 @@
{ lib, ... }:
{
age.secrets = lib.listToAttrs (
map
(secret: {
name = secret;
value = {
file = ../../secrets/${secret}.age;
};
})
[
"context7"
]
);
}

18
home/default.nix Normal file
View File

@@ -0,0 +1,18 @@
{
inputs,
pkgs-unstable,
self,
...
}:
{
home-manager = {
users = {
jokke = import ./jokke;
moco = import ./moco;
};
extraSpecialArgs = { inherit inputs self pkgs-unstable; };
sharedModules = [ (import ./common) ];
useUserPackages = true;
useGlobalPkgs = true;
};
}

150
home/gnome/default.nix Normal file
View File

@@ -0,0 +1,150 @@
{
config,
lib,
osConfig,
...
}:
let
cfg = config.gnome;
in
{
imports = [ ./extensions ];
options.gnome = with lib.types; {
profilePicture = lib.mkOption {
type = nullOr path;
default = null;
};
keyboard = {
sources = lib.mkOption {
type = listOf str;
default = [ "us" ];
};
};
keybindings = {
builtin = lib.mkOption {
type = attrsOf (listOf str);
default = { };
};
custom = lib.mkOption {
type = listOf (submodule {
options = {
binding = lib.mkOption { type = str; };
command = lib.mkOption { type = str; };
name = lib.mkOption { type = str; };
};
});
default = [ ];
};
};
scalingFactor = lib.mkOption {
type = numbers.positive;
default = 1;
};
power = {
powerButton = lib.mkOption {
type = enum [
"suspend"
"interactive"
"hibernate"
"nothing"
];
default = "interactive";
};
};
nightLight = {
enable = lib.mkEnableOption { };
temperature = lib.mkOption {
type = ints.between 1700 4700;
default = 2700;
};
schedule = lib.mkOption {
type = nullOr (submodule {
options = {
from = lib.mkOption {
type = numbers.between 0 23.99;
default = 20.0;
};
to = lib.mkOption {
type = numbers.between 0 23.99;
default = 8.0;
};
};
});
default = null;
};
};
calendar = {
showWeekNumbers = lib.mkEnableOption { };
};
automaticTimeZone = lib.mkEnableOption { };
region = lib.mkOption {
type = str;
default = osConfig.i18n.defaultLocale;
};
};
config = {
dconf.settings =
with lib.hm.gvariant;
{
"org/gnome/shell/keybindings" = cfg.keybindings.builtin;
"org/gnome/settings-daemon/plugins/media-keys" = {
custom-keybindings = (
lib.lists.imap0 (
i: _: "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom${toString i}/"
) cfg.keybindings.custom
);
};
"org/gnome/desktop/input-sources" = {
sources = map (
source:
mkTuple [
"xkb"
source
]
) cfg.keyboard.sources;
};
"org/gnome/mutter" = {
experimental-features = [ "scale-monitor-framebuffer" ];
};
"org/gnome/settings-daemon/plugins/xsettings" = {
overrides = [
(mkTuple [
"GdkWindowScalingFactor"
cfg.scalingFactor
])
];
};
"org/gnome/desktop/interface" = {
scaling-factor = cfg.scalingFactor;
};
"org/gnome/settings-daemon/plugins/power" = {
"power-button-action" = cfg.power.powerButton;
};
"org/gnome/settings-daemon/plugins/color" = with cfg.nightLight; {
night-light-enabled = enable;
night-light-temperature = temperature;
night-light-schedule-automatic = isNull schedule;
night-light-schedule-from = lib.mkIf (!isNull schedule) schedule.from;
night-light-schedule-to = lib.mkIf (!isNull schedule) schedule.to;
};
"org/gnome/desktop/calendar" = {
show-weekdate = cfg.calendar.showWeekNumbers;
};
"org/gnome/desktop/datetime" = {
automatic-timezone = cfg.automaticTimeZone;
};
"system/locale" = {
region = cfg.region;
};
}
// (builtins.listToAttrs (
lib.lists.imap0 (i: keybinding: {
name = "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom${toString i}";
value = keybinding;
}) cfg.keybindings.custom
));
home.file.".face".source = lib.mkIf (!isNull cfg.profilePicture) cfg.profilePicture;
};
}

View File

@@ -0,0 +1,12 @@
{ ... }:
let
inherit (builtins) attrNames filter readDir;
dirs =
let
files = readDir ./.;
in
filter (name: files."${name}" == "directory") (attrNames files);
in
{
imports = map (dir: ./${dir}) dirs;
}

View File

@@ -0,0 +1,29 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.gnome.extensions.freon;
in
{
options.gnome.extensions = with lib.types; {
freon = {
enable = lib.mkEnableOption { };
package = lib.mkOption {
type = package;
default = pkgs.gnomeExtensions.freon;
};
};
};
config = lib.mkIf cfg.enable {
dconf.settings = {
"org/gnome/shell".enabled-extensions = [
cfg.package.extensionUuid
];
};
home.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,97 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.gnome.extensions;
in
{
options.gnome.extensions = with lib.types; {
paperwm = {
enable = lib.mkEnableOption { };
package = lib.mkOption {
type = package;
default = pkgs.gnomeExtensions.paperwm;
};
cycle-height-steps = lib.mkOption {
type = listOf numbers.nonnegative;
default = [
0.33
0.5
0.67
1.0
];
};
cycle-width-steps = lib.mkOption {
type = listOf numbers.nonnegative;
default = [
0.33
0.5
0.67
1.0
];
};
horizontal-margin = lib.mkOption {
type = ints.unsigned;
default = 5;
};
vertical-margin = lib.mkOption {
type = ints.unsigned;
default = 5;
};
window-gap = lib.mkOption {
type = ints.unsigned;
default = 5;
};
winprops = lib.mkOption {
type = listOf (submodule {
options = {
wm_class = lib.mkOption {
type = str;
default = "";
};
title = lib.mkOption {
type = str;
default = "";
};
scratch_layer = lib.mkEnableOption { };
preferredWidth = lib.mkOption {
type = nullOr str;
default = null;
};
};
});
default = [ ];
};
keybindings = lib.mkOption {
type = attrsOf (listOf str);
default = { };
};
};
};
config = lib.mkIf cfg.paperwm.enable {
home.packages = [ cfg.paperwm.package ];
dconf.settings = {
"org/gnome/shell".enabled-extensions = lib.mkIf cfg.paperwm.enable [
cfg.paperwm.package.extensionUuid
];
"org/gnome/shell/extensions/paperwm" =
with cfg.paperwm;
lib.mkIf cfg.paperwm.enable {
inherit
cycle-height-steps
cycle-width-steps
horizontal-margin
vertical-margin
window-gap
;
winprops = map (props: builtins.toJSON props) winprops;
};
"org/gnome/shell/extensions/paperwm/keybindings" =
lib.mkIf cfg.paperwm.enable cfg.paperwm.keybindings;
};
};
}

58
home/jokke/default.nix Normal file
View File

@@ -0,0 +1,58 @@
{
pkgs,
pkgs-unstable,
inputs,
...
}:
{
config = {
# Home Manager needs a bit of information about you and the paths it should
# manage.
home.username = "jokke";
home.homeDirectory = "/home/jokke";
home.packages = with pkgs; [
ffmpeg
mediainfo
git-lfs
telegram-desktop
shards
deno
virt-manager
gimp
lftp
inkscape
wineWowPackages.waylandFull
reaper
inputs.ksoloti-pr.legacyPackages.${pkgs.stdenv.hostPlatform.system}.ksoloti
calibre
jellyfin-media-player
darktable
shutter
git-annex
hledger
hledger-ui
hledger-iadd
];
gnome.profilePicture = ../assets/profile-pictures/jokke.png;
programs.ssh.matchBlocks = {
"alderaan" = {
hostname = "alderaan.space";
user = "root";
};
"base.alderaan" = {
hostname = "base.alderaan.space";
user = "luke";
};
"jokke.space" = {
user = "root";
};
};
programs.git.settings.user.email = "joakim@repomaa.com";
services.syncthing = {
enable = true;
};
};
}

234
home/moco/default.nix Normal file
View File

@@ -0,0 +1,234 @@
{
pkgs,
pkgs-unstable,
lib,
...
}:
let
homeDirectory = "/home/moco";
in
{
# Home Manager needs a bit of information about you and the paths it should
# manage.
home = {
username = "moco";
inherit homeDirectory;
packages = with pkgs; [
chromium
(slack.overrideAttrs (oldAttrs: {
fixupPhase = ''
sed -i -e 's/,"WebRTCPipeWireCapturer"/,"LebRTCPipeWireCapturer"/' $out/lib/slack/resources/app.asar
rm $out/bin/slack
makeWrapper $out/lib/slack/slack $out/bin/slack \
--prefix XDG_DATA_DIRS : $GSETTINGS_SCHEMAS_PATH \
--suffix PATH : ${lib.makeBinPath [ xdg-utils ]} \
--add-flags "--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations,WebRTCPipeWireCapturer"
'';
}))
pkgs-unstable.chromedriver
kooha
gnome-pomodoro
mitmproxy
(writeShellScriptBin "moco-admin-pw" ''
password=$(op item get n5h4c237tqnnlr5gruxyqmmggy --reveal --fields label=Kennwort)
otp=$(ykman oath accounts code -s 'MOCO:Admin')
echo "''${password}''${otp}" | wl-copy -n
'')
(writeShellScriptBin "moco-reto-otp" ''
ykman oath accounts code -s 'MOCO Reto' | wl-copy -n
'')
];
};
gnome = {
profilePicture = ../assets/profile-pictures/moco.png;
calendar.showWeekNumbers = true;
extensions.paperwm.winprops = [
{
wm_class = "gnome-pomodoro";
scratch_layer = true;
}
];
};
programs.poetry = {
enable = true;
settings = {
virtualenvs = {
create = true;
inProject = true;
};
};
};
programs.ashell.settings.settings.CustomButton =
let
nmcli = lib.getExe' pkgs.networkmanager "nmcli";
ykman = lib.getExe pkgs.yubikey-manager;
isMocoVpnActive = lib.getExe (
pkgs.writeShellScriptBin "is-moco-vpn-active" ''
${nmcli} -t -f NAME connection show --active | grep -q '^moco$'
''
);
toggleMocoVpn = lib.getExe (
pkgs.writeShellScriptBin "toggle-moco-vpn" ''
if ${isMocoVpnActive}; then
${nmcli} c down moco
else
${ykman} oath accounts code -s 'MOCO Reto' | ${nmcli} c up moco --ask
fi
''
);
in
[
{
name = "MOCO VPN";
icon = "󰖂";
command = toggleMocoVpn;
status_command = isMocoVpnActive;
}
];
programs.zsh = {
cdpath = [
"${homeDirectory}/Code/mocoapp"
"${homeDirectory}/Code/mocoapp-promo"
"${homeDirectory}/Code/mocoapp-llama"
];
dirHashes = {
moco = "${homeDirectory}/Code/mocoapp";
promo = "${homeDirectory}/Code/mocoapp-promo";
llama = "${homeDirectory}/Code/mocoapp-llama";
};
};
programs.git.settings.user.email = "joakim.repomaa@mocoapp.com";
programs.ssh.matchBlocks =
let
aliases = [
"moco"
"mocoapp"
"mocoapp.com"
];
matcher =
subdomains:
builtins.concatStringsSep " " (
map (
alias:
builtins.concatStringsSep " " (
map (subdomain: "*.${subdomain}.${alias} ${subdomain}.${alias}") subdomains
)
) aliases
);
in
{
"console.*.moco" = {
extraOptions = {
RequestTTY = "force";
RemoteCommand = "./rails-console.sh";
};
};
"${matcher [
"reto"
"reto.intern"
]}" =
{
hostname = "reto.intern.mocoapp.com";
user = "jokke";
};
"${matcher [ "web" ]}" = {
hostname = "mocoapp.com";
};
"${matcher [ "prod-inc" ]}" = {
hostname = "prod-inc.mocoapp.com";
};
"${matcher [ "prod" ]}" = {
hostname = "web02.mocoapp";
};
"${matcher [
"pfg"
"primeforcegroup"
]}" =
{
hostname = "primeforcegroup.mocoapp.com";
};
"${matcher [ "crafft" ]}" = {
hostname = "crafft.mocoapp.com";
};
"${matcher [
"bd"
"businessdecision"
]}" =
{
hostname = "businessdecision.mocoapp.com";
};
"${matcher [ "festland" ]}" = {
hostname = "festland.mocoapp.com";
};
"${matcher [
"oi"
"one-inside"
]}" =
{
hostname = "one-inside.mocoapp.com";
};
"${matcher [
"se"
"scope-engineering"
]}" =
{
hostname = "scope-engineering.mocoapp.com";
};
"${matcher [
"cpc"
"cpc-ag"
]}" =
{
hostname = "cpc-ag.mocoapp.com";
};
"${matcher [ "weareact3" ]}" = {
hostname = "weareact3.mocoapp.com";
};
"${matcher [ "avenit" ]}" = {
hostname = "avenit.mocoapp.com";
};
"${matcher [ "staging" ]}" = {
hostname = "staging.mocoapp.com";
};
"${matcher [ "staging-v2" ]}" = {
hostname = "staging-v2.mocoapp.com";
};
"${matcher [ "staging-v3" ]}" = {
hostname = "staging-v3.mocoapp.com";
};
"${matcher [ "staging-v4" ]}" = {
hostname = "staging-v4.mocoapp.com";
};
"${matcher [ "staging-v5" ]}" = {
hostname = "staging-v5.mocoapp.com";
};
"${matcher [ "staging-v6" ]}" = {
hostname = "staging-v6.mocoapp.com";
};
"${matcher [
"prod-db"
"production-db"
]}" =
{
hostname = "production-db.mocoapp";
};
"${matcher [ "pdf-renderer" ]}" = {
hostname = "mocoapp-pdf-renderer01.mocoapp.com";
};
"*.moco *.mocoapp *.mocoapp.com" = {
forwardAgent = true;
};
"*.moco !reto.moco *.mocoapp.com !reto.mocoapp.com" = {
user = "butler";
};
};
}

View File

@@ -0,0 +1,43 @@
{
config,
lib,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.services.elephant;
wrappedPackage = pkgs.symlinkJoin {
inherit (cfg.package) name meta;
paths = [ cfg.package ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/elephant \
--prefix PATH : ${lib.makeBinPath [ pkgs.bash ]}
'';
};
in
{
options.services.elephant = {
enable = lib.mkEnableOption "Enable Elephant";
package = lib.mkOption {
type = lib.types.package;
default = pkgs-unstable.elephant;
};
};
config = lib.mkIf cfg.enable {
systemd.user.services.elephant = {
Unit = {
Description = "Elephant";
After = [ "niri.service" ];
Wants = [ "niri.service" ];
};
Service = {
ExecStart = lib.getExe wrappedPackage;
Restart = "on-failure";
};
Install.WantedBy = [ "graphical-session.target" ];
};
};
}

View File

@@ -0,0 +1,92 @@
{
config,
osConfig,
lib,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.programs.voxtype;
postProcessUnwrapped =
pkgs.runCommand "voxtype-post-process-unwrapped"
{
code = ./post-process.cr;
nativeBuildInputs = [
pkgs-unstable.crystal
];
}
''
mkdir -p $out/bin
crystal build $code -o $out/bin/voxtype-post-process
'';
postProcess = pkgs.symlinkJoin {
name = "voxtype-post-process";
paths = [ postProcessUnwrapped ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/voxtype-post-process \
--set OLLAMA_PORT ${toString osConfig.services.ollama.port} \
--set WALKER_BIN ${lib.getExe config.services.walker.package}
'';
meta.mainProgram = "voxtype-post-process";
};
in
{
options.programs.voxtype = {
postProcessing = lib.mkOption {
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "Enable post-processing of transcriptions";
settings = lib.mkOption {
type = lib.types.submodule {
options = {
model = lib.mkOption {
type = lib.types.str;
description = "The ollama model to use for post-processing";
};
commonInstructions = lib.mkOption {
type = lib.types.str;
default = "no quotes, no emojis, no explanations";
description = "Instructions to include in every post-processing prompt";
};
prompts = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
title = lib.mkOption {
type = lib.types.str;
description = "A title for this prompt, used in the selector";
};
instructions = lib.mkOption {
type = lib.types.str;
description = "Instructions to include in the post-processing prompt, in addition to the common instructions";
};
};
}
);
default = [
{
title = "Clean up";
instructions = "Clean up this dictation. Remove filler words, fix grammar and punctuation. Output ONLY the cleaned text";
}
];
};
};
};
default = { };
};
};
};
};
};
config = lib.mkIf cfg.postProcessing.enable {
xdg.configFile."voxtype/post-processing.json".text = builtins.toJSON cfg.postProcessing.settings;
programs.voxtype.settings.output.post_process = {
command = lib.getExe postProcess;
timeout_ms = 5 * 60 * 1000; # 5 minutes
};
};
}

View File

@@ -0,0 +1,54 @@
require "json"
require "http/client"
struct OllamaResponse
include JSON::Serializable
getter response : String
end
struct Prompt
include JSON::Serializable
getter title : String
getter instructions : String
end
struct Config
include JSON::Serializable
getter model : String
getter prompts : Array(Prompt)
@[JSON::Field(key: "commonInstructions")]
getter common_instructions : String
end
config_path = "#{ENV.fetch("XDG_CONFIG_HOME", "~/.config")}/voxtype/post-processing.json"
config = File.open(config_path) { |file| Config.from_json(file) }
client = HTTP::Client.new("localhost", ENV.fetch("OLLAMA_PORT", "11434").to_i)
prompt_selection = Process.run(ENV["WALKER_BIN"], ["--dmenu"]) do |process|
config.prompts.each do |prompt|
process.input.puts prompt.title
end
process.input.close
process.output.gets_to_end.chomp
end
instructions = config.prompts.find { |prompt| prompt.title == prompt_selection }.try(&.instructions) || prompt_selection
payload = {
model: config.model,
prompt: "#{instructions} - #{config.common_instructions}:\n\n#{STDIN.gets_to_end.chomp}",
think: false,
stream: false,
}
client.post("/api/generate", body: payload.to_json) do |response|
if response.status_code == 200
puts OllamaResponse.from_json(response.body_io).response.strip
else
abort "Ollama API error: #{response.status_code} #{response.body}"
end
end

View File

@@ -0,0 +1,469 @@
{
config,
lib,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.programs.zed-editor;
ameba-ls = pkgs.stdenv.mkDerivation rec {
pname = "ameba-ls";
version = "0.1.0";
src =
let
selectSystem =
attrs:
attrs.${pkgs.stdenv.hostPlatform.system}
or (throw "Unsupported system: ${pkgs.stdenv.hostPlatform.system}");
in
pkgs.fetchurl (selectSystem {
x86_64-linux = {
url = "https://github.com/crystal-lang-tools/ameba-ls/releases/download/v${version}/ameba-ls-${version}-x86_64-linux-musl.tar.gz";
hash = "sha256-NtqR8NHytuHT1XIhRVvnp7Lo4Ed9UGbISbTn/BfLGA8=";
};
aarch64-linux = {
url = "https://github.com/crystal-lang-tools/ameba-ls/releases/download/v${version}/ameba-ls-${version}-aarch64-linux-musl.tar.gz";
hash = "sha256-77iqdaI+Mqivk0/9jkNdGpluDbz297vsY9wzuipcPOU=";
};
aarch64-darwin = {
url = "https://github.com/crystal-lang-tools/ameba-ls/releases/download/v${version}/ameba-ls-${version}-aarch64-apple-darwin.tar.gz";
hash = "sha256-tDB+ZjgjlGjNg3DjX09z1dJnqTv9h/1sXiauBj7uHNc=";
};
});
sourceRoot = ".";
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp ameba-ls $out/bin/
chmod +x $out/bin/ameba-ls
runHook postInstall
'';
meta = with lib; {
description = "Language server for the Ameba linter for Crystal lang";
homepage = "https://github.com/crystal-lang-tools/ameba-ls";
license = licenses.mit;
platforms = [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
];
mainProgram = "ameba-ls";
};
};
in
{
options.programs.zed-editor = {
defaultEditor = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
config = lib.mkIf cfg.enable {
programs.zed-editor = {
package = pkgs-unstable.zed-editor;
extensions = [
"ruby"
"crystal"
"nix"
"svelte"
];
userSettings = {
node = {
path = lib.getExe pkgs.nodejs_latest;
npm_path = lib.getExe' pkgs.nodejs_latest "npm";
};
agent = {
enabled = true;
default_model = {
provider = "openai";
model = "kimi-k2.5";
};
};
auto_update = false;
telemetry = {
diagnostics = false;
metrics = false;
};
vim_mode = true;
languages = {
Ruby = {
language_servers = [
"ruby-lsp"
"rubocop"
"!solargraph"
];
formatter.external = {
command = pkgs.writeShellScript "rufo" ''
bundle exec rufo "$@"
if [ $? -eq 1 ]; then
exit 1
fi
'';
arguments = [
"--filename"
"{buffer_path}"
];
};
};
Nix = {
formatter.external = {
command = lib.getExe pkgs.nixfmt-rfc-style;
arguments = [ "-q" ];
};
};
Rust = {
formatter.external = {
command = lib.getExe pkgs.rustfmt;
arguments = [
"--edition"
"2018"
];
};
};
Crystal = {
language_servers = [
"crystalline"
"ameba-ls"
];
formatter.external = {
command = lib.getExe pkgs.crystal;
arguments = [
"tool"
"format"
"--no-color"
"-"
];
};
};
};
lsp = with pkgs; {
nixd.binary.path = lib.getExe nixd;
nil.binary.path = lib.getExe nil;
ruby-lsp = {
initialization_options = {
enabledFeatures = {
codeActions = true;
codeLens = true;
completion = true;
definition = true;
diagnostics = true;
documentHighlights = true;
documentLink = true;
documentSymbols = true;
foldingRanges = true;
formatting = false;
hover = true;
inlayHint = true;
onTypeFormatting = true;
selectionRanges = true;
semanticHighlighting = true;
signatureHelp = true;
typeHierarchy = true;
workspaceSymbol = true;
};
linters = [ "standard" ];
};
settings.use_bundler = true;
};
rubocop.binary = {
path = "bundle";
arguments = [
"exec"
"rubocop"
"--lsp"
];
};
yaml-language-server.binary = {
path = lib.getExe yaml-language-server;
arguments = [ "--stdio" ];
};
json-language-server.binary = {
path = lib.getExe nodePackages.vscode-json-languageserver;
arguments = [ "--stdio" ];
};
package-version-server.binary.path = lib.getExe pkgs-unstable.package-version-server;
eslint.settings.onIgnoredFiles = "off";
crystalline.binary.path = lib.getExe crystalline;
ameba-ls.binary.path = lib.getExe ameba-ls;
rust-analyzer.binary.path = lib.getExe rust-analyzer;
};
load_direnv = "shell_hook";
theme = {
mode = "system";
light = "One Light";
dark = "One Dark";
};
terminal = {
line_height = "standard";
font_family = "IosevkaTerm Nerd Font";
};
edit_predictions = {
enabled = true;
mode = "eager";
provider = "copilot";
};
buffer_font_family = "Iosevka Nerd Font";
buffer_font_size = 16;
ui_font_size = 17;
relative_line_numbers = "enabled";
file_types = {
JSONC = [
"tsconfig.json"
"tsconfig.*.json"
];
};
language_models = {
openai = {
api_url = "https://opencode.ai/zen/v1";
available_models = [
# Claude models
{
name = "claude-opus-4-6";
display_name = "OpenCode Zen - Claude Opus 4.6";
max_tokens = 1048576;
}
{
name = "claude-opus-4-5";
display_name = "OpenCode Zen - Claude Opus 4.5";
max_tokens = 200000;
}
{
name = "claude-opus-4-1";
display_name = "OpenCode Zen - Claude Opus 4.1";
max_tokens = 200000;
}
{
name = "claude-sonnet-4";
display_name = "OpenCode Zen - Claude Sonnet 4";
max_tokens = 1048576;
}
{
name = "claude-sonnet-4-5";
display_name = "OpenCode Zen - Claude Sonnet 4.5";
max_tokens = 1048576;
}
{
name = "claude-3-5-haiku";
display_name = "OpenCode Zen - Claude 3.5 Haiku";
max_tokens = 200000;
}
{
name = "claude-haiku-4-5";
display_name = "OpenCode Zen - Claude Haiku 4.5";
max_tokens = 200000;
}
# GPT models
{
name = "gpt-5.2";
display_name = "OpenCode Zen - GPT 5.2";
max_tokens = 400000;
}
{
name = "gpt-5.2-codex";
display_name = "OpenCode Zen - GPT 5.2 Codex";
max_tokens = 400000;
}
{
name = "gpt-5.1";
display_name = "OpenCode Zen - GPT 5.1";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex-max";
display_name = "OpenCode Zen - GPT 5.1 Codex Max";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex";
display_name = "OpenCode Zen - GPT 5.1 Codex";
max_tokens = 400000;
}
{
name = "gpt-5.1-codex-mini";
display_name = "OpenCode Zen - GPT 5.1 Codex Mini";
max_tokens = 400000;
}
{
name = "gpt-5";
display_name = "OpenCode Zen - GPT 5";
max_tokens = 400000;
}
{
name = "gpt-5-codex";
display_name = "OpenCode Zen - GPT 5 Codex";
max_tokens = 400000;
}
{
name = "gpt-5-nano";
display_name = "OpenCode Zen - GPT 5 Nano";
max_tokens = 400000;
}
# Gemini models
{
name = "gemini-3-pro";
display_name = "OpenCode Zen - Gemini 3 Pro";
max_tokens = 1048576;
}
{
name = "gemini-3-flash";
display_name = "OpenCode Zen - Gemini 3 Flash";
max_tokens = 1048576;
}
# GLM models
{
name = "glm-4.7";
display_name = "OpenCode Zen - GLM 4.7";
max_tokens = 205000;
}
{
name = "glm-4.6";
display_name = "OpenCode Zen - GLM 4.6";
max_tokens = 205000;
}
{
name = "glm-4.7-free";
display_name = "OpenCode Zen - GLM 4.7 Free";
max_tokens = 205000;
}
# Kimi models
{
name = "kimi-k2.5";
display_name = "OpenCode Zen - Kimi K2.5";
max_tokens = 262000;
}
{
name = "kimi-k2.5-free";
display_name = "OpenCode Zen - Kimi K2.5 Free";
max_tokens = 262000;
}
{
name = "kimi-k2";
display_name = "OpenCode Zen - Kimi K2";
max_tokens = 262000;
}
{
name = "kimi-k2-thinking";
display_name = "OpenCode Zen - Kimi K2 Thinking";
max_tokens = 262000;
}
# MiniMax models
{
name = "minimax-m2.1";
display_name = "OpenCode Zen - MiniMax M2.1";
max_tokens = 205000;
}
{
name = "minimax-m2.5-free";
display_name = "OpenCode Zen - MiniMax M2.5 Free";
max_tokens = 205000;
}
{
name = "minimax-m2.1-free";
display_name = "OpenCode Zen - MiniMax M2.1 Free";
max_tokens = 205000;
}
# Other models
{
name = "trinity-large-preview-free";
display_name = "OpenCode Zen - Trinity Large Preview Free";
max_tokens = 131000;
}
{
name = "big-pickle";
display_name = "OpenCode Zen - Big Pickle";
max_tokens = 200000;
}
{
name = "alpha-g5";
display_name = "OpenCode Zen - Alpha G5";
max_tokens = 200000;
}
];
};
};
};
userKeymaps = [
{
context = "VimControl && !menu";
bindings = {
"space g" = [
"task::Spawn"
{
task_name = "lazygit";
reveal_target = "center";
}
];
"space o" = [
"task::Spawn"
{
task_name = "opencode";
reveal_target = "center";
}
];
"space r" = "pane::DeploySearch";
"space f" = [
"file_finder::Toggle"
{ separate_history = true; }
];
"space :" = "task::Spawn";
"g R" = "editor::FindAllReferences";
};
}
{
context = "Dock || Pane || Editor";
bindings = {
"ctrl-h" = "workspace::ActivatePaneLeft";
"ctrl-j" = "workspace::ActivatePaneDown";
"ctrl-k" = "workspace::ActivatePaneUp";
"ctrl-l" = "workspace::ActivatePaneRight";
};
}
{
context = "Terminal";
bindings = {
"ctrl-p" = [
"terminal::SendKeystroke"
"ctrl-p"
];
};
}
];
userTasks = [
{
label = "lazygit";
command = lib.getExe config.programs.lazygit.package;
env = {
EDITOR = lib.getExe cfg.package;
};
args = [
"-p"
"$ZED_WORKTREE_ROOT"
];
reveal = "always";
allow_concurrent_runs = true;
use_new_terminal = false;
hide = "on_success";
}
{
label = "opencode";
command = lib.getExe pkgs-unstable.opencode;
reveal = "always";
allow_concurrent_runs = true;
use_new_terminal = false;
hide = "on_success";
}
];
};
home.sessionVariables = lib.mkIf cfg.defaultEditor {
EDITOR = "${lib.getExe cfg.package} -w";
};
};
}

283
hosts/apu/configuration.nix Normal file
View File

@@ -0,0 +1,283 @@
# Edit this configuration file to define what should be installed on
# your system. Help is availanodev";
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{
ssh,
pkgs,
config,
lib,
...
}:
{
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda";
networking.hostName = "apu";
networking.useNetworkd = true;
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "daily";
options = "--delete-older-than 7d";
};
};
modules.vlans = {
enable = true;
networks = {
koti = {
id = 10;
ipv6 = true;
};
gast = {
id = 20;
ipv6 = true;
staticLeases = {
"dc:a6:32:05:08:5d" = "10.20.1.235";
};
};
iot = {
id = 30;
};
cfg = {
id = 40;
staticLeases = {
"8c:3b:ad:c5:b8:ee" = "10.40.0.10";
};
};
};
bridge = {
enable = true;
pvid = config.modules.vlans.networks.cfg.id;
netdev = "20-lan";
network = "30-lan";
bindNetwork = "30-bind-lan";
};
};
modules.firewall = {
enable = true;
interfaces = {
koti = [
"dhcp"
"dns"
"ssh"
"web"
];
gast = [
"dhcp"
"dns"
];
iot = [
"dhcp"
"dns"
];
cfg = [
"dhcp"
"dns"
];
"tailscale*" = [
"ssh"
"web"
];
};
allInterfaces = [ ];
};
systemd.network = {
enable = true;
config.networkConfig.IPv6Forwarding = true;
links = {
"10-extern0" = {
matchConfig.Path = "pci-0000:01:00.0";
linkConfig.Name = "extern0";
};
"10-intern0" = {
matchConfig.Path = "pci-0000:02:00.0";
linkConfig.Name = "intern0";
};
"10-intern1" = {
matchConfig.Path = "pci-0000:03:00.0";
linkConfig.Name = "intern1";
};
};
netdevs = {
"20-lan" = {
netdevConfig = {
Name = "lan";
Kind = "bridge";
};
};
};
networks = {
"30-bind-lan" = {
matchConfig = {
Name = "intern*";
};
networkConfig = {
Bridge = "lan";
};
};
"30-lan" = {
matchConfig = {
Name = "lan";
};
networkConfig = {
IPv6AcceptRA = false;
ConfigureWithoutCarrier = true;
};
};
"30-wan" = {
matchConfig = {
Name = "extern0";
};
networkConfig = {
DHCP = true;
DNS = "127.0.0.1";
IPv6AcceptRA = true;
IPv4Forwarding = true;
};
dhcpV6Config = {
PrefixDelegationHint = "::/56";
};
dhcpV4Config = {
Use6RD = true;
};
};
};
};
services.networkd-dispatcher = {
enable = true;
rules."50-tailscale" = {
onState = [ "routable" ];
script = ''
#!${pkgs.runtimeShell}
${pkgs.ethtool}/bin/ethtool -K ${
config.systemd.network.links."10-extern0".linkConfig.Name
} rx-udp-gro-forwarding on rx-gro-list off
'';
};
};
time.timeZone = "Europe/Helsinki";
users.users.jokke = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = [ pkgs.nh ];
openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
initialPassword = "change-me";
};
environment.systemPackages = with pkgs; [
wget
curl
dig
neovim
vim
htop
];
services.openssh = {
enable = true;
openFirewall = false;
settings.PasswordAuthentication = false;
};
services.tailscale = {
enable = true;
useRoutingFeatures = "both";
};
services.resolved.enable = false;
modules.services.dhcp-dns-sync = {
enable = true;
interface = "koti";
domain = "home.arpa";
interval = "30s";
};
services.unbound = {
enable = true;
settings = {
server = {
interface =
(lib.map (name: config.systemd.network.networks."30-${name}".dhcpServerConfig.DNS) (
lib.attrNames config.modules.vlans.networks
))
++ [
"127.0.0.1"
"::1"
];
access-control = [
"10.0.0.0/8 allow"
"127.0.0.0/8 allow"
"::1/128 allow"
];
verbosity = 2;
};
forward-zone = [
{
name = ".";
forward-addr = "100.84.105.63#dns.freun.dev";
forward-tls-upstream = true;
}
];
remote-control.control-enable = true;
};
};
services.avahi = {
enable = true;
nssmdns4 = true;
reflector = true;
allowInterfaces = [ "lan" ];
openFirewall = true;
};
services.invidious-companion = {
enable = true;
host = "0.0.0.0";
port = 8282;
secretKeyFile = config.age.secrets.invidious-companion.path;
binaryHash = "sha256-nZXKpExKCc2zgSdVT3qo05NyFdpM9H9NJB5UWo+MVWI=";
};
networking.firewall = {
enable = true;
interfaces.tailscale0.allowedTCPPorts = [ 8282 ];
};
security.acme.defaults.environmentFile = config.age.secrets.hetzner.path;
networking = {
nftables.enable = true;
useDHCP = false;
domain = "apu.home.arpa";
};
system.stateVersion = "24.05";
}

12
hosts/apu/default.nix Normal file
View File

@@ -0,0 +1,12 @@
{ inputs, ... }:
let
inherit (inputs) nixos-hardware;
in
{
imports = [
./hardware-configuration.nix
./configuration.nix
./secrets.nix
nixos-hardware.nixosModules.pcengines-apu
];
}

View File

@@ -0,0 +1,69 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"ehci_pci"
"usb_storage"
"sd_mod"
"sdhci_pci"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/f221c6a7-e05e-40dc-bc85-7970d7c8f22b";
fsType = "btrfs";
options = [ "subvol=@" ];
};
fileSystems."/var/log" = {
device = "/dev/disk/by-uuid/f221c6a7-e05e-40dc-bc85-7970d7c8f22b";
fsType = "btrfs";
options = [ "subvol=@var_log" ];
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/14D2-F8F4";
fsType = "vfat";
options = [
"fmask=0022"
"dmask=0022"
];
};
fileSystems."/swap" = {
device = "/dev/disk/by-uuid/f221c6a7-e05e-40dc-bc85-7970d7c8f22b";
fsType = "btrfs";
options = [ "subvol=@swap" ];
};
swapDevices = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
# networking.interfaces.enp2s0.useDHCP = lib.mkDefault true;
# networking.interfaces.enp3s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

16
hosts/apu/secrets.nix Normal file
View File

@@ -0,0 +1,16 @@
{ lib, ... }:
{
age.secrets = lib.listToAttrs (
map
(secret: {
name = secret;
value = {
file = ../../secrets/${secret}.age;
};
})
[
"hetzner"
"invidious-companion"
]
);
}

View File

@@ -2,50 +2,87 @@
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running `nixos-help`).
{ pkgs, ... }:
{
config,
pkgs,
ssh,
...
}:
let
ipv4Address = "65.21.145.150";
ipv6Address = "2a01:4f9:c011:9ac1::1";
in
{
nix = {
settings = {
experimental-features = [ "nix-command" "flakes" ];
experimental-features = [
"nix-command"
"flakes"
];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
options = "--delete-older-than 7d";
};
};
system.autoUpgrade = {
enable = true;
flake = "/etc/nixos";
flags = [
"--update-input"
"nixpkgs"
"-L" # print build logs
];
dates = "02:00";
randomizedDelaySec = "45min";
};
# Use the GRUB 2 boot loader.
boot.loader.grub.enable = true;
boot.loader.grub.efiSupport = false;
# boot.loader.grub.efiInstallAsRemovable = true;
# boot.loader.efi.efiSysMountPoint = "/boot/efi";
# Define on which hard drive you want to install Grub.
boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.binfmt.emulatedSystems = [ "x86_64-linux" ];
networking.hostName = "freun-dev"; # Define your hostname.
networking.domain = "freun.dev";
networking.useDHCP = false;
networking.nftables.enable = true;
services.octodns.records = {
"" = {
A = {
ttl = 86400;
values = [ ipv4Address ];
};
AAAA = {
ttl = 86400;
values = [ ipv6Address ];
};
TXT = {
ttl = 86400;
values = [
"v=spf1 include:spf.uberspace.de ~all"
];
};
};
"_dmarc".TXT = {
ttl = 86400;
values = [ "v=DMARC1\\; p=reject" ];
};
"uberspace._domainkey".TXT = {
ttl = 86400;
values = [
"v=DKIM1\\;t=s\\;n=core\\;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArjZrrWhFn/xSH3+aPA3G1eGD5yAeyn7ZhHSCBM7b/9A5Fny9URjEf1La7dcU+oF/d7outgmTCs/umItBCB7ybkMriaLC+RBZJY0blf35bMYWL9NLpbKzVVAZQPgDZJm7R/grbg0nJgb5RCGLYu9iPcRjJtpu2vYaitkNh8WJ4q+iA/YOos2pQmP+6I5zLTVenFfEWJccdtfhJcORSuONN+Xi1+dQxpQzlwxUWz4DgOsfVaE3woDH3RAAQCtiMyk8ZQhhrL85pHH6zT3sNXzzVphJdIIYA+VYAgYYOTkRbdDhYSd5zCDX5f4L1UmBsJS2ommBGvw2qMDhagXsaN32BwIDAQAB"
];
};
"ts" = {
A = {
ttl = 86400;
values = [ "100.84.105.63" ];
};
AAAA = {
ttl = 86400;
values = [ "fd7a:115c:a1e0::7901:693f" ];
};
};
};
systemd.network = {
enable = true;
networks.static = {
name = "enp1s0";
address = [
"95.217.223.61/32"
"2a01:4f9:c012:5e97::1/64"
"${ipv4Address}/32"
"${ipv6Address}/64"
];
routes = [
{ Gateway = "fe80::1"; }
@@ -69,6 +106,17 @@
};
};
services.networkd-dispatcher = {
enable = true;
rules."50-tailscale" = {
onState = [ "routable" ];
script = ''
#!${pkgs.runtimeShell}
${pkgs.ethtool}/bin/ethtool -K ${config.systemd.network.networks.static.name} rx-udp-gro-forwarding on rx-gro-list off
'';
};
};
# Set your time zone.
time.timeZone = "Europe/Helsinki";
@@ -84,30 +132,39 @@
# useXkbConfig = true; # use xkbOptions in tty.
# };
# Configure keymap in X11
services.xserver.xkb.layout = "us";
services.xserver.xkb.options = "eurosign:e,caps:escape";
# Define a user account. Don't forget to set a password with passwd.
users.users.jokke = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
# packages = with pkgs; [
# firefox
# tree
# ];
openssh.authorizedKeys.keys = [
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLIUkESu5NnBi1M0+ZjYrkp6/rIFuwc3aguspf98jmOydNce6l65cnS3GRzc9oWx4lu11ahi87ZuE+pYV+gaHm4="
];
users.users = {
jokke = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = [ pkgs.nh ];
openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
};
builder = {
isNormalUser = true;
openssh.authorizedKeys.keys = [ ssh.publicKeys.builder ];
};
root.openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
};
nix.settings.trusted-users = [
"jokke"
"builder"
];
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
wget
htop
git
];
# Some programs need SUID wrappers, can be configured further or are
@@ -126,11 +183,10 @@
settings.PasswordAuthentication = false;
};
# Open ports in the firewall.
networking.firewall.allowedTCPPorts = [ 22 ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
modules.firewall = {
enable = true;
allInterfaces = [ "ssh" ];
};
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
@@ -143,5 +199,5 @@
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.05"; # Did you read the comment?
system.stateVersion = "24.11"; # Did you read the comment?
}

View File

@@ -1,7 +1,9 @@
{ ... }: {
{ ... }:
{
imports = [
./configuration.nix
./hardware-configuration.nix
./configuration.nix
./services.nix
./secrets.nix
];
}

339
hosts/freun-dev/glance.nix Normal file
View File

@@ -0,0 +1,339 @@
{ config, ... }:
let
secrets = config.age.secrets;
in
{
services.tailscaledGlance = {
enable = true;
subdomain = "glance";
settings = {
server.port = 4114;
pages = [
{
name = "Home";
columns = [
{
size = "small";
widgets = [
{
type = "clock";
hour-format = "24h";
timezones = [
{
timezone = "Europe/Zurich";
label = "Zürich";
}
];
}
{
type = "group";
title = "Weather";
widgets =
let
buildWeatherWidget =
{ location, title }:
{
type = "weather";
hide-location = true;
hour-format = "24h";
units = "metric";
inherit location title;
};
in
builtins.map buildWeatherWidget [
{
title = "Espoo";
location = "Espoo, Finland";
}
{
title = "Mökki";
location = "Pellosniemi, Finland";
}
{
title = "Frankfurt";
location = "Frankfurt, Germany";
}
];
}
{
type = "markets";
markets = [
{
symbol = "KOZ0.F";
name = "Kongsberg Gruppen";
}
{
symbol = "AMD";
name = "AMD";
}
];
}
{
type = "group";
widgets = [
{
type = "rss";
title = "News";
limit = 10;
collapse-after = 3;
cache = "12h";
feeds = [
{
url = "https://yle.fi/rss/uutiset/paauutiset";
title = "Yle";
}
];
}
{
type = "rss";
title = "Tech";
limit = 10;
collapse-after = 3;
cache = "12h";
feeds = [
{
url = "https://nixos.org/blog/announcements-rss.xml";
title = "NixOS";
}
{
url = "https://selfh.st/rss";
title = "selfh.st";
}
];
}
{
type = "rss";
title = "Blogs";
limit = 10;
collapse-after = 3;
cache = "12h";
feeds = [
{
url = "https://samharris.substack.com/feed";
title = "Sam Harris";
}
];
}
];
}
{
type = "custom-api";
title = "Steam Specials";
cache = "12h";
url = "https://store.steampowered.com/api/featuredcategories?cc=fi";
template = ''
<ul class="list list-gap-10 collapsible-container" data-collapse-after="5">
{{ range .JSON.Array "specials.items" }}
<li>
<a class="size-h4 color-highlight block text-truncate" href="https://store.steampowered.com/app/{{ .Int "id" }}/">{{ .String "name" }}</a>
<ul class="list-horizontal-text">
<li>{{ div (.Int "final_price" | toFloat) 100 | printf "$%.2f" }}</li>
{{ $discount := .Int "discount_percent" }}
<li{{ if ge $discount 40 }} class="color-positive"{{ end }}>{{ $discount }}% off</li>
</ul>
</li>
{{ end }}
</ul>
'';
}
];
}
{
size = "full";
widgets = [
{
type = "group";
widgets = [
{ type = "hacker-news"; }
{ type = "lobsters"; }
];
}
{
type = "videos";
channels = [
"UCXuqSBlHAE6Xw-yeJA0Tunw" # Linus Tech Tips
"UCshObcm-nLhbu8MY50EZ5Ng" # Benn Jordan
];
}
{
type = "group";
widgets =
let
type = "reddit";
show-thumbnails = true;
app-auth = {
name = "Glance";
id = {
_secret = secrets."glance/reddit/app-id".path;
};
secret = {
_secret = secrets."glance/reddit/app-secret".path;
};
};
in
[
{
inherit type app-auth show-thumbnails;
subreddit = "nixos";
}
{
inherit type app-auth show-thumbnails;
subreddit = "technology";
}
{
inherit type app-auth show-thumbnails;
subreddit = "selfhosted";
}
];
}
];
}
{
size = "small";
widgets = [
{
type = "server-stats";
servers = [
{
type = "local";
name = "freun.dev";
}
];
}
{
type = "group";
title = "Services";
widgets =
let
buildSite =
domain:
{
subdomain,
title,
alt-status-codes ? [ ],
}:
{
inherit title alt-status-codes;
url = "https://${subdomain}.${domain}";
};
buildMonitor =
{ domain, sites }:
{
type = "monitor";
cache = "1m";
title = domain;
style = "compact";
sites = builtins.map (buildSite domain) sites;
};
in
builtins.map buildMonitor [
{
domain = "freun.dev";
sites = [
{
subdomain = "img";
title = "Immich";
}
{
subdomain = "pw";
title = "Vaultwarden";
}
{
subdomain = "social";
title = "Gotosocial";
}
{
subdomain = "graph";
title = "Grafana";
}
{
subdomain = "home";
title = "Home Assistant";
}
{
subdomain = "cook";
title = "Mealie";
}
{
subdomain = "sync";
title = "Syncthing";
}
{
subdomain = "bin";
title = "Hastebin";
alt-status-codes = [ 404 ];
}
{
subdomain = "ledger";
title = "Ledger";
}
{
subdomain = "note";
title = "Dnote";
}
{
subdomain = "trackmap";
title = "Trackmap";
}
{
subdomain = "fit";
title = "Workout Tracker";
}
{
subdomain = "stream";
title = "Owncast";
}
{
subdomain = "read";
title = "Readeck";
}
];
}
{
domain = "alderaan.space";
sites = [
{
subdomain = "jelly";
title = "Jellyfin";
}
{
subdomain = "radarr";
title = "Radarr";
}
{
subdomain = "sonarr";
title = "Sonarr";
}
{
subdomain = "deluge";
title = "qBittorrent";
}
{
subdomain = "lidarr";
title = "Lidarr";
}
{
subdomain = "bazarr";
title = "Bazarr";
}
{
subdomain = "req";
title = "Jellyseer";
}
];
}
];
}
{
type = "dns-stats";
service = "adguard";
url = "http://localhost:3006";
}
];
}
];
}
];
};
};
}

View File

@@ -1,33 +1,41 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
lib,
modulesPath,
...
}:
{
imports =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ];
boot.initrd.availableKernelModules = [
"xhci_pci"
"virtio_scsi"
"sr_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/8456c7de-2116-4cbe-8deb-76cafbd3e6dd";
fsType = "btrfs";
options = [ "subvol=@" ];
};
fileSystems."/" = {
device = "/dev/disk/by-uuid/f594ffbc-4553-42e4-8206-4d762c94b4c3";
fsType = "ext4";
};
fileSystems."/var" =
{ device = "/dev/disk/by-uuid/8456c7de-2116-4cbe-8deb-76cafbd3e6dd";
fsType = "btrfs";
options = [ "subvol=@var" ];
};
swapDevices =
[ { device = "/dev/disk/by-uuid/614a1b7f-04aa-478c-9011-8b81f133da98"; }
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/46F1-18E3";
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
swapDevices = [ { device = "/dev/disk/by-uuid/d9955575-d4e0-4a49-a3c5-41f54110d12b"; } ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
@@ -36,5 +44,5 @@
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
}

View File

@@ -0,0 +1,59 @@
{ lib, config, ... }:
{
age.secrets =
lib.listToAttrs (
map
(secret: {
name = secret;
value = {
file = ../../secrets/${secret}.age;
};
})
[
"gotosocial"
"immich"
"readeck"
"storage-box-credentials"
"vaultwarden"
"donetick"
"dnote"
"mealie"
"mosquitto/homie"
"mosquitto/telegraf"
"mosquitto/openhab"
"mosquitto/shelly"
"mosquitto/mokkimaatti"
"gitlab-runner/default"
"gitlab-runner/docker"
"glance/reddit/app-id"
"glance/reddit/app-secret"
"hetzner"
"actual"
"voidauth"
"gitea"
"gitea-actions-runner"
]
)
// {
smtp-password = {
file = ../../secrets/smtp-password.age;
owner =
if (config.services.grafana.enable) then
config.systemd.services.grafana.serviceConfig.User
else
"root";
};
hastebin-tokens = {
file = ../../secrets/hastebin-tokens.age;
owner = if (config.services.hastebin.enable) then config.users.users.hastebin.name else "root";
};
hledger-basic-auth = {
file = ../../secrets/hledger-basic-auth.age;
owner = if (config.services.hledger-web.enable) then "nginx" else "root";
};
invidious = lib.mkIf config.services.invidious.enable {
file = ../../secrets/invidious.age;
owner = config.systemd.services.invidious.serviceConfig.User;
};
};
}

View File

@@ -1,63 +1,397 @@
{ pkgs, ... }:
rec {
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.caddy = {
enable = true;
enableReload = true;
email = "admin@pimeys.pm";
{
pkgs,
pkgs-unstable,
config,
inputs,
lib,
...
}:
let
immichDataDir = "/mnt/storage/immich";
syncthingDataDir = "/mnt/storage/syncthing";
smtp = {
host = "horologium.uberspace.de";
port = 587;
username = "noreply@${config.networking.domain}";
from = "noreply@${config.networking.domain}";
heloName = config.networking.domain;
};
secrets = config.age.secrets;
in
{
imports = [
./glance.nix
];
virtualisation.podman.enable = true;
virtualisation.oci-containers.backend = "podman";
security.acme.defaults.environmentFile = secrets.hetzner.path;
services.postgresql.enable = true;
modules.storageBoxMounts = {
${immichDataDir} = {
path = "/backup/immich";
user = "u407959";
uid = config.users.users.${config.services.immich.user}.uid;
gid = config.users.groups.${config.services.immich.user}.gid;
};
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
defaultNetwork.settings = {
# Required for container networking to be able to use names.
dns_enabled = true;
${syncthingDataDir} = {
path = "/backup/syncthing";
user = "u407959";
uid = config.users.users.${config.services.syncthing.user}.uid;
gid = config.users.groups.${config.services.syncthing.user}.gid;
};
};
virtualisation.oci-containers.backend = "podman";
services = {
postgresql.package = pkgs.postgresql_16;
octodns = {
enable = true;
records."".MX = {
ttl = 86400;
values = [ { exchange = "${smtp.host}."; } ];
};
defaults.CNAME.ttl = 60;
};
networking.firewall = {
trustedInterfaces = [ "podman1" ];
interfaces.podman1.allowedUDPPorts = [ 53 ];
hastebin = {
enable = true;
subdomain = "bin";
renderers =
with pkgs;
let
hl = rustPlatform.buildRustPackage {
name = "syntax-renderer";
src = inputs.syntax-renderer;
cargoHash = "sha256-kZy+HVPcUfPDTBQZ8TQ/xlEEqmSIttzdGeRwX9EF4xU=";
};
in
{
hl = "${hl}/bin/syntax-renderer";
sha = writeShellScript "sha-renderer" ''
echo "Content-Type: text/plain"
echo "---"
${coreutils}/bin/sha256sum - | cut -d' ' -f1
'';
stl = writeShellScript "stl-renderer" ''
echo "Content-Type: model/stl"
echo "Content-Disposition: attachment; filename=$(basename "$1" .scad).stl"
echo "---"
${openscad}/bin/openscad --export-format stl -o - -
'';
};
cleanup.enable = true;
};
readeck = {
enable = true;
subdomain = "read";
settings.email = {
host = smtp.host;
port = smtp.port;
username = smtp.username;
encryption = "starttls";
from = smtp.from;
from_noreply = smtp.from;
};
};
donetick = {
enable = true;
subdomain = "do";
settings = {
email = {
host = smtp.host;
port = smtp.port;
email = smtp.username;
};
};
};
grafana = {
enable = true;
subdomain = "graph";
settings = {
server.http_port = 3005;
smtp = {
enabled = true;
host = smtp.host;
port = smtp.port;
user = smtp.username;
from_address = smtp.from;
};
};
};
owncast = {
enable = true;
subdomain = "stream";
};
gtrackmap = {
enable = false;
subdomain = "trackmap";
port = 3001;
};
invidious = {
enable = true;
subdomain = "vid";
extraSettingsFile = secrets.invidious.path;
settings = {
invidious_companion = [
{ private_url = "http://apu:8282/companion"; }
];
};
};
syncthing = {
enable = true;
subdomain = "sync";
dataDir = syncthingDataDir;
configDir = "/var/lib/syncthing";
};
tailscale.enable = true;
gotosocial = {
enable = true;
subdomain = "social";
settings = {
port = 3002;
smtp-host = smtp.host;
smtp-port = smtp.port;
smtp-username = smtp.username;
smtp-from = smtp.from;
instance-languages = [
"de"
"fi"
"en"
];
};
};
vaultwarden = {
enable = true;
subdomain = "pw";
config = {
YUBICO_CLIENT_ID = 86799;
SMTP_HOST = smtp.host;
SMTP_FROM = smtp.from;
SMTP_FROM_NAME = "Vaultwarden";
SMTP_USERNAME = smtp.username;
SMTP_PORT = smtp.port;
HELO_NAME = smtp.heloName;
};
};
workout-tracker = {
enable = true;
subdomain = "fit";
port = 3004;
};
workout-sync = {
enable = true;
subdomain = "ws";
};
immich = {
enable = true;
subdomain = "img";
mediaLocation = immichDataDir;
timezone = "Europe/Helsinki";
settings.job = {
thumbnailGeneration.concurrency = 8;
videoConversion.concurrency = 2;
};
};
dnote = {
enable = true;
subdomain = "note";
environment = {
SmtpHost = smtp.host;
SmtpPort = smtp.port;
SmtpUsername = smtp.username;
};
environmentFile = secrets.dnote.path;
};
tailscaledAdguardhome = {
enable = true;
subdomain = "dns";
port = 3006;
};
mealie = {
enable = true;
package = pkgs-unstable.mealie;
subdomain = "cook";
credentialsFile = secrets.mealie.path;
settings = {
SMTP_HOST = smtp.host;
SMTP_FROM_EMAIL = smtp.from;
SMTP_USER = smtp.username;
SMTP_PORT = smtp.port;
};
};
uptime-kuma = {
enable = true;
subdomain = "status";
settings = {
PORT = "3007";
};
};
mosquitto = {
enable = true;
listeners = [
{
users = {
homie = {
acl = [
"readwrite homie/#"
];
hashedPasswordFile = secrets."mosquitto/homie".path;
};
telegraf = {
acl = [
"read openhab/#"
"read homie/#"
"read shellies/#"
"read mokkimaatti/#"
];
hashedPasswordFile = secrets."mosquitto/telegraf".path;
};
openhab = {
acl = [
"readwrite openhab/#"
];
hashedPasswordFile = secrets."mosquitto/openhab".path;
};
shelly = {
acl = [
"readwrite shellies/#"
];
hashedPasswordFile = secrets."mosquitto/shelly".path;
};
mokkimaatti = {
acl = [
"readwrite mokkimaatti/#"
];
hashedPasswordFile = secrets."mosquitto/mokkimaatti".path;
};
};
}
];
openFirewall = true;
};
gitlab-runner = {
enable = true;
services = {
default = {
dockerImage = "alpine";
authenticationTokenConfigFile = secrets."gitlab-runner/default".path;
};
docker = {
dockerImage = "docker:stable";
dockerVolumes = [ "/var/run/docker.sock:/var/run/docker.sock" ];
authenticationTokenConfigFile = secrets."gitlab-runner/docker".path;
};
};
};
weechat = {
enable = true;
subdomain = "irc";
};
sillytavern = {
enable = true;
subdomain = "st";
listen = true;
whitelist = false;
port = 3100;
};
nginx.virtualHosts."isarepomaa.com" = {
forceSSL = true;
enableACME = true;
http2 = true;
locations."/".root = "/var/www/isas-portfolio";
};
hledger-web = {
enable = true;
subdomain = "ledger";
stateDir = "${syncthingDataDir}/ledger";
user = config.systemd.services.syncthing.serviceConfig.User;
group = config.systemd.services.syncthing.serviceConfig.Group;
extraOptions = [ "--forecast" ];
journalFiles = [
"main.ldg"
];
};
actual = {
enable = true;
package = pkgs-unstable.actual-server;
subdomain = "actual";
environmentFile = secrets.actual.path;
settings = {
port = 3200;
openId = {
discoveryURL = "https://${config.services.voidauth.subdomain}.${config.networking.domain}/oidc/.well-known/openid-configuration";
server_hostname = "https://${config.services.actual.subdomain}.${config.networking.domain}";
};
loginMethod = "openid";
allowedLoginMethods = [ "openid" ];
enforceOpenId = true;
userCreationMode = "login";
};
};
voidauth = {
enable = true;
subdomain = "auth";
environmentFile = secrets.voidauth.path;
settings = {
APP_PORT = 3300;
SMTP_HOST = smtp.host;
SMTP_FROM = smtp.from;
SMTP_PORT = smtp.port;
SMTP_USER = smtp.username;
};
};
gitea = {
enable = true;
subdomain = "git";
secrets = secrets.gitea.path;
};
gitea-actions-runner.instances = {
default = {
enable = true;
name = config.networking.domain;
labels = [
"linux_arm64"
"ubuntu-latest:docker://node:latest"
"nixos-latest:docker://git.freun.dev/repomaa/nixos/node:latest"
];
tokenFile = secrets.gitea-actions-runner.path;
url = "https://${config.services.gitea.subdomain}.${config.networking.domain}";
settings.container.network = "bridge";
};
};
webserver = {
acme.dnsChallenge = true;
tailscaleAuth.expectedTailnet = "tempel-vibes.ts.net";
};
};
imports = [
../../modules/services/vaultwarden.nix
../../modules/services/immich.nix
../../modules/services/syncthing.nix
../../modules/services/invidious.nix
../../modules/services/grafana.nix
../../modules/services/gtrackmap.nix
../../modules/services/owncast.nix
../../modules/services/hydra.nix
../../modules/services/wireguard.nix
];
services.immich = {
enable = true;
fqdn = "img.freun.dev";
data_dir = fileSystems.immich_data.mountPoint;
secrets = "/var/secrets/immich";
};
fileSystems.immich_data = {
mountPoint = "/mnt/storage/immich";
device = "//u407959.your-storagebox.de/backup/immich";
fsType = "cifs";
options =
let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
in
[ "${automount_opts},credentials=/var/secrets/smb-storage" ];
};
environment.systemPackages = [ pkgs.cifs-utils ];
virtualisation.docker.autoPrune.enable = true;
}

31
hosts/radish/boot.nix Normal file
View File

@@ -0,0 +1,31 @@
{ lib, pkgs, ... }:
{
boot = {
loader.systemd-boot.enable = lib.mkForce false;
lanzaboote = {
enable = true;
pkiBundle = "/etc/secureboot";
configurationLimit = 10;
settings = {
editor = false;
};
};
loader.efi.canTouchEfiVariables = true;
bootspec.enable = true;
kernelPackages = pkgs.linuxPackages_latest;
kernelParams = [
"amdgpu.sg_display=0"
"resume_offset=533760"
];
resumeDevice = "/dev/disk/by-uuid/a331b669-f5c5-42f7-be58-434873c1b689";
tmp.useTmpfs = true;
kernel.sysctl = {
"vm.max_map_count" = 262144;
};
};
environment.systemPackages = with pkgs; [
efibootmgr
];
}

View File

@@ -0,0 +1,55 @@
{ ... }:
{
nix = {
distributedBuilds = true;
buildMachines = [
{
hostName = "freun.dev";
maxJobs = 8;
protocol = "ssh";
sshUser = "builder";
systems = [
"aarch64-linux"
];
supportedFeatures = [
"kvm"
"big-parallel"
"nixos-test"
];
}
];
settings = {
experimental-features = [
"nix-command"
"flakes"
];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "daily";
options = "--delete-older-than 7d";
};
};
networking.nftables.enable = true;
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "23.11"; # Did you read the comment?
}

View File

@@ -0,0 +1,27 @@
{ pkgs-unstable, ... }:
{
virtualisation = {
containers = {
enable = true;
storage.settings.storage.driver = "btrfs";
};
oci-containers.backend = "podman";
podman = {
enable = true;
autoPrune.enable = true;
defaultNetwork.settings.dns_enabled = true;
package = pkgs-unstable.podman;
};
docker = {
storageDriver = "btrfs";
enable = true;
autoPrune.enable = true;
};
};
users.users.jokke.extraGroups = [ "docker" ];
users.users.moco.extraGroups = [ "docker" ];
}

26
hosts/radish/default.nix Normal file
View File

@@ -0,0 +1,26 @@
{ inputs, ... }:
let
inherit (inputs)
lanzaboote
nixos-hardware
home-manager
;
in
{
imports = [
./hardware-configuration.nix
./configuration.nix
./host.nix
./boot.nix
./hardware.nix
./packages.nix
./containers.nix
./desktop.nix
./users.nix
./secrets.nix
lanzaboote.nixosModules.lanzaboote
nixos-hardware.nixosModules.framework-13-7040-amd
home-manager.nixosModules.home-manager
../../home
];
}

38
hosts/radish/desktop.nix Normal file
View File

@@ -0,0 +1,38 @@
{ ... }:
{
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
programs.niri.enable = true;
services.printing.enable = true;
services.keyd = {
enable = true;
keyboards.default = {
ids = [ "*" ];
settings = {
global = {
overload_tap_timeout = 200;
};
main = {
leftmeta = "overload(meta, macro(leftmeta+z))";
};
};
};
};
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
};
services.pulseaudio.enable = false;
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
dedicatedServer.openFirewall = true;
};
}

View File

@@ -0,0 +1,77 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"nvme"
"xhci_pci"
"thunderbolt"
"usb_storage"
"sd_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/a331b669-f5c5-42f7-be58-434873c1b689";
fsType = "btrfs";
options = [ "subvol=@" ];
};
boot.initrd.luks.devices."cryptroot" = {
device = "/dev/disk/by-uuid/43895585-8899-4e94-a413-889127c214f8";
allowDiscards = true;
};
fileSystems."/var/log" = {
device = "/dev/disk/by-uuid/a331b669-f5c5-42f7-be58-434873c1b689";
fsType = "btrfs";
options = [ "subvol=@var_log" ];
};
fileSystems."/home" = {
device = "/dev/disk/by-uuid/a331b669-f5c5-42f7-be58-434873c1b689";
fsType = "btrfs";
options = [ "subvol=@home" ];
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/01E6-6258";
fsType = "vfat";
};
fileSystems."/swap" = {
device = "/dev/disk/by-uuid/a331b669-f5c5-42f7-be58-434873c1b689";
fsType = "btrfs";
options = [ "subvol=@swap" ];
};
swapDevices = [
{
device = "/swap/swapfile";
size = 64 * 1024;
}
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

89
hosts/radish/hardware.nix Normal file
View File

@@ -0,0 +1,89 @@
{
pkgs,
pkgs-unstable,
inputs,
lib,
config,
...
}:
{
hardware.bluetooth.enable = true;
hardware.bluetooth.powerOnBoot = true;
services.udev.packages = [
pkgs.zsa-udev-rules
pkgs-unstable.yubikey-personalization
inputs.ksoloti-pr.legacyPackages.${pkgs.stdenv.hostPlatform.system}.ksoloti
];
services.usbmuxd = {
enable = true;
package = pkgs.usbmuxd;
};
services.fwupd = {
enable = true;
};
services.fprintd.enable = true;
services.fstrim.enable = true;
security.pam.services.login.fprintAuth = false;
# similarly to how other distributions handle the fingerprinting login
security.pam.services.gdm-fingerprint = lib.mkIf (config.services.fprintd.enable) {
text = ''
auth required pam_shells.so
auth requisite pam_nologin.so
auth requisite pam_faillock.so preauth
auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so
auth optional pam_permit.so
auth required pam_env.so
auth [success=ok default=1] ${pkgs.gdm}/lib/security/pam_gdm.so
auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so
account include login
password required pam_deny.so
session include login
session optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start
'';
};
hardware.keyboard.zsa.enable = true;
services.logind = {
settings.Login = {
IdleAction = "suspend";
HandleLidSwitch = "suspend";
HandlePowerKey = "suspend";
};
};
powerManagement = {
enable = true;
powerDownCommands = "${pkgs.networkmanager}/bin/nmcli radio wifi off";
powerUpCommands = "${pkgs.networkmanager}/bin/nmcli radio wifi on";
};
services.power-profiles-daemon.enable = true;
hardware.amdgpu.opencl.enable = true;
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [ rocmPackages.clr.icd ];
};
networking.networkmanager = {
enable = true;
wifi.backend = "iwd";
plugins = with pkgs; [
networkmanager-openvpn
];
};
security.tpm2.enable = true;
services.nqptp.enable = true;
networking.firewall.allowedTCPPortRanges = [
{
from = 7000;
to = 7010;
}
];
networking.firewall.allowedUDPPorts = [ 5353 ];
}

17
hosts/radish/host.nix Normal file
View File

@@ -0,0 +1,17 @@
{ lib, ... }:
{
networking.hostName = "radish";
time.timeZone = lib.mkForce null; # allow TZ to be set by desktop user
i18n.defaultLocale = "de_DE.UTF-8";
i18n.supportedLocales = map (locale: "${locale}.UTF-8/UTF-8") [
"C"
"en_US"
"de_DE"
"fi_FI"
];
i18n.extraLocaleSettings.LANG = "en_US.UTF-8";
console = {
font = "Lat2-Terminus16";
keyMap = "us";
};
}

171
hosts/radish/packages.nix Normal file
View File

@@ -0,0 +1,171 @@
{
pkgs,
pkgs-unstable,
lib,
inputs,
config,
...
}:
let
secrets = config.age.secrets;
in
{
nixpkgs.config.allowUnfree = true;
nixpkgs.overlays = [ (import ../../custom-pkgs { inherit lib inputs pkgs-unstable; }) ];
environment = {
systemPackages = with pkgs; [
vim
wget
curl
htop
tmux
libimobiledevice
ripgrep
fd
];
pathsToLink = [ "/share/zsh" ];
};
fonts.packages = with pkgs-unstable; [
nerd-fonts.iosevka
nerd-fonts.iosevka-term
];
services = {
pcscd = {
enable = true;
plugins = [ pkgs.ccid ];
};
openssh = {
enable = true;
openFirewall = false;
};
tailscale = {
enable = true;
useRoutingFeatures = "client";
package = pkgs-unstable.tailscale;
};
ollama = {
enable = true;
package = pkgs-unstable.ollama-vulkan;
syncModels = true;
loadModels = [
"qwen3:4b-instruct"
"qwen3:8b"
];
};
borgbackup.jobs.root = {
paths = "/";
exclude = [
"/nix"
"/var/cache"
"/run"
"/sys"
"/etc"
"/swap"
"/proc"
"**/node_modules"
"**/.cargo"
"**/ruby/*/gems"
"**/.cache"
"**/.meteor"
"**/.next"
"**/.local/share/containers/cache"
"**/.local/share/containers/storage/overlay"
"**/.local/share/docker/overlay2"
"**/log/*.log"
"**/.local/share/Trash"
];
environment = {
BORG_RSH = "ssh -i /root/.ssh/id_ed25519.borg";
};
repo = "ssh://u324815-sub2@u324815.your-storagebox.de:23/./backup";
encryption = {
mode = "repokey";
passphrase = "will be overridden from environment file";
};
extraCreateArgs = [
"--stats"
"--progress"
];
inhibitsSleep = true;
compression = "auto,zstd";
startAt = "daily";
persistentTimer = true;
preHook = with pkgs; ''
${coreutils}/bin/timeout 60 ${bash}/bin/sh -c '
until ${iputils}/bin/ping -c1 your-storagebox.de; do
sleep 1
done
'
'';
postCreate = with pkgs; ''
${curl}/bin/curl "https://status.freun.dev/api/push/''${UPTIME_KUMA_TOKEN}?status=up&msg=OK&ping="
'';
prune.keep = {
within = "3d";
daily = 14;
weekly = 8;
monthly = -1;
};
};
locate = {
enable = true;
package = pkgs.plocate;
};
protonmail-bridge = {
enable = true;
package = pkgs-unstable.protonmail-bridge;
path = [ pkgs.gnome-keyring ];
};
};
systemd.services.borgbackup-job-root.serviceConfig.EnvironmentFile = secrets.borgbackup-radish.path;
programs = {
zsh.enable = true;
_1password-gui = {
enable = true;
polkitPolicyOwners = [ "moco" ];
};
_1password.enable = true;
};
environment.etc."1password/custom_allowed_browsers".text = ''
vivaldi
'';
systemd.services.ollama-keep-alive =
let
ollamaURL = "http://localhost:${toString config.services.ollama.port}/api/generate";
payload = {
model = lib.elemAt config.services.ollama.loadModels 0;
keep_alive = -1;
};
in
{
enable = true;
description = "Keep Ollama primary model loaded by pinging it";
after = [
"ollama.service"
"network-online.target"
];
wants = [ "network-online.target" ];
bindsTo = [ "ollama.service" ];
wantedBy = [
"multi-user.target"
"ollama.service"
];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.curl}/bin/curl -s '${ollamaURL}' -d '${builtins.toJSON payload}'";
};
};
}

15
hosts/radish/secrets.nix Normal file
View File

@@ -0,0 +1,15 @@
{ lib, ... }:
{
age.secrets = lib.listToAttrs (
map
(secret: {
name = secret;
value = {
file = ../../secrets/${secret}.age;
};
})
[
"borgbackup-radish"
]
);
}

31
hosts/radish/users.nix Normal file
View File

@@ -0,0 +1,31 @@
{ pkgs, ... }:
{
# Define a user account. Don't forget to set a password with passwd.
users.users.jokke = {
uid = 1001;
isNormalUser = true;
extraGroups = [ "wheel" ];
shell = pkgs.zsh;
initialPassword = "changeme";
};
users.users.moco = {
uid = 1000;
isNormalUser = true;
extraGroups = [ "wheel" ];
shell = pkgs.zsh;
initialPassword = "changeme";
subUidRanges = [
{
startUid = 10000;
count = 65536;
}
];
subGidRanges = [
{
startGid = 10000;
count = 65536;
}
];
};
}

View File

@@ -0,0 +1,98 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running `nixos-help`).
{
pkgs,
ssh,
config,
inputs,
...
}:
let
secrets = config.age.secrets;
in
{
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
auto-optimise-store = true;
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
networking = {
hostName = "turny"; # Define your hostname.
useDHCP = true;
useNetworkd = true;
nftables.enable = true;
wireless = {
enable = true;
networks = {
KotiWLANi.pskRaw = "ext:psk_kotiwlani";
};
secretsFile = secrets.wpa_supplicant.path;
};
};
services.avahi = {
publish.enable = true;
};
services.tailscale = {
enable = true;
useRoutingFeatures = "client";
};
# Set your time zone.
time.timeZone = "Europe/Helsinki";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Define a user account. Don't forget to set a password with passwd.
users.users.jokke = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = [ pkgs.nh ];
openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
initialPassword = "changeme";
};
users.users.root.openssh.authorizedKeys.keys = [ ssh.publicKeys.yubikey ];
nix.settings.trusted-users = [ "jokke" ];
environment.systemPackages = with pkgs; [
vim
wget
htop
git
inputs.turny.packages.${stdenv.hostPlatform.system}.default
];
# Enable the OpenSSH daemon.
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
modules.firewall = {
enable = true;
allInterfaces = [ "ssh" ];
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It's perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
}

10
hosts/turny/default.nix Normal file
View File

@@ -0,0 +1,10 @@
{ inputs, ... }:
{
imports = [
./hardware-configuration.nix
./configuration.nix
./secrets.nix
inputs.nixos-hardware.nixosModules.raspberry-pi-3
"${inputs.nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix"
];
}

View File

@@ -0,0 +1,4 @@
{ lib, ... }:
{
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
}

15
hosts/turny/secrets.nix Normal file
View File

@@ -0,0 +1,15 @@
{ lib, ... }:
{
age.secrets = lib.listToAttrs (
map
(secret: {
name = secret;
value = {
file = ../../secrets/${secret}.age;
};
})
[
"wpa_supplicant"
]
);
}

View File

@@ -1,202 +0,0 @@
{ pkgs, lib, ... }:
let
immich_version = "release";
storage_dir = "/mnt/storage/syncthing";
immich_data_dir = "/mnt/storage/immich";
volumeServices = names: (
lib.lists.foldl (services: name:
services // {
"podman-volume-immich_${name}" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect immich_${name} || podman volume create immich_${name}
'';
partOf = [ "podman-compose-immich-root.target" ];
wantedBy = [ "podman-compose-immich-root.target" ];
};
}
) {} names
);
containerServices = services: (
lib.lists.foldl (acc: { name, volumes ? [], ... }:
let
volume_services = map (volume: "podman-volume-immich_${volume}.service") volumes;
dependent_services = [ "podman-network-immich_default.service" ] ++ volume_services;
in
acc // {
"podman-immich_${name}" = {
serviceConfig = {
Restart = lib.mkOverride 500 "always";
};
after = dependent_services;
requires = dependent_services;
partOf = [
"podman-compose-immich-root.target"
];
wantedBy = [
"podman-compose-immich-root.target"
];
};
}
)
{} services
);
in {
# Containers
virtualisation.oci-containers.containers = {
"immich_machine_learning" = {
image = "ghcr.io/immich-app/immich-machine-learning:${immich_version}";
environmentFiles = [
"/var/secrets/immich.env"
];
volumes = [
"immich_model_cache:/cache:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-machine-learning"
"--network=immich_default"
];
};
"immich_microservices" = {
image = "ghcr.io/immich-app/immich-server:${immich_version}";
environmentFiles = [
"/var/secrets/immich.env"
];
volumes = [
"/etc/localtime:/etc/localtime:ro"
"${immich_data_dir}:/usr/src/app/upload:rw"
"${storage_dir}:${storage_dir}:ro"
];
cmd = [ "start.sh" "microservices" ];
dependsOn = [
"immich_postgres"
"immich_redis"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-microservices"
"--network=immich_default"
];
};
"immich_postgres" = {
image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0";
environmentFiles = [
"/var/secrets/immich.env"
];
environment = {
POSTGRES_INITDB_ARGS = "--data-checksums";
};
volumes = [
"immich_db_data:/var/lib/postgresql/data:rw"
];
cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ];
log-driver = "journald";
extraOptions = [
"--network-alias=database"
"--network=immich_default"
];
};
"immich_redis" = {
image = "registry.hub.docker.com/library/redis:6.2-alpine";
environmentFiles = [
"/var/secrets/immich.env"
];
log-driver = "journald";
extraOptions = [
"--network-alias=redis"
"--network=immich_default"
];
};
"immich_server" = {
image = "ghcr.io/immich-app/immich-server:${immich_version}";
environmentFiles = [
"/var/secrets/immich.env"
];
volumes = [
"/etc/localtime:/etc/localtime:ro"
"${immich_data_dir}:/usr/src/app/upload:rw"
"${storage_dir}:${storage_dir}:ro"
];
ports = [
"2283:3001/tcp"
];
cmd = [ "start.sh" "immich" ];
dependsOn = [
"immich_postgres"
"immich_redis"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-server"
"--network=immich_default"
];
};
};
systemd.services = (containerServices [
{ name = "machine_learning"; volumes = [ "model_cache" ]; }
{ name = "postgres"; volumes = [ "db_data" ]; }
{ name = "redis"; }
{ name = "server"; }
{ name = "microservices"; }
]) // {
# Networks
"podman-network-immich_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "${pkgs.podman}/bin/podman network rm -f immich_default";
};
script = ''
podman network inspect immich_default || podman network create immich_default
'';
partOf = [ "podman-compose-immich-root.target" ];
wantedBy = [ "podman-compose-immich-root.target" ];
};
} // (volumeServices [
"db_data"
"model_cache"
]);
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-immich-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
services.caddy.virtualHosts = {
"img.freun.dev".extraConfig = ''
reverse_proxy localhost:2283
'';
};
fileSystems."${immich_data_dir}" = {
device = "//u407959.your-storagebox.de/backup/immich";
fsType = "cifs";
options = let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
in ["${automount_opts},credentials=/var/secrets/smb-storage"];
};
environment.systemPackages = [ pkgs.cifs-utils ];
}

17
modules/default.nix Normal file
View File

@@ -0,0 +1,17 @@
{ inputs, config, ... }:
{
_module.args = {
pkgs-unstable = import inputs.nixpkgs-unstable {
system = config.nixpkgs.hostPlatform.system;
config = config.nixpkgs.config;
};
};
imports = [
./vlans.nix
./firewall.nix
./storage-box-mounts.nix
./services
inputs.agenix.nixosModules.default
];
}

69
modules/firewall.nix Normal file
View File

@@ -0,0 +1,69 @@
{ lib, config, ... }:
let
services = {
ssh = {
tcp = [ 22 ];
};
dhcp = {
udp = [
67
68
];
};
dns = {
udp = [
53
853
];
tcp = [
53
853
];
};
web = {
tcp = [
80
443
];
};
};
rulesForServices =
enabledServices:
lib.foldr
(
service:
{ allowedUDPPorts, allowedTCPPorts }:
{
allowedUDPPorts = allowedUDPPorts ++ services.${service}.udp or [ ];
allowedTCPPorts = allowedTCPPorts ++ services.${service}.tcp or [ ];
}
)
{
allowedUDPPorts = [ ];
allowedTCPPorts = [ ];
}
enabledServices;
cfg = config.modules.firewall;
in
{
options.modules.firewall = {
enable = lib.mkEnableOption "Enable the firewall";
interfaces = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf (lib.types.enum (lib.attrNames services)));
default = { };
};
allInterfaces = lib.mkOption {
type = lib.types.listOf (lib.types.enum (lib.attrNames services));
default = [ ];
};
};
config = lib.mkIf cfg.enable {
networking.firewall = {
enable = true;
interfaces = lib.mapAttrs (_: enabledServices: rulesForServices enabledServices) cfg.interfaces;
} // rulesForServices cfg.allInterfaces;
};
}

View File

@@ -0,0 +1,32 @@
{
lib,
config,
...
}:
let
cfg = config.services.actual;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.actual = {
subdomain = lib.mkOption {
type = lib.types.str;
};
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
};
};
config = lib.mkIf cfg.enable {
services = {
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = cfg.settings.port;
};
};
systemd.services.actual = {
serviceConfig.EnvironmentFile = cfg.environmentFile;
};
};
}

View File

@@ -0,0 +1,63 @@
{ config, lib, ... }:
let
cfg = config.services.tailscaledAdguardhome;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
tailscaleIps = [
"100.84.105.63"
"fd7a:115c:a1e0::7901:693f"
];
acme = config.security.acme;
in
{
imports = [
(lib.mkAliasOptionModule
[ "services" "tailscaledAdguardhome" "settings" ]
[ "services" "adguardhome" "settings" ]
)
(lib.mkAliasOptionModule
[ "services" "tailscaledAdguardhome" "port" ]
[ "services" "adguardhome" "port" ]
)
];
options.services.tailscaledAdguardhome = {
enable = lib.mkEnableOption "Enable tailscaled adguardhome";
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services.tailscale.enable = true;
modules.firewall.interfaces.${config.services.tailscale.interfaceName} = [ "dns" ];
systemd.services.adguardhome.serviceConfig.LoadCredential = [
"fullchain.pem:${acme.certs.${fqdn}.directory}/fullchain.pem"
"key.pem:${acme.certs.${fqdn}.directory}/key.pem"
];
services.adguardhome = {
enable = cfg.enable;
settings = {
tls = {
enabled = true;
server_name = fqdn;
port_https = 4443;
certificate_chain_path = "/run/credentials/adguardhome.service/fullchain.pem";
private_key_path = "/run/credentials/adguardhome.service/key.pem";
};
dns.bind_hosts = tailscaleIps;
};
};
systemd.services.adguardhome = {
requires = [ "tailscaled.service" ];
after = [ "tailscaled.service" ];
};
services.webserver.vHosts.${fqdn} = {
tailscaleAuth = true;
locations."/".proxyPort = cfg.port;
};
};
}

View File

@@ -0,0 +1,37 @@
{ ... }:
{
imports = [
./webserver.nix
./vaultwarden.nix
./immich.nix
./syncthing.nix
./invidious.nix
./grafana.nix
./gtrackmap.nix
./owncast.nix
./tailscale.nix
./workout-tracker.nix
./gotosocial.nix
./hastebin.nix
./workout-sync.nix
./readeck.nix
./donetick.nix
./dnote.nix
./octodns.nix
./adguardhome.nix
./mealie.nix
./uptime-kuma.nix
./mosquitto.nix
./home-assistant.nix
./weechat.nix
./hledger-web.nix
./glance.nix
./sillytavern.nix
./nqptp.nix
./actual.nix
./voidauth.nix
./gitea.nix
./dhcp-dns-sync
./invidious-companion.nix
];
}

View File

@@ -0,0 +1,125 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.modules.services.dhcp-dns-sync;
ownAddress = (
lib.elemAt (lib.splitString "/"
config.systemd.network.networks."30-${cfg.interface}".networkConfig.Address
) 0
);
dhcp-leases-to-unbound =
pkgs.runCommand "dhcp-leases-to-unbound"
{
code = ./dhcp-leases-to-unbound.cr;
nativeBuildInputs = [ pkgs.crystal ];
meta.mainProgram = "dhcp-leases-to-unbound";
}
''
mkdir -p $out/bin
crystal build $code --release -o $out/bin/dhcp-leases-to-unbound
'';
in
{
options.modules.services.dhcp-dns-sync = {
enable = lib.mkEnableOption "Enable DHCP to DNS synchronization";
interface = lib.mkOption {
type = lib.types.str;
default = "koti";
description = "Network interface to monitor for DHCP leases";
};
domain = lib.mkOption {
type = lib.types.str;
default = "home.arpa";
description = "Domain suffix for DHCP hostnames";
};
unboundConfigPath = lib.mkOption {
type = lib.types.str;
default = "/var/lib/unbound/dhcp-hosts.conf";
description = "Path to write Unbound include file";
};
interval = lib.mkOption {
type = lib.types.str;
default = "30s";
description = "Interval for checking DHCP lease updates";
};
};
config = lib.mkIf cfg.enable {
# Create user and group for the service
users.users.dhcp-dns-sync = {
isSystemUser = true;
group = "dhcp-dns-sync";
description = "DHCP DNS sync service user";
};
users.groups.dhcp-dns-sync = { };
# Ensure directories and files exist with proper permissions
# Directory needs to be group-writable for unbound group
systemd.tmpfiles.rules = [
"d /var/lib/unbound 0775 unbound unbound -"
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync unbound -"
];
# Extend Unbound configuration to include generated file
services.unbound.settings = {
server = {
local-zone = [ "${cfg.domain}. static" ];
include = cfg.unboundConfigPath;
local-data = [ ''"apu.home.arpa. IN A ${ownAddress}"'' ];
local-data-ptr = [ ''"${ownAddress} apu.home.arpa."'' ];
};
};
# Make sure Unbound control is enabled
services.unbound.settings.remote-control.control-enable = true;
# Systemd service
systemd.services.dhcp-dns-sync = {
description = "Sync DHCP leases to Unbound DNS";
after = [
"systemd-networkd.service"
"unbound.service"
];
requires = [ "unbound.service" ];
wants = [ "unbound-control.socket" ];
serviceConfig = {
Type = "oneshot";
User = "dhcp-dns-sync";
Group = "unbound";
# Allow access to networkctl via D-Bus
SupplementaryGroups = [ "systemd-network" ];
# Read/write paths
ReadWritePaths = [ "/var/lib/unbound" ];
ExecStart = lib.concatStringsSep " " [
(lib.getExe dhcp-leases-to-unbound)
"-i ${cfg.interface}"
"-d ${cfg.domain}"
"-o ${cfg.unboundConfigPath}"
"--networkctl ${lib.getExe' pkgs.systemd "networkctl"}"
"--unbound-control ${lib.getExe' pkgs.unbound "unbound-control"}"
];
};
};
# Systemd timer
systemd.timers.dhcp-dns-sync = {
description = "Periodic DHCP to DNS sync";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "10s";
OnUnitActiveSec = cfg.interval;
};
};
};
}

View File

@@ -0,0 +1,187 @@
#!/usr/bin/env crystal
require "json"
require "file_utils"
require "option_parser"
struct Lease
include JSON::Serializable
@[JSON::Field(key: "Address")]
property address_bytes : Array(Int32)
@[JSON::Field(key: "HardwareAddress")]
property hardware_address : Array(Int32)?
@[JSON::Field(key: "Hostname")]
property hostname : String?
@[JSON::Field(key: "ExpirationUSec")]
property expiration_usec : Int64?
def address : String
address_bytes.join('.')
end
end
struct DHCPServer
include JSON::Serializable
@[JSON::Field(key: "Leases")]
property leases : Array(Lease)?
end
struct NetworkStatus
include JSON::Serializable
@[JSON::Field(key: "DHCPServer")]
property dhcp_server : DHCPServer?
end
def sanitize_hostname(hostname : String) : String?
# Lowercase and strip invalid characters
sanitized = hostname.downcase.gsub(/[^a-z0-9-]/, "-")
# Collapse multiple dashes into one
sanitized = sanitized.gsub(/-+/, "-")
# Strip leading/trailing dashes
sanitized = sanitized.strip('-')
# Ensure non-empty and not too long (max 63 chars for DNS label)
return nil if sanitized.empty?
return nil if sanitized.size > 63
# Ensure starts with letter or number (not dash)
return nil if sanitized.starts_with?('-')
sanitized
end
def generate_unbound_config(leases : Array(Lease), domain : String) : String
lines = [] of String
lines << "# Auto-generated DHCP leases for #{domain}"
lines << "# Generated at #{Time.utc}"
lines << ""
leases.each do |lease|
next unless hostname = lease.hostname
sanitized = sanitize_hostname(hostname)
next unless sanitized
fqdn = "#{sanitized}.#{domain}."
# A record
lines << %{local-data: "#{fqdn} IN A #{lease.address}"}
# PTR record - local-data-ptr expects IP in normal form, unbound reverses it
lines << %{local-data-ptr: "#{lease.address} #{fqdn}"}
end
lines.join("\n") + "\n"
end
def get_leases(interface : String, networkctl_path : String? = nil) : Array(Lease)
cmd = networkctl_path ? "#{networkctl_path}" : "networkctl"
args = ["status", interface, "--json=short"]
Process.run(cmd, args, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
result = process.wait
output = process.output.to_s
unless result.success?
error = process.error.to_s
raise "networkctl failed (exit code #{result.exit_code}): #{error.empty? ? output : error}"
end
status = NetworkStatus.from_json(output)
status.dhcp_server.try(&.leases) || [] of Lease
end
end
def write_if_changed(content : String, path : String) : Bool
# Check if content is the same
if File.exists?(path)
current = File.read(path)
return false if current == content
end
# Write to temp file and atomically move
temp_path = "#{path}.tmp"
File.write(temp_path, content)
FileUtils.mv(temp_path, path)
true
end
# Configuration
interface = "koti"
domain = "home.arpa"
output_path = "/var/lib/unbound/dhcp-hosts.conf"
networkctl_path : String? = nil
unbound_control_path : String? = nil
OptionParser.parse do |parser|
parser.banner = "Usage: dhcp-leases-to-unbound [options]"
parser.on("-i INTERFACE", "Network interface to monitor (default: koti)") do |i|
interface = i
end
parser.on("-d DOMAIN", "Domain suffix (default: home.arpa)") do |d|
domain = d
end
parser.on("-o PATH", "Output path for unbound config (default: /var/lib/unbound/dhcp-hosts.conf)") do |o|
output_path = o
end
parser.on("--networkctl PATH", "Path to networkctl binary (default: networkctl from PATH)") do |path|
networkctl_path = path
end
parser.on("--unbound-control PATH", "Path to unbound-control binary (default: unbound-control from PATH)") do |path|
unbound_control_path = path
end
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end
def reload_unbound(unbound_control_path : String?)
cmd = unbound_control_path ? "#{unbound_control_path}" : "unbound-control"
puts "Reloading Unbound..."
Process.run(cmd, ["reload"], output: Process::Redirect::Pipe, error: Process::Redirect::Pipe) do |process|
result = process.wait
unless result.success?
raise "unbound reload failed (exit code #{result.exit_code}): #{process.error}"
end
end
puts "Unbound reloaded successfully."
end
begin
# Get leases from networkd
leases = get_leases(interface, networkctl_path)
# Generate Unbound config
config = generate_unbound_config(leases, domain)
# Write Unbound config if changed
changed = write_if_changed(config, output_path)
# Reload Unbound if config changed
if changed
reload_unbound(unbound_control_path)
else
puts "No DHCP lease changes detected."
end
rescue ex : Exception
STDERR.puts "Error: #{ex.message}"
exit 1
end

138
modules/services/dnote.nix Normal file
View File

@@ -0,0 +1,138 @@
{
lib,
config,
pkgs,
pkgs-unstable,
...
}:
let
cfg = config.services.dnote;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
types = {
environment = lib.types.submodule {
options = {
DBName = lib.mkOption {
type = lib.types.str;
default = "dnote";
};
SmtpHost = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
SmtpPort = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
};
SmtpUsername = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
SmtpPassword = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
};
};
};
immutableEnvironment = {
GO_ENV = "PRODUCTION";
OnPremises = true;
DBHost = "/var/run/postgresql";
DBPort = config.services.postgresql.settings.port;
DBUser = cfg.user;
WebURL = "https://${fqdn}";
};
serializeEnvVar =
value: if (lib.isBool value) then if value then "true" else "false" else toString value;
in
{
options.services.dnote = {
enable = lib.mkEnableOption "Enable dnote server";
subdomain = lib.mkOption {
type = lib.types.str;
};
user = lib.mkOption {
type = lib.types.str;
default = "dnote";
};
port = lib.mkOption {
type = lib.types.int;
default = 3040;
};
environment = lib.mkOption {
type = types.environment;
default = { };
};
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkIf cfg.enable {
users = {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.user;
};
groups.${cfg.user} = { };
};
systemd.services.dnote = {
enable = true;
description = "Dnote server";
environment = lib.mapAttrs (_: value: serializeEnvVar value) (
cfg.environment // immutableEnvironment
);
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
serviceConfig = {
ExecStart = "${pkgs-unstable.dnote}/bin/dnote-server -port ${toString cfg.port} start";
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
User = cfg.user;
BindPaths = [
"/var/run/postgresql"
];
BindReadOnlyPaths = [
/run/systemd/resolve/stub-resolv.conf
/etc/ssl
/etc/static/ssl
/etc/resolv.conf
/etc/static/resolv.conf
/etc/nsswitch.conf
/etc/static/nsswitch.conf
/etc/hosts
];
};
confinement = {
enable = true;
packages = [ pkgs.cacert ];
};
wantedBy = [ "multi-user.target" ];
};
services = {
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ cfg.environment.DBName ];
ensureUsers = [
{
name = cfg.user;
ensureDBOwnership = true;
}
];
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
proxyBuffering = false;
locations."/".proxyPort = cfg.port;
};
};
};
};
}

View File

@@ -0,0 +1,217 @@
{
lib,
pkgs,
config,
...
}:
let
types = {
jwtSettings = {
session_time = lib.mkOption {
type = lib.types.str;
default = "168h";
};
max_refresh = lib.mkOption {
type = lib.types.str;
default = "168h";
};
};
serverSettings = {
port = lib.mkOption {
type = lib.types.int;
default = 2021;
};
read_timeout = lib.mkOption {
type = lib.types.str;
default = "10s";
};
write_timeout = lib.mkOption {
type = lib.types.str;
default = "10s";
};
rate_period = lib.mkOption {
type = lib.types.str;
default = "60s";
};
rate_limit = lib.mkOption {
type = lib.types.int;
default = 300;
};
cors_allow_origins = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"http://localhost:5173"
"http://localhost:7926"
"https://localhost"
"capacitor://localhost"
];
};
serve_frontend = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
schedulerSettings = {
due_job = lib.mkOption {
type = lib.types.str;
default = "30m";
};
overdue_job = lib.mkOption {
type = lib.types.str;
default = "3h";
};
pre_due_job = lib.mkOption {
type = lib.types.str;
default = "3h";
};
};
emailSettings = {
host = lib.mkOption {
type = lib.types.str;
default = "";
};
port = lib.mkOption {
type = lib.types.int;
default = 587;
};
email = lib.mkOption {
type = lib.types.str;
default = "";
};
key = lib.mkOption {
type = lib.types.str;
default = "";
};
};
oauth2Settings = {
client_id = lib.mkOption {
type = lib.types.str;
default = "";
};
client_secret = lib.mkOption {
type = lib.types.str;
default = "";
};
auth_url = lib.mkOption {
type = lib.types.str;
default = "";
};
token_url = lib.mkOption {
type = lib.types.str;
default = "";
};
user_info_url = lib.mkOption {
type = lib.types.str;
default = "";
};
redirect_url = lib.mkOption {
type = lib.types.str;
default = "";
};
name = lib.mkOption {
type = lib.types.str;
default = "";
};
};
settings = lib.types.submodule {
options = {
is_user_creation_disabled = lib.mkEnableOption "Disable user creation";
telegram.token = lib.mkOption {
type = lib.types.str;
default = "";
};
pushover.token = lib.mkOption {
type = lib.types.str;
default = "";
};
jwt = types.jwtSettings;
server = types.serverSettings;
scheduler_jobs = types.schedulerSettings;
email = types.emailSettings;
oauth2 = types.oauth2Settings;
};
};
};
version = "0.0.33";
package = pkgs.stdenv.mkDerivation {
name = "donetick";
src = pkgs.fetchurl {
url = "https://github.com/donetick/donetick/releases/download/v${version}/donetick_Linux_arm64.tar.gz";
hash = "sha256-o5PcONv+fxBMToOCwBcr2fnSpsHn5bdfujyUTuMKTbI=";
};
nativeBuildInputs = [
pkgs.autoPatchelfHook
];
sourceRoot = ".";
installPhase = ''
runHook preInstall
install -Dm755 donetick $out/bin/donetick
runHook postInstall
'';
meta = with pkgs.lib; {
description = "A self-hosted task and chore manager";
license = licenses.mit;
mainProgram = "donetick";
};
};
cfg = config.services.donetick;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
immutableSettings = {
name = "selfhosted";
is_done_tick_dot_com = false;
email.appHost = "https://${fqdn}";
database = {
type = "sqlite";
migration = true;
};
};
settings = (pkgs.formats.yaml { }).generate "selfhosted.yaml" (cfg.settings // immutableSettings);
secrets = config.age.secrets;
in
{
options.services.donetick = {
enable = lib.mkEnableOption "Enable donetick";
subdomain = lib.mkOption {
type = lib.types.str;
};
settings = lib.mkOption {
type = types.settings;
};
};
config = lib.mkIf cfg.enable {
systemd.services.donetick = {
enable = true;
environment = {
DT_ENV = "selfhosted";
};
serviceConfig = {
DynamicUser = true;
ExecStart = "${package}/bin/donetick";
BindReadOnlyPaths = [
"${settings}:/var/lib/donetick/config/selfhosted.yaml"
/run/systemd/resolve/stub-resolv.conf
/etc/ssl
/etc/static/ssl
/etc/resolv.conf
/etc/static/resolv.conf
/etc/nsswitch.conf
/etc/static/nsswitch.conf
/etc/hosts
];
WorkingDirectory = "/var/lib/donetick";
StateDirectory = "donetick";
EnvironmentFile = secrets.donetick.path;
};
wantedBy = [ "multi-user.target" ];
confinement.enable = true;
};
services.webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = cfg.settings.server.port;
};
};
}

View File

@@ -0,0 +1,74 @@
{
lib,
config,
...
}:
let
cfg = config.services.gitea;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.gitea = {
subdomain = lib.mkOption {
type = lib.types.str;
};
secrets = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
};
};
config = lib.mkIf cfg.enable {
services.gitea = {
database = {
type = "postgres";
socket = "/run/postgresql";
};
settings = {
server = {
ROOT_URL = "https://${fqdn}/";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3008;
SSH_DOMAIN = fqdn;
SSH_PORT = 2222;
SSH_LISTEN_PORT = 2222;
START_SSH_SERVER = true;
};
service = {
DISABLE_REGISTRATION = false;
};
mailer = {
ENABLED = true;
};
};
};
services = {
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/" = {
proxyPort = 3008;
extraConfig = "client_max_body_size 0;";
};
};
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "gitea" ];
ensureUsers = [
{
name = "gitea";
ensureDBOwnership = true;
}
];
};
};
systemd.services.gitea = {
serviceConfig = lib.mkIf (cfg.secrets != null) {
EnvironmentFile = cfg.secrets;
};
};
networking.firewall.allowedTCPPorts = [ 2222 ];
};
}

View File

@@ -0,0 +1,45 @@
{ config, lib, ... }:
let
cfg = config.services.tailscaledGlance;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
acme = config.security.acme;
in
{
imports = [
(lib.mkAliasOptionModule
[ "services" "tailscaledGlance" "settings" ]
[ "services" "glance" "settings" ]
)
];
options.services.tailscaledGlance = {
enable = lib.mkEnableOption "Enable tailscaled glance";
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services.tailscale.enable = true;
modules.firewall.interfaces.${config.services.tailscale.interfaceName} = [ "dns" ];
systemd.services.glance.serviceConfig.LoadCredential = [
"fullchain.pem:${acme.certs.${fqdn}.directory}/fullchain.pem"
"key.pem:${acme.certs.${fqdn}.directory}/key.pem"
];
services.glance = {
enable = cfg.enable;
};
systemd.services.glance = {
requires = [ "tailscaled.service" ];
after = [ "tailscaled.service" ];
};
services.webserver.vHosts.${fqdn} = {
tailscaleAuth = true;
locations."/".proxyPort = cfg.settings.server.port;
};
};
}

View File

@@ -0,0 +1,53 @@
{ config, lib, ... }:
let
cfg = config.services.gotosocial;
secrets = config.age.secrets;
domain = config.networking.domain;
fqdn = "${cfg.subdomain}.${domain}";
port = cfg.settings.port;
in
{
options.services.gotosocial = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services = {
gotosocial = {
environmentFile = secrets.gotosocial.path;
settings = {
host = fqdn;
account-domain = domain;
protocol = "https";
bind-address = "localhost";
instance-inject-mastodon-version = true;
accounts-registration-open = true;
instance-expose-public-timeline = true;
letsencrypt-enabled = false;
};
setupPostgresqlDB = true;
};
webserver.vHosts = {
${domain}.locations = lib.listToAttrs (
lib.map
(path: {
name = "/.well-known/${path}";
value.extraConfig = ''
rewrite ^.*$ https://${fqdn}/.well-known/${path} permanent;
'';
})
[
"host-meta"
"webfinger"
"nodeinfo"
]
);
"${fqdn}".locations."/".proxyPort = port;
};
};
};
}

View File

@@ -1,60 +1,62 @@
{ ... }:
{ lib, config, ... }:
let
fqdn = "graph.freun.dev";
port = 3300;
cfg = config.services.grafana;
secrets = config.age.secrets;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
services.grafana = {
enable = true;
settings = {
server = {
root_url = "https://${fqdn}";
http_port = port;
};
database = {
host = "/var/run/postgresql";
type = "postgres";
user = "grafana";
};
smtp = {
enabled = true;
host = "horologium.uberspace.de";
from_address = "noreply@freun.dev";
from_name = "Vaultwarden";
user = "noreply@freun.dev";
password = "$__file{/var/secrets/smtp-password}";
};
options.services.grafana = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
services.prometheus = {
enable = true;
exporters.node.enable = true;
scrapeConfigs = [
{
job_name = "node";
static_configs = [
{ targets = [ "localhost:9100" ]; }
config = lib.mkIf cfg.enable {
services.grafana = {
settings = {
server = {
root_url = "https://${fqdn}";
};
database = {
host = "/var/run/postgresql";
type = "postgres";
user = "grafana";
};
smtp = {
from_name = "Grafana";
password = "$__file{${secrets.smtp-password.path}}";
};
};
};
services = {
prometheus = {
enable = true;
exporters.node.enable = true;
scrapeConfigs = [
{
job_name = "node";
static_configs = [
{ targets = [ "localhost:9100" ]; }
];
}
];
}
];
};
};
services.caddy.virtualHosts = {
"${fqdn}".extraConfig = ''
reverse_proxy localhost:${builtins.toString port}
'';
};
webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.settings.server.http_port;
services.postgresql = {
ensureDatabases = [ "grafana" ];
ensureUsers = [{
name = "grafana";
ensureDBOwnership = true;
}];
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "grafana" ];
ensureUsers = [
{
name = "grafana";
ensureDBOwnership = true;
}
];
};
};
};
}

View File

@@ -1,16 +1,25 @@
{ config, ... }:
{
lib,
config,
inputs,
...
}:
let
fqdn = "trackmap.freun.dev";
cfg = config.services.gtrackmap;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
services.gtrackmap = {
enable = true;
port = 3200;
imports = [
inputs.gtrackmap.nixosModules.default
];
options.services.gtrackmap = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
services.caddy.virtualHosts = {
"${fqdn}".extraConfig = ''
reverse_proxy localhost:${toString config.services.gtrackmap.port}
'';
config = lib.mkIf cfg.enable {
services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
};
}

View File

@@ -0,0 +1,61 @@
{
lib,
config,
inputs,
...
}:
let
cfg = config.services.hastebin;
secrets = config.age.secrets;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
imports = [
inputs.hastebin.nixosModules.default
];
options.services.hastebin.subdomain = lib.mkOption {
type = lib.types.str;
};
config = lib.mkIf cfg.enable {
services.hastebin.settings = lib.mkDefault {
max_size = "1 GiB";
host = "::1";
port = 3600;
mime_overrides = {
"text/plain" = [
"log"
"txt"
"diff"
"sh"
"rs"
"toml"
"cr"
"nix"
"rb"
"ts"
"tsx"
"jsx"
];
};
auth_tokens_file = secrets.hastebin-tokens.path;
};
services.webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
proxyBuffering = false;
locations."/" = {
proxyPort = cfg.settings.port;
extraConfig = ''
client_max_body_size 0;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
'';
};
};
};
};
}

View File

@@ -0,0 +1,72 @@
{ config, lib, ... }:
let
cfg = config.services.hledger-web;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.hledger-web = {
subdomain = lib.mkOption {
type = lib.types.str;
};
user = lib.mkOption {
type = lib.types.str;
};
group = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services = {
hledger-web = {
allow = lib.mkDefault "edit";
baseUrl = "https://${fqdn}";
serveApi = true;
extraOptions = [
"--exchange="
];
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
tailscaleAuth = true;
extraConfig = ''
root /var/www/ledgio;
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods 'OPTIONS, GET, PUT' always;
add_header Access-Control-Allow-Headers 'Content-Type' always;
location ~ \.(html|js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
}
'';
locations = {
"@api" = {
proxyPort = cfg.port;
};
"/".extraConfig = ''
if ($request_method = OPTIONS) {
add_header Content-Type text/plain;
add_header Content-Length 0;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods 'OPTIONS, GET, PUT';
add_header Access-Control-Allow-Headers 'Content-Type';
return 204;
}
try_files $uri $uri/ @api;
'';
};
};
};
};
systemd.services.hledger-web.serviceConfig = {
User = lib.mkForce cfg.user;
Group = lib.mkForce cfg.group;
};
};
}

View File

@@ -0,0 +1,20 @@
{ config, lib, ... }:
let
cfg = config.services.home-assistant;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.home-assistant = {
subdomain = lib.mkOption {
type = lib.types.str;
description = "The subdomain to use for Home Assistant";
};
};
config = lib.mkIf cfg.enable {
services.webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = cfg.config.http.server_port;
};
};
}

View File

@@ -1,21 +0,0 @@
{ ... }:
let
fqdn = "ci.freun.dev";
port = 3400;
in
{
services.hydra = {
enable = true;
hydraURL = "https://${fqdn}";
notificationSender = "Hydra <noreply@freun.dev>";
buildMachinesFiles = [];
useSubstitutes = true;
inherit port;
};
services.caddy.virtualHosts = {
"${fqdn}".extraConfig = ''
reverse_proxy localhost:${builtins.toString port}
'';
};
}

View File

@@ -1,110 +1,52 @@
{ lib, config, ... }:
let
cfg = config.services.immich;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.immich = with lib; {
enable = mkEnableOption "Enable immich";
fqdn = mkOption {
type = types.str;
description = "FQDN to use for the immich server";
options.services.immich = {
subdomain = lib.mkOption {
type = lib.types.str;
};
data_dir = mkOption {
type = types.str;
description = "The directory to store immich data in";
timezone = lib.mkOption {
type = lib.types.str;
default = "UTC";
};
secrets = mkOption {
type = types.str;
description = "Path to file with secrets";
uid = lib.mkOption {
type = lib.types.int;
default = 990;
};
version = mkOption {
type = types.str;
default = "release";
description = "The version (docker image tag) of immich to use";
};
mounts = mkOption {
type = types.listOf types.str;
description = "Additional mounts to add to the immich container";
default = [ ];
};
port = mkOption {
type = types.int;
default = 2283;
description = "Port to expose the immich server on";
gid = lib.mkOption {
type = lib.types.int;
default = 997;
};
};
imports = [
../util/container-services.nix
];
config = lib.mkIf cfg.enable rec {
container-services.immich = {
description = "Immich image server";
services = {
server = {
image = "ghcr.io/immich-app/immich-server:${cfg.version}";
environmentFiles = [
cfg.secrets
];
volumes = [
"${cfg.data_dir}:/usr/src/app/upload:rw"
"/etc/localtime:/etc/localtime:ro"
] ++ cfg.mounts;
ports = [ "${builtins.toString cfg.port}:3001/tcp" ];
dependsOn = [
container-services.immich.services.redis
container-services.immich.services.postgres
];
config = lib.mkIf cfg.enable {
services = {
immich = {
environment = {
TZ = cfg.timezone;
};
machine_learning = {
image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}";
environmentFiles = [
cfg.secrets
];
volumes = [
"model_cache:/cache:rw"
];
};
redis = {
image = "registry.hub.docker.com/library/redis:6.2-alpine";
healthCheck.test = "redis-cli ping || exit 1";
environmentFiles = [
cfg.secrets
];
};
postgres = {
image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0";
environmentFiles = [
cfg.secrets
];
environment = {
POSTGRES_INITDB_ARGS = "--data-checksums";
};
volumes = [
"db_data:/var/lib/postgresql/data:rw"
];
cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ];
healthCheck = {
test = ''
pg_isready --dbname='$\{DB_DATABASE_NAME}' || exit 1
Chksum="$(psql --dbname='$\{DB_DATABASE_NAME}' --username='$\{DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"
echo "checksum failure count is $Chksum"
[ "$Chksum" = '0' ] || exit 1
settings.server.externalDomain = "https://${fqdn}";
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
proxyBuffering = false;
locations."/" = {
proxyPort = cfg.port;
extraConfig = ''
client_max_body_size 0;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
'';
interval = "5m";
startInterval = "30s";
startPeriod = "5m";
};
};
};
};
services.caddy.virtualHosts = {
"${cfg.fqdn}".extraConfig = ''
reverse_proxy localhost:${builtins.toString cfg.port}
'';
};
users.users.${cfg.user}.uid = cfg.uid;
users.groups.${cfg.user}.gid = cfg.gid;
};
}

View File

@@ -0,0 +1,99 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.invidious-companion;
companionRelease = "release-master";
hostPlatform = pkgs.stdenv.hostPlatform.system;
# Invidious Companion package - fetches binary release and patches for NixOS
unwrappedCompanion = pkgs.stdenv.mkDerivation {
pname = "unwrapped-invidious-companion";
version = companionRelease;
src =
let
archMap = {
x86_64-linux = "x86_64-unknown-linux-gnu";
aarch64-linux = "aarch64-unknown-linux-gnu";
};
platform = archMap.${hostPlatform} or (throw "Unsupported platform: ${hostPlatform}");
in
pkgs.fetchzip {
url = "https://github.com/iv-org/invidious-companion/releases/download/${companionRelease}/invidious_companion-${platform}.tar.gz";
sha256 = cfg.binaryHash;
};
dontStrip = true;
dontPatchELF = true;
installPhase = ''
mkdir -p $out/bin
cp invidious_companion $out/bin/invidious_companion
chmod +x $out/bin/invidious_companion
'';
};
invidiousCompanion = pkgs.buildFHSEnv {
name = "invidious-companion";
targetPkgs = pkgs: [ unwrappedCompanion ];
runScript = "invidious_companion";
meta = {
description = "Invidious companion for handling video streams";
homepage = "https://github.com/iv-org/invidious-companion";
license = lib.licenses.agpl3Only;
};
};
in
{
options.services.invidious-companion = {
enable = lib.mkEnableOption "Enable Invidious Companion service";
host = lib.mkOption {
type = lib.types.str;
default = "localhost";
};
port = lib.mkOption {
type = lib.types.port;
default = 8282;
description = "Port for Invidious Companion to listen on";
};
secretKeyFile = lib.mkOption {
type = lib.types.str;
description = "Path to file containing the companion secret key";
};
binaryHash = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "SHA256 hash of the invidious companion binary release";
};
};
config = lib.mkIf cfg.enable {
systemd.services.invidious-companion = {
description = "Invidious Companion - video stream handler";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
User = "invidious";
Group = "invidious";
DynamicUser = true;
ExecStart = lib.getExe invidiousCompanion;
Environment = [
"HOST=${cfg.host}"
"PORT=${toString cfg.port}"
"TMPDIR=/var/cache/invidious-companion"
];
EnvironmentFile = [ cfg.secretKeyFile ];
CacheDirectory = "invidious-companion";
WorkingDirectory = "%C/invidious-companion";
Restart = "always";
RestartSec = 5;
};
};
};
}

View File

@@ -1,25 +1,49 @@
{ ... }:
{
config,
lib,
...
}:
let
fqdn = "vid.freun.dev";
cfg = config.services.invidious;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
services.invidious = {
enable = true;
domain = fqdn;
settings = {
external_port = 443;
db = {
dbname = "invidious";
user = "invidious";
};
options.services.invidious = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
services.caddy.virtualHosts = {
"${fqdn}".extraConfig = ''
reverse_proxy localhost:3000
'';
};
config = lib.mkIf cfg.enable {
services = {
invidious = {
domain = fqdn;
address = "127.0.0.1";
settings = {
external_port = 443;
db = {
dbname = "invidious";
user = "invidious";
};
};
};
postgresql.enable = lib.mkDefault true;
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
};
};
systemd.services.invidious.serviceConfig.DynamicUser = lib.mkForce false;
users.groups.invidious = { };
users.users.invidious = {
isSystemUser = true;
group = "invidious";
description = "Invidious user";
};
};
}

View File

@@ -0,0 +1,37 @@
{ lib, config, ... }:
let
cfg = config.services.mealie;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.mealie = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services = {
mealie.settings = {
BASE_URL = "https://${fqdn}";
ALLOW_SIGNUP = true;
DB_ENGINE = "postgres";
POSTGRES_URL_OVERRIDE = "postgresql://mealie:@mealie?host=/var/run/postgresql";
SMTP_FROM_NAME = "Mealie";
};
webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "mealie" ];
ensureUsers = [
{
name = "mealie";
ensureDBOwnership = true;
}
];
};
};
};
}

View File

@@ -0,0 +1,14 @@
{ config, lib, ... }:
let
cfg = config.services.mosquitto;
in
{
options = {
services.mosquitto = {
openFirewall = lib.mkEnableOption "Open firewall port for Mosquitto";
};
};
config = lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = map ({ port, ... }: port) cfg.listeners;
};
}

View File

@@ -0,0 +1,64 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.modules.services.network-status;
in
{
options.modules.services.network-status = {
enable = lib.mkEnableOption "Enable network status socket service";
port = lib.mkOption {
type = lib.types.int;
default = 8473;
description = "TCP port to listen on for network status requests";
};
interface = lib.mkOption {
type = lib.types.str;
default = "koti";
description = "Network interface to allow access from";
};
};
config = lib.mkIf cfg.enable {
users.users.network-status = {
isSystemUser = true;
group = "network-status";
description = "Network status socket service user";
};
users.groups.network-status = { };
systemd.sockets.network-status = {
description = "Network Status Socket";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = cfg.port;
Accept = true;
};
};
systemd.services."network-status@" = {
description = "Network Status Service";
serviceConfig = {
Type = "simple";
User = "network-status";
Group = "systemd-network";
SupplementaryGroups = [ "systemd-network" ];
StandardOutput = "socket";
StandardInput = "socket";
ExecStart = lib.concatStringsSep " " [
(lib.getExe' pkgs.systemd "networkctl")
"status"
"--json=short"
];
};
};
modules.firewall.interfaces.${cfg.interface} = lib.mkDefault [ "network-status" ];
};
}

View File

@@ -0,0 +1,27 @@
{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.nqptp;
in
{
options.services.nqptp = {
enable = lib.mkEnableOption "nqptp service for shairport-sync";
};
config = lib.mkIf cfg.enable {
systemd.services.nqptp = {
description = "Network Queued PCM Transport Protocol";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = lib.getExe pkgs.nqptp;
Restart = "on-failure";
};
};
};
}

View File

@@ -0,0 +1,248 @@
{
pkgs,
pkgs-unstable,
lib,
config,
...
}:
let
cfg = config.services.octodns;
secrets = config.age.secrets;
types = {
ttlOptions = default: {
ttl = lib.mkOption {
type = lib.types.int;
default = default;
};
};
defaults = lib.types.submodule {
options = {
A = lib.mkOption {
type = lib.types.submodule {
options = types.ttlOptions cfg.defaults.ttl;
};
default = { };
};
AAAA = lib.mkOption {
type = lib.types.submodule {
options = types.ttlOptions cfg.defaults.ttl;
};
default = { };
};
CNAME = lib.mkOption {
type = lib.types.submodule {
options = types.ttlOptions cfg.defaults.ttl;
};
default = { };
};
MX = lib.mkOption {
type = lib.types.submodule {
options = types.ttlOptions cfg.defaults.ttl;
};
default = { };
};
TXT = lib.mkOption {
type = lib.types.submodule {
options = types.ttlOptions cfg.defaults.ttl;
};
default = { };
};
}
// (types.ttlOptions 3600);
};
aRecord = lib.types.submodule {
options = {
values = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
ttl = lib.mkOption {
type = lib.types.int;
default = cfg.defaults.A.ttl;
};
};
};
aaaaRecord = lib.types.submodule {
options = {
values = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
ttl = lib.mkOption {
type = lib.types.int;
default = cfg.defaults.AAAA.ttl;
};
};
};
cnameRecord = lib.types.submodule {
options = {
target = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
toRoot = lib.mkOption {
type = lib.types.bool;
default = false;
};
ttl = lib.mkOption {
type = lib.types.int;
default = cfg.defaults.CNAME.ttl;
};
};
};
mxValue = lib.types.submodule {
options = {
exchange = lib.mkOption {
type = lib.types.str;
};
preference = lib.mkOption {
type = lib.types.int;
default = 10;
};
};
};
mxRecord = lib.types.submodule {
options = {
values = lib.mkOption {
type = lib.types.listOf types.mxValue;
default = [ ];
};
ttl = lib.mkOption {
type = lib.types.int;
default = cfg.defaults.MX.ttl;
};
};
};
txtRecord = lib.types.submodule {
options = {
values = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
ttl = lib.mkOption {
type = lib.types.int;
default = cfg.defaults.AAAA.ttl;
};
};
};
records = lib.types.submodule {
options = {
A = lib.mkOption {
type = types.aRecord;
default = { };
};
AAAA = lib.mkOption {
type = types.aaaaRecord;
default = { };
};
CNAME = lib.mkOption {
type = types.cnameRecord;
default = { };
};
MX = lib.mkOption {
type = types.mxRecord;
default = { };
};
TXT = lib.mkOption {
type = types.txtRecord;
default = { };
};
};
};
};
yamlFormat = pkgs.formats.yaml { };
zoneFile = yamlFormat.generate "octodns-zone" (
lib.filterAttrs (_: records: (lib.length records) > 0) (
lib.mapAttrs (
_: types:
lib.filter
(
{
values ? [ ],
value ? null,
...
}:
(lib.length values) > 0 || !(builtins.isNull value)
)
(
lib.mapAttrsToList (
type:
{ ttl, ... }@options:
if (type == "CNAME") then
let
inherit (options) target toRoot;
value = if toRoot then "${config.networking.domain}." else target;
in
{
inherit type ttl value;
}
else
{
inherit type ttl;
inherit (options) values;
}
) types
)
) cfg.records
)
);
zonesDir = pkgs.linkFarm "octodns-zones" {
"${config.networking.domain}.yaml" = zoneFile;
};
configFile = yamlFormat.generate "octodns-config.yaml" {
providers = {
config = {
class = "octodns.provider.yaml.YamlProvider";
directory = zonesDir;
default_ttl = cfg.defaults.ttl;
};
hetzner = {
class = "octodns_hetzner.HetznerProvider";
token = "env/HETZNER_API_TOKEN";
};
};
zones."*" = {
sources = [ "config" ];
targets = [ "hetzner" ];
};
};
octodns = pkgs-unstable.octodns.withProviders (_: [
pkgs-unstable.octodns-providers.hetzner
]);
in
{
options.services.octodns = {
enable = lib.mkEnableOption "Enable octodns";
records = lib.mkOption {
type = lib.types.attrsOf types.records;
default = { };
};
defaults = lib.mkOption {
type = types.defaults;
default = { };
};
};
config = lib.mkIf cfg.enable {
systemd.services.octodns = {
enable = true;
description = "OctoDNS";
serviceConfig = {
Type = "oneshot";
ExecStart = "${octodns}/bin/octodns-sync --config-file ${configFile} --doit";
DynamicUser = true;
EnvironmentFile = secrets.hetzner.path;
RemainAfterExit = true;
};
wantedBy = lib.optionals config.services.nginx.enable [ "nginx.service" ];
before = lib.optionals config.services.nginx.enable [ "nginx.service" ];
};
};
}

View File

@@ -1,16 +1,22 @@
{ ... }:
{ lib, config, ... }:
let
fqdn = "stream.freun.dev";
in
cfg = config.services.owncast;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
services.owncast = {
enable = true;
openFirewall = true;
options.services.owncast = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
services.caddy.virtualHosts = {
"${fqdn}".extraConfig = ''
reverse_proxy localhost:8080
'';
config = lib.mkIf cfg.enable {
services = {
owncast = {
openFirewall = true;
};
webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
};
};
}

View File

@@ -0,0 +1,52 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.services.readeck;
secrets = config.age.secrets;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.readeck = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services = {
readeck = {
package = pkgs.readeck;
environmentFile = secrets.readeck.path;
settings = {
server.port = lib.mkDefault 8090;
database.source = "postgres://readeck:@readeck?host=/var/run/postgresql";
};
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
proxyBuffering = false;
locations."/" = {
proxyPort = cfg.settings.server.port;
};
};
};
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "readeck" ];
ensureUsers = [
{
name = "readeck";
ensureDBOwnership = true;
}
];
};
};
};
}

View File

@@ -0,0 +1,28 @@
{
config,
lib,
...
}:
let
cfg = config.services.sillytavern;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.sillytavern = {
subdomain = lib.mkOption {
type = lib.types.str;
default = "sillytavern";
};
};
config = lib.mkIf cfg.enable {
services.webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn} = {
tailscaleAuth = true;
proxyBuffering = false;
locations."/".proxyPort = cfg.port;
};
};
};
}

View File

@@ -1,33 +1,40 @@
{ pkgs, config, lib, ... }:
{ config, lib, ... }:
let
storage_dir = "/mnt/storage/syncthing";
cfg = config.services.syncthing;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
services.syncthing = {
enable = true;
dataDir = "/mnt/storage/syncthing";
openDefaultPorts = true;
options.services.syncthing = {
subdomain = lib.mkOption {
type = lib.types.str;
};
port = lib.mkOption {
type = lib.types.int;
default = 8384;
};
};
services.caddy.virtualHosts = {
"sync.freun.dev".extraConfig = ''
reverse_proxy localhost:8384 {
header_up Host {upstream_hostport}
}
'';
config = lib.mkIf cfg.enable {
services = {
syncthing = {
openDefaultPorts = true;
guiAddress = "[::1]:${toString cfg.port}";
settings.gui.insecureSkipHostCheck = true;
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = cfg.port;
};
};
systemd.services.syncthing.serviceConfig =
lib.mkIf
(
cfg.dataDir == "/var/lib/syncthing" || cfg.dataDir == null || cfg.configDir == "/var/lib/syncthing"
)
{
StateDirectory = "syncthing";
};
};
fileSystems."${storage_dir}" = {
device = "//u407959.your-storagebox.de/backup/syncthing";
fsType = "cifs";
options = let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
uid = builtins.toString config.users.users.syncthing.uid;
gid = builtins.toString config.users.groups.syncthing.gid;
in ["${automount_opts},credentials=/var/secrets/smb-storage,uid=${uid},gid=${gid}"];
};
environment.systemPackages = [ pkgs.cifs-utils ];
}

View File

@@ -0,0 +1,9 @@
{ lib, config, ... }:
let
cfg = config.services.tailscale;
in
{
config = lib.mkIf cfg.enable {
services.tailscale.useRoutingFeatures = lib.mkDefault "server";
};
}

View File

@@ -0,0 +1,16 @@
{ lib, config, ... }:
let
cfg = config.services.uptime-kuma;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
in
{
options.services.uptime-kuma = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkIf cfg.enable {
services.webserver.vHosts.${fqdn}.locations."/".proxyPort = lib.toInt cfg.settings.PORT;
};
}

View File

@@ -1,42 +1,48 @@
{ ... }:
{ lib, config, ... }:
let
cfg = config.services.vaultwarden;
secrets = config.age.secrets;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
port = config.services.vaultwarden.config.ROCKET_PORT;
in
{
services.vaultwarden = {
enable = true;
dbBackend = "postgresql";
environmentFile = "/var/secrets/vaultwarden.env";
config = {
DOMAIN = "https://pw.freun.dev";
DATABASE_URL = "postgres://%2Fvar%2Frun%2Fpostgresql/vaultwarden";
WEBSOCKET_ENABLED = true;
WEBSOCKET_ADDRESS = "127.0.0.1";
WEBSOCKET_PORT = 3012;
SIGNUPS_VERIFY = true;
PASSWORD_ITERATIONS = 600000;
YUBICO_CLIENT_ID = 86799;
SMTP_HOST = "horologium.uberspace.de";
SMTP_FROM = "noreply@freun.dev";
SMTP_FROM_NAME = "Vaultwarden";
SMTP_USERNAME = "noreply@freun.dev";
SMTP_PORT = 587;
HELO_NAME = "freun.dev";
ROCKET_LIMITS = "{json=10485760}";
options.services.vaultwarden = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
services.caddy.virtualHosts = {
"pw.freun.dev".extraConfig = ''
reverse_proxy /notifications/hub localhost:3012
reverse_proxy localhost:8000 {
header_up X-Real-IP {remote_host}
}
'';
};
config = lib.mkIf cfg.enable {
services = {
vaultwarden = {
dbBackend = "postgresql";
environmentFile = secrets.vaultwarden.path;
config = {
DOMAIN = "https://${fqdn}";
DATABASE_URL = "postgres://%2Fvar%2Frun%2Fpostgresql/vaultwarden";
WEBSOCKET_ENABLED = true;
SIGNUPS_VERIFY = true;
PASSWORD_ITERATIONS = 600000;
ROCKET_LIMITS = "{json=10485760}";
ROCKET_PORT = 8000;
};
};
services.postgresql = {
ensureDatabases = [ "vaultwarden" ];
ensureUsers = [{
name = "vaultwarden";
ensureDBOwnership = true;
}];
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = port;
};
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "vaultwarden" ];
ensureUsers = [
{
name = "vaultwarden";
ensureDBOwnership = true;
}
];
};
};
};
}

View File

@@ -0,0 +1,50 @@
{
lib,
config,
inputs,
...
}:
let
cfg = config.services.voidauth;
fqdn = "${cfg.subdomain}.${config.networking.domain}";
port = cfg.settings.APP_PORT or 3000;
in
{
options.services.voidauth = {
subdomain = lib.mkOption {
type = lib.types.str;
};
};
imports = [
inputs.voidauth.nixosModules.voidauth
];
config = lib.mkIf cfg.enable {
services = {
voidauth = {
settings = {
APP_URL = "https://${fqdn}";
DB_SOCKET_PATH = "/run/postgresql";
DB_NAME = "voidauth";
};
};
webserver = {
enable = lib.mkDefault true;
vHosts.${fqdn}.locations."/".proxyPort = port;
};
postgresql = {
enable = lib.mkDefault true;
ensureDatabases = [ "voidauth" ];
ensureUsers = [
{
name = "voidauth";
ensureDBOwnership = true;
}
];
};
};
};
}

Some files were not shown because too many files have changed in this diff Show More