Kieran's opinionated (and probably slightly dumb) nix config
1{
2 inputs,
3 lib,
4 config,
5 pkgs,
6 ...
7}:
8{
9 imports = [
10 ./disk-config.nix
11 ./home-manager.nix
12
13 (inputs.import-tree ../../modules/nixos)
14 inputs.tangled.nixosModules.knot
15 inputs.tangled.nixosModules.spindle
16 ];
17
18 nixpkgs = {
19 hostPlatform = "aarch64-linux";
20 config = {
21 allowUnfree = true;
22 };
23 };
24
25 nix =
26 let
27 flakeInputs = lib.filterAttrs (_: lib.isType "flake") inputs;
28 in
29 {
30 settings = {
31 experimental-features = "nix-command flakes";
32 flake-registry = "";
33 nix-path = config.nix.nixPath;
34 trusted-users = [
35 "kierank"
36 ];
37 };
38 channel.enable = false;
39 optimise.automatic = true;
40 registry = lib.mapAttrs (_: flake: { inherit flake; }) flakeInputs;
41 nixPath = lib.mapAttrsToList (n: _: "${n}=flake:${n}") flakeInputs;
42 };
43
44 time.timeZone = "America/New_York";
45
46 environment.systemPackages = with pkgs; [
47 # core
48 coreutils
49 screen
50 bc
51 jq
52 psmisc
53 # cli_utils
54 direnv
55 zsh
56 gum
57 vim
58 # networking
59 xh
60 curl
61 wget
62 dogdns
63 inetutils
64 mosh
65 # nix_tools
66 inputs.nixvim.packages.aarch64-linux.default
67 nixd
68 nil
69 nixfmt-rfc-style
70 inputs.agenix.packages.aarch64-linux.default
71 # security
72 openssl
73 gpgme
74 gnupg
75 # dev_langs
76 nodejs_22
77 unstable.bun
78 python3
79 go
80 gopls
81 gotools
82 go-tools
83 gcc
84 # misc
85 neofetch
86 git
87 ];
88
89 programs.nh = {
90 enable = true;
91 clean.enable = true;
92 clean.extraArgs = "--keep-since 4d --keep 3";
93 flake = "/home/kierank/dots";
94 };
95
96 age.identityPaths = [
97 "/home/kierank/.ssh/id_rsa"
98 "/etc/ssh/id_rsa"
99 ];
100 age.secrets = {
101 wakatime = {
102 file = ../../secrets/wakatime.age;
103 path = "/home/kierank/.wakatime.cfg";
104 owner = "kierank";
105 };
106 cachet = {
107 file = ../../secrets/cachet.age;
108 owner = "cachet";
109 };
110 hn-alerts = {
111 file = ../../secrets/hn-alerts.age;
112 owner = "hn-alerts";
113 };
114 emojibot = {
115 file = ../../secrets/emojibot.age;
116 owner = "emojibot";
117 };
118 cloudflare = {
119 file = ../../secrets/cloudflare.age;
120 owner = "caddy";
121 };
122 github-knot-sync = {
123 file = ../../secrets/github-knot-sync.age;
124 owner = "git";
125 };
126 battleship-arena = {
127 file = ../../secrets/battleship-arena.age;
128 owner = "battleship-arena";
129 };
130 frp-auth-token = {
131 file = ../../secrets/frp-auth-token.age;
132 };
133 };
134
135 environment.sessionVariables = {
136 XDG_CACHE_HOME = "$HOME/.cache";
137 XDG_CONFIG_HOME = "$HOME/.config";
138 XDG_DATA_HOME = "$HOME/.local/share";
139 XDG_STATE_HOME = "$HOME/.local/state";
140 EDITOR = "nvim";
141 SYSTEMD_EDITOR = "nvim";
142 VISUAL = "nvim";
143 };
144
145 atelier = {
146 authentication.enable = true;
147 };
148
149 networking = {
150 hostName = "terebithia";
151 networkmanager.enable = true;
152 };
153
154 programs.zsh.enable = true;
155 programs.direnv.enable = true;
156
157 users.users = {
158 kierank = {
159 initialPassword = "changeme";
160 isNormalUser = true;
161 shell = pkgs.zsh;
162 openssh.authorizedKeys.keys = [
163 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
164 ];
165 extraGroups = [
166 "wheel"
167 "networkmanager"
168 "services"
169 ];
170 };
171 root.openssh.authorizedKeys.keys = [
172 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
173 ];
174 };
175
176 # Allow passwordless sudo for wheel group (needed for deploy-rs)
177 security.sudo.wheelNeedsPassword = false;
178
179 services.openssh = {
180 enable = true;
181 openFirewall = true;
182 settings = {
183 PermitRootLogin = "no";
184 PasswordAuthentication = false;
185 };
186 };
187
188 networking.firewall = {
189 enable = true;
190 allowedTCPPorts = [
191 22
192 80
193 443
194 ];
195 logRefusedConnections = false;
196 rejectPackets = true;
197 };
198
199 services.tailscale = {
200 enable = true;
201 useRoutingFeatures = "client";
202 };
203
204 services.caddy = {
205 enable = true;
206 package = pkgs.caddy.withPlugins {
207 plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ];
208 hash = "sha256-ea8PC/+SlPRdEVVF/I3c1CBprlVp1nrumKM5cMwJJ3U=";
209 };
210 email = "me@dunkirk.sh";
211 globalConfig = ''
212 acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
213 '';
214 virtualHosts."knot.dunkirk.sh" = {
215 extraConfig = ''
216 tls {
217 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
218 }
219 header {
220 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
221 }
222 reverse_proxy localhost:5555 {
223 header_up X-Forwarded-Proto {scheme}
224 header_up X-Forwarded-For {remote}
225 }
226 '';
227 };
228 virtualHosts."spindle.dunkirk.sh" = {
229 extraConfig = ''
230 tls {
231 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
232 }
233 header {
234 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
235 }
236 reverse_proxy localhost:6555 {
237 header_up X-Forwarded-Proto {scheme}
238 header_up X-Forwarded-For {remote}
239 }
240 '';
241 };
242 virtualHosts."emojibot.dunkirk.sh" = {
243 extraConfig = ''
244 tls {
245 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
246 }
247 header {
248 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
249 }
250 reverse_proxy localhost:3002 {
251 header_up X-Forwarded-Proto {scheme}
252 header_up X-Forwarded-For {remote}
253 }
254 '';
255 };
256 virtualHosts."battleship.dunkirk.sh" = {
257 extraConfig = ''
258 tls {
259 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
260 }
261 header {
262 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
263 }
264 reverse_proxy localhost:8081 {
265 header_up X-Forwarded-Proto {scheme}
266 header_up X-Forwarded-For {remote}
267 }
268 '';
269 };
270 extraConfig = ''
271 # Default response for unhandled domains
272 :80 {
273 respond "404 - Looks like this bridge doesn't have an end" 404
274 }
275 :443 {
276 respond "404 - Looks like this bridge doesn't have an end" 404
277 }
278 '';
279 };
280
281 systemd.services.caddy.serviceConfig = {
282 EnvironmentFile = config.age.secrets.cloudflare.path;
283 };
284
285 atelier.services.cachet = {
286 enable = true;
287 domain = "cachet.dunkirk.sh";
288 secretsFile = config.age.secrets.cachet.path;
289 };
290
291 atelier.services.hn-alerts = {
292 enable = true;
293 domain = "hn.dunkirk.sh";
294 secretsFile = config.age.secrets.hn-alerts.path;
295 };
296
297 atelier.services.emojibot = {
298 enable = true;
299 domain = "emojibot.dunkirk.sh";
300 secretsFile = config.age.secrets.emojibot.path;
301 };
302
303 atelier.services.battleship-arena = {
304 enable = true;
305 domain = "battleship.dunkirk.sh";
306 sshPort = 2222;
307 package = inputs.battleship-arena.packages.aarch64-linux.default;
308 secretsFile = config.age.secrets.battleship-arena.path;
309 };
310
311 services.tangled.knot = {
312 enable = true;
313 package = inputs.tangled.packages.aarch64-linux.knot;
314 appviewEndpoint = "https://tangled.org";
315 server = {
316 owner = "did:plc:krxbvxvis5skq7jj6eot23ul";
317 hostname = "knot.dunkirk.sh";
318 listenAddr = "127.0.0.1:5555";
319 };
320 };
321
322 services.tangled.spindle = {
323 enable = true;
324 package = inputs.tangled.packages.aarch64-linux.spindle;
325 server = {
326 owner = "did:plc:krxbvxvis5skq7jj6eot23ul";
327 hostname = "spindle.dunkirk.sh";
328 listenAddr = "127.0.0.1:6555";
329 };
330 };
331
332 atelier.services.knot-sync = {
333 enable = true;
334 secretsFile = config.age.secrets.github-knot-sync.path;
335 };
336
337 atelier.services.frps = {
338 enable = true;
339 domain = "bore.dunkirk.sh";
340 authTokenFile = config.age.secrets.frp-auth-token.path;
341 };
342
343 services.n8n = {
344 enable = true;
345 environment = {
346 N8N_HOST = "n8n.dunkirk.sh";
347 N8N_PROTOCOL = "https";
348 WEBHOOK_URL = "https://n8n.dunkirk.sh";
349 };
350 };
351
352 services.caddy.virtualHosts."n8n.dunkirk.sh" = {
353 extraConfig = ''
354 tls {
355 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
356 }
357 header {
358 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
359 }
360 reverse_proxy localhost:5678 {
361 header_up X-Forwarded-Proto {scheme}
362 header_up X-Forwarded-For {remote}
363 }
364 '';
365 };
366
367 boot.loader.systemd-boot.enable = true;
368 boot.loader.efi.canTouchEfiVariables = true;
369 boot.kernelParams = [ "console=ttyS0" ];
370
371 system.stateVersion = "23.05";
372}