yep, more dotfiles
1{ self
2, config
3, pkgs
4, ...
5}:
6
7let
8 inherit (self.inputs) srvos;
9
10 ext-if = "eth0";
11 external-ip = "91.99.55.74";
12 external-netmask = 27;
13 external-gw = "144.x.x.255";
14 external-ip6 = "2a01:4f8:c2c:76d2::1";
15 external-netmask6 = 64;
16 external-gw6 = "fe80::1";
17
18 website-hostname = "wiro.world";
19
20 static-hostname = "static.wiro.world";
21in
22{
23 imports = [
24 srvos.nixosModules.server
25 srvos.nixosModules.hardware-hetzner-cloud
26 srvos.nixosModules.mixins-terminfo
27
28 ./authelia.nix
29 ./goatcounter.nix
30 ./grafana.nix
31 ./headscale.nix
32 ./hypixel-bank-tracker.nix
33 ./lldap.nix
34 ./miniflux.nix
35 ./pds.nix
36 ./tangled.nix
37 ./thelounge.nix
38 ./tuwunel.nix
39 ./vaultwarden.nix
40 ./warrior.nix
41 ./webfinger.nix
42 ];
43
44 config = {
45 boot.loader.grub.enable = true;
46 boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" "ext4" ];
47
48 # Single network card is `eth0`
49 networking.usePredictableInterfaceNames = false;
50
51 networking.nameservers = [ "2001:4860:4860::8888" "2001:4860:4860::8844" ];
52
53 networking = {
54 interfaces.${ext-if} = {
55 ipv4.addresses = [{ address = external-ip; prefixLength = external-netmask; }];
56 ipv6.addresses = [{ address = external-ip6; prefixLength = external-netmask6; }];
57 };
58 defaultGateway = { interface = ext-if; address = external-gw; };
59 defaultGateway6 = { interface = ext-if; address = external-gw6; };
60
61 # Reflect firewall configuration on Hetzner
62 firewall.allowedTCPPorts = [ 22 80 443 ];
63 };
64
65 services.qemuGuest.enable = true;
66
67 services.openssh.enable = true;
68
69 # age.secrets.tailscale-authkey.file = secrets/tailscale-authkey.age;
70 services.tailscale = {
71 enable = true;
72 extraSetFlags = [ "--advertise-exit-node" ];
73 # authKeyFile = config.age.secrets.tailscale-authkey.path;
74 authKeyParameters = {
75 baseURL = "https://headscale.wiro.world";
76 ephemeral = true;
77 preauthorized = true;
78 };
79 };
80
81 security.sudo.wheelNeedsPassword = false;
82
83 local.fragment.nix.enable = true;
84
85 programs.fish.enable = true;
86
87 services.fail2ban = {
88 enable = true;
89
90 maxretry = 5;
91 ignoreIP = [ ];
92
93 bantime = "24h";
94 bantime-increment = {
95 enable = true;
96 multipliers = "1 2 4 8 16 32 64";
97 maxtime = "168h";
98 overalljails = true;
99 };
100
101 jails = { };
102 };
103
104 age.secrets.caddy-env.file = secrets/caddy-env.age;
105 services.caddy = {
106 enable = true;
107 package = pkgs.caddy.withPlugins {
108 plugins = [
109 "github.com/caddy-dns/hetzner/v2@v2.0.0-preview-1"
110 "github.com/tailscale/caddy-tailscale@v0.0.0-20251016213337-01d084e119cb"
111 ];
112 hash = "sha256-muKwDYs5Jp4ib/psZxpp1Kyfsqz6wPz/lpHFGtx67uY=";
113 };
114
115 environmentFile = config.age.secrets.caddy-env.path;
116
117 globalConfig = ''
118 tailscale {
119 # this caddy instance already proxies headscale but needs to access headscale to start
120 # control_url https://headscale.wiro.world
121 control_url http://localhost:3006
122
123 ephemeral
124 }
125 '';
126
127 virtualHosts.${website-hostname}.extraConfig =
128 # TODO: host website on server with automatic deployment
129 ''
130 reverse_proxy https://mrnossiom.github.io {
131 header_up Host {http.request.host}
132 }
133 '';
134
135 virtualHosts.${static-hostname}.extraConfig = ''
136 root /var/www/static
137 file_server browse
138 '';
139 };
140
141 # TODO: use bind to declare dns records declaratively
142 };
143}