commit 93721815ecbc5db950c107cd1aeb78bbb735f8f8
parent 705e8c575acea8b494365ff2ea5db41ed5ab4bcb
Author: Silas Brack <silasbrack@gmail.com>
Date: Sun, 12 Apr 2026 16:28:56 +0200
chore: refactor into hosts and modules, use proper host names
Diffstat:
14 files changed, 586 insertions(+), 583 deletions(-)
diff --git a/.forgejo/workflows/nixos.yaml b/.forgejo/workflows/nixos.yaml
@@ -14,8 +14,8 @@ jobs:
- name: Build
run: |
- if [[ "$(nix eval .#nixosConfigurations.debian-2gb-hel1-1.pkgs.system)" = '"x86_64-linux"' ]]; then
- nix build .#nixosConfigurations.debian-2gb-hel1-1.config.system.build.toplevel
+ if [[ "$(nix eval .#nixosConfigurations.helios.pkgs.system)" = '"x86_64-linux"' ]]; then
+ nix build .#nixosConfigurations.helios.config.system.build.toplevel
fi
- name: Check
@@ -26,4 +26,5 @@ jobs:
env:
NIX_SSHOPTS: "-i /var/lib/gitea-runner/default/.ssh/id_ed25519 -o UserKnownHostsFile=/var/lib/gitea-runner/default/.ssh/known_hosts"
run: |
- nix run nixpkgs#nixos-rebuild -- --fast --flake .#debian-2gb-hel1-1 --target-host root@46.62.150.48 test
+ nix run nixpkgs#nixos-rebuild -- --fast --flake .#poseidon --target-host root@188.166.127.72 test
+ nix run nixpkgs#nixos-rebuild -- --fast --flake .#helios --target-host root@46.62.150.48 test
diff --git a/.sops.yaml b/.sops.yaml
@@ -2,8 +2,8 @@ keys:
- &users:
- &desktop age1s29qgfupgx7mqn9dk2rndp6xh63f5qan8vqa77ve603wd5m9a9rqkpq7re
- &hosts:
- - &server age1e52v0cr69sycuh59hksx9nk5327vgkemkxqwngd25gtztxhkpups87yd5g
- - &hetzner age1damatvmepexkts3ayg2rfac0nal24tghxnrrvvmpu76wa4gvd58qyn2nwc
+ - &poseidon age1e52v0cr69sycuh59hksx9nk5327vgkemkxqwngd25gtztxhkpups87yd5g
+ - &helios age1damatvmepexkts3ayg2rfac0nal24tghxnrrvvmpu76wa4gvd58qyn2nwc
- &gaia age173q4ncu9rux9ufx54vp0r9369qqkaz4z7nwsyqsyp0whkvv2rynqnnt93w
creation_rules:
- path_regex: secrets.yaml
@@ -11,5 +11,5 @@ creation_rules:
- age:
- *desktop
- *gaia
- - *server
- - *hetzner
+ - *poseidon
+ - *helios
diff --git a/Makefile b/Makefile
@@ -1,11 +1,13 @@
-.PHONY: deploy-digitalocean deploy-hetzner deploy-gaia
+.PHONY: deploy deploy-poseidon deploy-helios deploy-gaia
-## Deploy configuration to production server
-deploy-digitalocean:
- nixos-rebuild test --show-trace --no-reexec --flake .#nixos-s-1vcpu-512mb-10gb-ams3-01 --target-host root@188.166.127.72
+## Deploy configuration to production servers
+deploy: deploy-poseidon deploy-helios
-deploy-hetzner:
- nixos-rebuild test --show-trace --no-reexec --flake .#debian-2gb-hel1-1 --target-host root@46.62.150.48
+deploy-poseidon:
+ nixos-rebuild test --show-trace --no-reexec --flake .#poseidon --target-host root@188.166.127.72
+
+deploy-helios:
+ nixos-rebuild test --show-trace --no-reexec --flake .#helios --target-host root@46.62.150.48
deploy-gaia:
sudo nixos-rebuild switch --flake .#gaia
diff --git a/flake.nix b/flake.nix
@@ -34,14 +34,14 @@
};
in
{
- nixosConfigurations.nixos-s-1vcpu-512mb-10gb-ams3-01 = nixpkgs.lib.nixosSystem {
+ nixosConfigurations.poseidon = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
sops-nix.nixosModules.sops
- (import ./nixos/nixos-s-1vcpu-512mb-10gb-ams3-01.nix)
+ (import ./hosts/poseidon.nix)
];
};
- nixosConfigurations.debian-2gb-hel1-1 = nixpkgs.lib.nixosSystem {
+ nixosConfigurations.helios = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
sops-nix.nixosModules.sops
@@ -50,7 +50,7 @@
pkgs,
...
}:
- import ./nixos/debian-2gb-hel1-1.nix (inputs // { inherit simple-web-app mkv website; })
+ import ./hosts/helios.nix (inputs // { inherit simple-web-app mkv website; })
)
];
};
@@ -59,7 +59,7 @@
specialArgs = { inherit pkgs-unstable; };
modules = [
sops-nix.nixosModules.sops
- ./nixos/gaia/configuration.nix
+ ./hosts/gaia/configuration.nix
home-manager.nixosModules.home-manager
];
};
diff --git a/nixos/gaia/configuration.nix b/hosts/gaia/configuration.nix
diff --git a/nixos/gaia/hardware-configuration.nix b/hosts/gaia/hardware-configuration.nix
diff --git a/nixos/gaia/home.nix b/hosts/gaia/home.nix
diff --git a/nixos/gaia/hyprland.nix b/hosts/gaia/hyprland.nix
diff --git a/nixos/gaia/vpn.nix b/hosts/gaia/vpn.nix
diff --git a/hosts/helios.nix b/hosts/helios.nix
@@ -0,0 +1,366 @@
+{
+ pkgs,
+ config,
+ lib,
+ simple-web-app,
+ mkv,
+ website,
+ ...
+}:
+{
+ # Generic
+ system.stateVersion = "25.05"; # Do not change lightly!
+ environment.systemPackages = with pkgs; [
+ vim
+ wget
+ curl
+ rsync
+ rclone
+ zip
+ unzip
+ ];
+ nix.settings.experimental-features = [
+ "nix-command"
+ "flakes"
+ ];
+ services.openssh.enable = true;
+ networking.firewall = {
+ allowedTCPPorts = [
+ 80
+ 443
+ ];
+ # allowedUDPPorts = [
+ # 10232
+ # ];
+ };
+
+ boot.loader.grub = {
+ enable = false;
+ };
+ fileSystems."/" = {
+ device = "/dev/sda1";
+ fsType = "ext4";
+ };
+ fileSystems."/mnt/volume-hel1-1" = {
+ device = "/dev/sdb";
+ fsType = "ext4";
+ };
+ nix.gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 30d";
+ };
+ nix.settings.auto-optimise-store = true;
+
+ # Specific
+ networking.hostName = "helios";
+ time.timeZone = "Europe/Helsinki";
+
+ imports = [
+ # ../modules/simple-web-app.nix
+
+ # (builtins.fetchTarball {
+ # # Pick a release version you are interested in and set its hash, e.g.
+ # url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.05/nixos-mailserver-nixos-25.05.tar.gz";
+ # # To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
+ # # release="nixos-25.05"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
+ # sha256 = "0la8v8d9vzhwrnxmmyz3xnb6vm76kihccjyidhfg6qfi3143fiwq";
+ # })
+ ];
+
+ sops.defaultSopsFile = ../secrets.yaml;
+ sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
+ sops.secrets.hetzner_wireguard_key = {
+ restartUnits = [ "wg-quick-wg0.service" ];
+ };
+ sops.secrets.mediawiki_admin_password = {
+ owner = "mediawiki";
+ mode = "0400";
+ };
+
+ networking.wg-quick.interfaces = {
+ wg0 = {
+ address = [ "10.100.0.7/24" ];
+ dns = [ "10.100.0.1" ];
+ listenPort = 10232;
+ privateKeyFile = "/run/secrets/hetzner_wireguard_key";
+ peers = [
+ {
+ publicKey = "2jWfr5UmACvuS+0HOfSNgEUYqoqVNnfVDoaatmgEykw=";
+ allowedIPs = [ "10.100.0.0/24" ];
+ endpoint = "188.166.127.72:51820";
+ persistentKeepalive = 25;
+ }
+ ];
+ };
+ };
+
+ users.users = {
+ root.openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPGZOv4PvL/NvBiDFnCh8V6xm03k/C3b8O82z1+tmboI gitea-runner"
+ ];
+ silas = {
+ isNormalUser = true;
+ description = "Silas Brack";
+ home = "/home/silas";
+ extraGroups = [
+ "networkmanager"
+ "wheel"
+ ]; # "keys"
+ openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZ0gNM0od3EE/LL27kYQ/s5MOU7LBNc0pWRWROeINij silas@Lorenzas-MacBook-Pro.local" # Lolo's Macbook Pro
+ ];
+ };
+ };
+
+ security.acme = {
+ acceptTerms = true;
+ defaults.email = "silasbrack@gmail.com";
+ };
+ services.nginx.enable = true;
+ services.nginx.recommendedGzipSettings = true;
+ services.nginx.recommendedBrotliSettings = true; # Better compression than gzip for modern browsers
+ services.nginx.recommendedOptimisation = true; # sendfile, tcp_nopush, tcp_nodelay, keepalive
+ services.nginx.recommendedTlsSettings = true; # Strong TLS defaults
+ services.nginx.recommendedProxySettings = true; # Good defaults for proxied services (Forgejo)
+
+ # Rate limiting zone (10 requests/second per IP, 10MB zone)
+ services.nginx.commonHttpConfig = ''
+ limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=10r/s;
+ '';
+ services.nginx.appendHttpConfig = ''
+ limit_req_status 429;
+ '';
+
+ # Catch-all for unknown hostnames
+ services.nginx.virtualHosts."_" = {
+ default = true;
+ rejectSSL = true;
+ locations."/" = {
+ return = "444";
+ };
+ };
+
+ # MediaWiki
+ services.mediawiki = {
+ enable = true;
+ name = "Silas' Wiki";
+ webserver = "nginx";
+ passwordFile = config.sops.secrets.mediawiki_admin_password.path;
+ database = {
+ type = "postgres";
+ createLocally = true;
+ };
+ nginx.hostName = "wiki.fnarglebeast.com";
+ uploadsDir = "/var/lib/mediawiki/uploads";
+ extensions = {
+ ParserFunctions = null;
+ };
+ extraConfig = ''
+ $wgEnableUploads = true;
+ $wgUseImageMagick = true;
+
+ # Security: disable reading by anonymous users
+ $wgGroupPermissions['*']['read'] = false;
+
+ # Security: disable anonymous editing (require login)
+ $wgGroupPermissions['*']['edit'] = false;
+ $wgGroupPermissions['*']['createpage'] = false;
+ $wgGroupPermissions['*']['createtalk'] = false;
+
+ # Security: disable account creation by anonymous users
+ $wgGroupPermissions['*']['createaccount'] = false;
+
+ # Security: restrict upload file types
+ $wgFileExtensions = ['png', 'gif', 'jpg', 'jpeg', 'webp', 'pdf'];
+ '';
+ };
+ services.nginx.virtualHosts."wiki.fnarglebeast.com" = {
+ enableACME = true;
+ forceSSL = true;
+ extraConfig = ''
+ limit_req zone=ratelimit burst=20 nodelay;
+ '';
+ };
+
+ # Forgejo
+ services.forgejo = {
+ enable = true;
+ database.type = "postgres";
+ lfs.enable = true;
+ settings = {
+ server = {
+ DOMAIN = "git.fnarglebeast.com";
+ ROOT_URL = "https://git.fnarglebeast.com/";
+ HTTP_PORT = 3001;
+ };
+ service = {
+ DISABLE_REGISTRATION = true;
+ };
+ session = {
+ COOKIE_SECURE = true;
+ };
+ };
+ };
+ services.nginx.virtualHosts."git.fnarglebeast.com" = {
+ enableACME = true;
+ forceSSL = true;
+ extraConfig = ''
+ limit_req zone=ratelimit burst=20 nodelay;
+ '';
+ locations."/" = {
+ proxyPass = "http://127.0.0.1:3001";
+ proxyWebsockets = true;
+ };
+ };
+
+ # Personal website
+ services.nginx.virtualHosts."silasbrack.com" = let
+ staticSite = pkgs.runCommand "website" {} ''
+ mkdir -p $out
+ cp ${website}/index.html $out/
+ cp ${website}/style.css $out/
+ cp ${website}/style.min.css $out/
+ cp -r ${website}/posts $out/
+ cp -r ${website}/assets $out/
+ '';
+ in {
+ enableACME = true;
+ forceSSL = true;
+ root = staticSite;
+ extraConfig = ''
+ limit_req zone=ratelimit burst=20 nodelay;
+ '';
+ locations."/" = {
+ index = "index.html";
+ extraConfig = ''
+ try_files $uri $uri.html $uri/ /index.html;
+ '';
+ };
+ };
+ # services.simple-web-app = {
+ # enable = true;
+ # package = simple-web-app.packages."x86_64-linux".default;
+ # port = 8032;
+ # };
+
+ # services.karakeep = {
+ # enable = true;
+ # extraEnvironment = {
+ # PORT = "8360";
+ # DISABLE_SIGNUPS = "true";
+ # };
+ # };
+ # services.nginx.virtualHosts."karakeep.fnarglebeast.com" = {
+ # forceSSL = true;
+ # enableACME = true;
+ # locations."/" = {
+ # proxyPass = "http://127.0.0.1:8360";
+ # };
+ # };
+
+ # services.nginx.virtualHosts."mail.silasbrack.com" = {
+ # forceSSL = true;
+ # enableACME = true;
+ # locations."/" = {
+ # proxyPass = "";
+ # };
+ # };
+ # mailserver = {
+ # enable = true;
+ # fqdn = "mail.silasbrack.com";
+ # domains = [ "silasbrack.com" ];
+ #
+ # # Use Let's Encrypt certificates. Note that this needs to set up a stripped
+ # # down nginx and opens port 80.
+ # certificateScheme = "acme-nginx";
+ #
+ # # A list of all login accounts. To create the password hashes, use
+ # # nix-shell -p mkpasswd --run 'mkpasswd -s'
+ # loginAccounts = {
+ # "test@silasbrack.com" = {
+ # hashedPasswordFile = "/home/silas/.test.txt";
+ # # aliases = [ "postmaster@example.com" ];
+ # };
+ # };
+ # };
+
+ # Create mkv volume directory
+ systemd.tmpfiles.rules = [
+ "d /mnt/volume-hel1-1/mkv 0755 nginx nginx -"
+ ];
+
+ # mkv key-value store
+ systemd.services.mkv = {
+ description = "mkv distributed key-value store";
+ wantedBy = [ "multi-user.target" ];
+ after = [
+ "network-online.target"
+ "wg-quick-wg0.service"
+ "nginx.service"
+ ];
+ wants = [ "network-online.target" ];
+ requires = [ "wg-quick-wg0.service" ];
+ environment = {
+ MKV_DB = "/var/lib/mkv/index.db";
+ MKV_VOLUMES = "http://10.100.0.7:8081,http://10.100.0.4:8081";
+ MKV_REPLICAS = "2";
+ MKV_PORT = "3000";
+ };
+ serviceConfig = {
+ Type = "simple";
+ DynamicUser = true;
+ StateDirectory = "mkv";
+ ExecStart = "${mkv.packages.${pkgs.system}.default}/bin/mkv serve";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+
+ # nginx WebDAV volume server for mkv (VPN only)
+ services.nginx.virtualHosts."mkv-volume" = {
+ listen = [
+ {
+ addr = "10.100.0.7";
+ port = 8081;
+ }
+ ];
+ root = "/mnt/volume-hel1-1/mkv";
+ extraConfig = ''
+ autoindex on;
+ autoindex_format json;
+ dav_methods PUT DELETE;
+ create_full_put_path on;
+ client_max_body_size 256M;
+ '';
+ };
+
+ systemd.services.nginx.serviceConfig = {
+ ReadWritePaths = [ "/mnt/volume-hel1-1/mkv" ];
+ ProtectSystem = lib.mkForce "full"; # downgrade from strict
+ };
+
+ # Allow mkv and volume ports on VPN interface only
+ networking.firewall.interfaces.wg0.allowedTCPPorts = [
+ 3000
+ 8081
+ ];
+
+ # Gitea runner SSH known hosts setup
+ systemd.services.gitea-runner-known-hosts = {
+ description = "Populate Gitea runner SSH known_hosts";
+ wantedBy = [ "multi-user.target" ];
+ before = [ "gitea-runner-default.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ script = ''
+ mkdir -p /var/lib/gitea-runner/default/.ssh
+ ${pkgs.openssh}/bin/ssh-keyscan -H 46.62.150.48 >> /var/lib/gitea-runner/default/.ssh/known_hosts 2>/dev/null || true
+ chmod 644 /var/lib/gitea-runner/default/.ssh/known_hosts
+ '';
+ };
+}
diff --git a/hosts/poseidon.nix b/hosts/poseidon.nix
@@ -0,0 +1,199 @@
+{
+ pkgs,
+ modulesPath,
+ ...
+}:
+{
+ # Generic
+ system.stateVersion = "24.05"; # Do not change lightly!
+ environment.systemPackages = with pkgs; [
+ vim
+ ];
+ nix.settings.experimental-features = [
+ "nix-command"
+ "flakes"
+ ];
+ services.openssh.enable = true;
+ networking.firewall.allowedTCPPorts = [
+ 80
+ 443
+ ];
+
+ boot.loader.grub = {
+ enable = false;
+ };
+ fileSystems."/" = {
+ device = "/dev/sda1";
+ fsType = "ext4";
+ };
+ nix.gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 30d";
+ };
+ nix.settings.auto-optimise-store = true;
+
+ # Specific
+ networking.hostName = "poseidon";
+ time.timeZone = "Europe/Amsterdam";
+
+ imports = [
+ (modulesPath + "/virtualisation/digital-ocean-image.nix")
+ ];
+
+ sops.defaultSopsFile = ../secrets.yaml;
+ sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
+
+ sops.secrets.server_wireguard_key = {
+ restartUnits = [ "wg-quick-wg0.service" ];
+ };
+
+ # VPN
+ networking.nat = {
+ enable = true;
+ externalInterface = "eth0";
+ internalInterfaces = [ "wg0" ];
+ };
+ # Port 53 for DNS, port 51820 for WireGuard
+ networking.firewall = {
+ allowedTCPPorts = [ 53 ];
+ allowedUDPPorts = [
+ 53
+ 51820
+ ];
+ };
+ services.dnsmasq = {
+ enable = true;
+ settings = {
+ interface = "wg0";
+ };
+ };
+ networking.wg-quick.interfaces = {
+ wg0 = {
+ address = [ "10.100.0.1/24" ];
+ listenPort = 51820;
+ privateKeyFile = "/run/secrets/server_wireguard_key";
+
+ postUp = ''
+ ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -A FORWARD -o wg0 -i eth0 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.1/24 -j MASQUERADE
+ '';
+ preDown = ''
+ ${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -D FORWARD -o wg0 -i eth0 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.100.0.1/24 -j MASQUERADE
+ '';
+
+ peers = [
+ {
+ # Thinkpad
+ publicKey = "3owbBawNk7IEI8sHmJjr+Wna7QH5WxYuRGjU+OP4i2M=";
+ allowedIPs = [ "10.100.0.2/32" ];
+ }
+ {
+ # Pixel
+ publicKey = "pTuUtRphmXYKhwInaCIQY7nXRgftNzgKJxleACutrFc=";
+ allowedIPs = [ "10.100.0.3/32" ];
+ }
+ {
+ # Desktop
+ publicKey = "F/UODKJ2SQaRUOb7my2LtvRaN4p/khfW8bstO+09/zo=";
+ allowedIPs = [ "10.100.0.4/32" ];
+ }
+ {
+ # iPad
+ publicKey = "KwQo8pMz0JYHaEV3sbezmpD8jwgbXjaumIKeqWYaeiE=";
+ allowedIPs = [ "10.100.0.5/32" ];
+ }
+ {
+ # Lolo's Macbook Pro
+ publicKey = "qmch+NjlcVOxBcOsfwk2SJIcpN58i54131OS9J2yvmM=";
+ allowedIPs = [ "10.100.0.6/32" ];
+ }
+ {
+ # Hetzner server
+ publicKey = "QE4UoW2vSQMX8o1liA4MSwe4fE5sUB0+Ari5a/CqkSc=";
+ allowedIPs = [ "10.100.0.7/32" ];
+ }
+ {
+ # XPS 13
+ publicKey = "ZkRCc3EpJYl0EmRBEAjf9WXCW0JQvlzeq5h7fUn5aEs=";
+ allowedIPs = [ "10.100.0.8/32" ];
+ }
+ {
+ # Work Macbook pro
+ publicKey = "j+5aLHMTUGQyyjmU55ibA+JXC0pmEvQgQIOOfdMEQUA=";
+ allowedIPs = [ "10.100.0.9/32" ];
+ }
+ ];
+ };
+ };
+
+ users.users = {
+ silas = {
+ isNormalUser = true;
+ description = "Silas Brack";
+ home = "/home/silas";
+ extraGroups = [
+ "networkmanager"
+ "wheel"
+ ]; # "keys"
+ openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZ0gNM0od3EE/LL27kYQ/s5MOU7LBNc0pWRWROeINij silas@Lorenzas-MacBook-Pro.local" # Lolo's Macbook Pro
+ ];
+ };
+ };
+
+ security.acme = {
+ acceptTerms = true;
+ defaults.email = "silasbrack@gmail.com";
+ };
+ services.nginx = {
+ enable = true;
+ virtualHosts."freshrss.fnarglebeast.com" = {
+ forceSSL = true;
+ enableACME = true;
+ };
+ # virtualHosts."livebook.fnarglebeast.com" = {
+ # forceSSL = true;
+ # enableACME = true;
+ # locations."/" = {
+ # proxyPass = "http://127.0.0.1:20123";
+ # proxyWebsockets = true; # needed if you need to use WebSocket
+ # extraConfig =
+ # # required when the target is also TLS server with multiple hosts
+ # "proxy_ssl_server_name on;"
+ # +
+ # # required when the server wants to use HTTP Authentication
+ # "proxy_pass_header Authorization;";
+ # };
+ # };
+ };
+
+ # sops.secrets.livebook_password_env_file = { };
+ # services.livebook = {
+ # enableUserService = true;
+ # environment = {
+ # LIVEBOOK_PORT = 20123;
+ # LIVEBOOK_IFRAME_PORT = 20124;
+ # };
+ # environmentFile = "/run/secrets/livebook_password_env_file";
+ # extraPackages = with pkgs; [
+ # gcc
+ # gnumake
+ # ];
+ # };
+
+ sops.secrets.freshrss_password = {
+ owner = "freshrss";
+ };
+ services.freshrss = {
+ enable = true;
+ defaultUser = "silas";
+ passwordFile = "/run/secrets/freshrss_password";
+ baseUrl = "http://localhost:8080";
+ virtualHost = "freshrss.fnarglebeast.com";
+ };
+}
diff --git a/nixos/simple-web-app.nix b/modules/simple-web-app.nix
diff --git a/nixos/debian-2gb-hel1-1.nix b/nixos/debian-2gb-hel1-1.nix
@@ -1,366 +0,0 @@
-{
- pkgs,
- config,
- lib,
- simple-web-app,
- mkv,
- website,
- ...
-}:
-{
- # Generic
- system.stateVersion = "25.05"; # Do not change lightly!
- environment.systemPackages = with pkgs; [
- vim
- wget
- curl
- rsync
- rclone
- zip
- unzip
- ];
- nix.settings.experimental-features = [
- "nix-command"
- "flakes"
- ];
- services.openssh.enable = true;
- networking.firewall = {
- allowedTCPPorts = [
- 80
- 443
- ];
- # allowedUDPPorts = [
- # 10232
- # ];
- };
-
- boot.loader.grub = {
- enable = false;
- };
- fileSystems."/" = {
- device = "/dev/sda1";
- fsType = "ext4";
- };
- fileSystems."/mnt/volume-hel1-1" = {
- device = "/dev/sdb";
- fsType = "ext4";
- };
- nix.gc = {
- automatic = true;
- dates = "weekly";
- options = "--delete-older-than 30d";
- };
- nix.settings.auto-optimise-store = true;
-
- # Specific
- networking.hostName = "debian-2gb-hel1-1";
- time.timeZone = "Europe/Helsinki";
-
- imports = [
- # ./simple-web-app.nix
-
- # (builtins.fetchTarball {
- # # Pick a release version you are interested in and set its hash, e.g.
- # url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.05/nixos-mailserver-nixos-25.05.tar.gz";
- # # To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
- # # release="nixos-25.05"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
- # sha256 = "0la8v8d9vzhwrnxmmyz3xnb6vm76kihccjyidhfg6qfi3143fiwq";
- # })
- ];
-
- sops.defaultSopsFile = ../secrets.yaml;
- sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
- sops.secrets.hetzner_wireguard_key = {
- restartUnits = [ "wg-quick-wg0.service" ];
- };
- sops.secrets.mediawiki_admin_password = {
- owner = "mediawiki";
- mode = "0400";
- };
-
- networking.wg-quick.interfaces = {
- wg0 = {
- address = [ "10.100.0.7/24" ];
- dns = [ "10.100.0.1" ];
- listenPort = 10232;
- privateKeyFile = "/run/secrets/hetzner_wireguard_key";
- peers = [
- {
- publicKey = "2jWfr5UmACvuS+0HOfSNgEUYqoqVNnfVDoaatmgEykw=";
- allowedIPs = [ "10.100.0.0/24" ];
- endpoint = "188.166.127.72:51820";
- persistentKeepalive = 25;
- }
- ];
- };
- };
-
- users.users = {
- root.openssh.authorizedKeys.keys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPGZOv4PvL/NvBiDFnCh8V6xm03k/C3b8O82z1+tmboI gitea-runner"
- ];
- silas = {
- isNormalUser = true;
- description = "Silas Brack";
- home = "/home/silas";
- extraGroups = [
- "networkmanager"
- "wheel"
- ]; # "keys"
- openssh.authorizedKeys.keys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZ0gNM0od3EE/LL27kYQ/s5MOU7LBNc0pWRWROeINij silas@Lorenzas-MacBook-Pro.local" # Lolo's Macbook Pro
- ];
- };
- };
-
- security.acme = {
- acceptTerms = true;
- defaults.email = "silasbrack@gmail.com";
- };
- services.nginx.enable = true;
- services.nginx.recommendedGzipSettings = true;
- services.nginx.recommendedBrotliSettings = true; # Better compression than gzip for modern browsers
- services.nginx.recommendedOptimisation = true; # sendfile, tcp_nopush, tcp_nodelay, keepalive
- services.nginx.recommendedTlsSettings = true; # Strong TLS defaults
- services.nginx.recommendedProxySettings = true; # Good defaults for proxied services (Forgejo)
-
- # Rate limiting zone (10 requests/second per IP, 10MB zone)
- services.nginx.commonHttpConfig = ''
- limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=10r/s;
- '';
- services.nginx.appendHttpConfig = ''
- limit_req_status 429;
- '';
-
- # Catch-all for unknown hostnames
- services.nginx.virtualHosts."_" = {
- default = true;
- rejectSSL = true;
- locations."/" = {
- return = "444";
- };
- };
-
- # MediaWiki
- services.mediawiki = {
- enable = true;
- name = "Silas' Wiki";
- webserver = "nginx";
- passwordFile = config.sops.secrets.mediawiki_admin_password.path;
- database = {
- type = "postgres";
- createLocally = true;
- };
- nginx.hostName = "wiki.fnarglebeast.com";
- uploadsDir = "/var/lib/mediawiki/uploads";
- extensions = {
- ParserFunctions = null;
- };
- extraConfig = ''
- $wgEnableUploads = true;
- $wgUseImageMagick = true;
-
- # Security: disable reading by anonymous users
- $wgGroupPermissions['*']['read'] = false;
-
- # Security: disable anonymous editing (require login)
- $wgGroupPermissions['*']['edit'] = false;
- $wgGroupPermissions['*']['createpage'] = false;
- $wgGroupPermissions['*']['createtalk'] = false;
-
- # Security: disable account creation by anonymous users
- $wgGroupPermissions['*']['createaccount'] = false;
-
- # Security: restrict upload file types
- $wgFileExtensions = ['png', 'gif', 'jpg', 'jpeg', 'webp', 'pdf'];
- '';
- };
- services.nginx.virtualHosts."wiki.fnarglebeast.com" = {
- enableACME = true;
- forceSSL = true;
- extraConfig = ''
- limit_req zone=ratelimit burst=20 nodelay;
- '';
- };
-
- # Forgejo
- services.forgejo = {
- enable = true;
- database.type = "postgres";
- lfs.enable = true;
- settings = {
- server = {
- DOMAIN = "git.fnarglebeast.com";
- ROOT_URL = "https://git.fnarglebeast.com/";
- HTTP_PORT = 3001;
- };
- service = {
- DISABLE_REGISTRATION = true;
- };
- session = {
- COOKIE_SECURE = true;
- };
- };
- };
- services.nginx.virtualHosts."git.fnarglebeast.com" = {
- enableACME = true;
- forceSSL = true;
- extraConfig = ''
- limit_req zone=ratelimit burst=20 nodelay;
- '';
- locations."/" = {
- proxyPass = "http://127.0.0.1:3001";
- proxyWebsockets = true;
- };
- };
-
- # Personal website
- services.nginx.virtualHosts."silasbrack.com" = let
- staticSite = pkgs.runCommand "website" {} ''
- mkdir -p $out
- cp ${website}/index.html $out/
- cp ${website}/style.css $out/
- cp ${website}/style.min.css $out/
- cp -r ${website}/posts $out/
- cp -r ${website}/assets $out/
- '';
- in {
- enableACME = true;
- forceSSL = true;
- root = staticSite;
- extraConfig = ''
- limit_req zone=ratelimit burst=20 nodelay;
- '';
- locations."/" = {
- index = "index.html";
- extraConfig = ''
- try_files $uri $uri.html $uri/ /index.html;
- '';
- };
- };
- # services.simple-web-app = {
- # enable = true;
- # package = simple-web-app.packages."x86_64-linux".default;
- # port = 8032;
- # };
-
- # services.karakeep = {
- # enable = true;
- # extraEnvironment = {
- # PORT = "8360";
- # DISABLE_SIGNUPS = "true";
- # };
- # };
- # services.nginx.virtualHosts."karakeep.fnarglebeast.com" = {
- # forceSSL = true;
- # enableACME = true;
- # locations."/" = {
- # proxyPass = "http://127.0.0.1:8360";
- # };
- # };
-
- # services.nginx.virtualHosts."mail.silasbrack.com" = {
- # forceSSL = true;
- # enableACME = true;
- # locations."/" = {
- # proxyPass = "";
- # };
- # };
- # mailserver = {
- # enable = true;
- # fqdn = "mail.silasbrack.com";
- # domains = [ "silasbrack.com" ];
- #
- # # Use Let's Encrypt certificates. Note that this needs to set up a stripped
- # # down nginx and opens port 80.
- # certificateScheme = "acme-nginx";
- #
- # # A list of all login accounts. To create the password hashes, use
- # # nix-shell -p mkpasswd --run 'mkpasswd -s'
- # loginAccounts = {
- # "test@silasbrack.com" = {
- # hashedPasswordFile = "/home/silas/.test.txt";
- # # aliases = [ "postmaster@example.com" ];
- # };
- # };
- # };
-
- # Create mkv volume directory
- systemd.tmpfiles.rules = [
- "d /mnt/volume-hel1-1/mkv 0755 nginx nginx -"
- ];
-
- # mkv key-value store
- systemd.services.mkv = {
- description = "mkv distributed key-value store";
- wantedBy = [ "multi-user.target" ];
- after = [
- "network-online.target"
- "wg-quick-wg0.service"
- "nginx.service"
- ];
- wants = [ "network-online.target" ];
- requires = [ "wg-quick-wg0.service" ];
- environment = {
- MKV_DB = "/var/lib/mkv/index.db";
- MKV_VOLUMES = "http://10.100.0.7:8081,http://10.100.0.4:8081";
- MKV_REPLICAS = "2";
- MKV_PORT = "3000";
- };
- serviceConfig = {
- Type = "simple";
- DynamicUser = true;
- StateDirectory = "mkv";
- ExecStart = "${mkv.packages.${pkgs.system}.default}/bin/mkv serve";
- Restart = "on-failure";
- RestartSec = 5;
- };
- };
-
- # nginx WebDAV volume server for mkv (VPN only)
- services.nginx.virtualHosts."mkv-volume" = {
- listen = [
- {
- addr = "10.100.0.7";
- port = 8081;
- }
- ];
- root = "/mnt/volume-hel1-1/mkv";
- extraConfig = ''
- autoindex on;
- autoindex_format json;
- dav_methods PUT DELETE;
- create_full_put_path on;
- client_max_body_size 256M;
- '';
- };
-
- systemd.services.nginx.serviceConfig = {
- ReadWritePaths = [ "/mnt/volume-hel1-1/mkv" ];
- ProtectSystem = lib.mkForce "full"; # downgrade from strict
- };
-
- # Allow mkv and volume ports on VPN interface only
- networking.firewall.interfaces.wg0.allowedTCPPorts = [
- 3000
- 8081
- ];
-
- # Gitea runner SSH known hosts setup
- systemd.services.gitea-runner-known-hosts = {
- description = "Populate Gitea runner SSH known_hosts";
- wantedBy = [ "multi-user.target" ];
- before = [ "gitea-runner-default.service" ];
- serviceConfig = {
- Type = "oneshot";
- RemainAfterExit = true;
- };
- script = ''
- mkdir -p /var/lib/gitea-runner/default/.ssh
- ${pkgs.openssh}/bin/ssh-keyscan -H 46.62.150.48 >> /var/lib/gitea-runner/default/.ssh/known_hosts 2>/dev/null || true
- chmod 644 /var/lib/gitea-runner/default/.ssh/known_hosts
- '';
- };
-}
diff --git a/nixos/nixos-s-1vcpu-512mb-10gb-ams3-01.nix b/nixos/nixos-s-1vcpu-512mb-10gb-ams3-01.nix
@@ -1,199 +0,0 @@
-{
- pkgs,
- modulesPath,
- ...
-}:
-{
- # Generic
- system.stateVersion = "24.05"; # Do not change lightly!
- environment.systemPackages = with pkgs; [
- vim
- ];
- nix.settings.experimental-features = [
- "nix-command"
- "flakes"
- ];
- services.openssh.enable = true;
- networking.firewall.allowedTCPPorts = [
- 80
- 443
- ];
-
- boot.loader.grub = {
- enable = false;
- };
- fileSystems."/" = {
- device = "/dev/sda1";
- fsType = "ext4";
- };
- nix.gc = {
- automatic = true;
- dates = "weekly";
- options = "--delete-older-than 30d";
- };
- nix.settings.auto-optimise-store = true;
-
- # Specific
- networking.hostName = "nixos-s-1vcpu-512mb-10gb-ams3-01";
- time.timeZone = "Europe/Amsterdam";
-
- imports = [
- (modulesPath + "/virtualisation/digital-ocean-image.nix")
- ];
-
- sops.defaultSopsFile = ../secrets.yaml;
- sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
-
- sops.secrets.server_wireguard_key = {
- restartUnits = [ "wg-quick-wg0.service" ];
- };
-
- # VPN
- networking.nat = {
- enable = true;
- externalInterface = "eth0";
- internalInterfaces = [ "wg0" ];
- };
- # Port 53 for DNS, port 51820 for WireGuard
- networking.firewall = {
- allowedTCPPorts = [ 53 ];
- allowedUDPPorts = [
- 53
- 51820
- ];
- };
- services.dnsmasq = {
- enable = true;
- settings = {
- interface = "wg0";
- };
- };
- networking.wg-quick.interfaces = {
- wg0 = {
- address = [ "10.100.0.1/24" ];
- listenPort = 51820;
- privateKeyFile = "/run/secrets/server_wireguard_key";
-
- postUp = ''
- ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
- ${pkgs.iptables}/bin/iptables -A FORWARD -o wg0 -i eth0 -j ACCEPT
- ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.100.0.1/24 -j MASQUERADE
- '';
- preDown = ''
- ${pkgs.iptables}/bin/iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
- ${pkgs.iptables}/bin/iptables -D FORWARD -o wg0 -i eth0 -j ACCEPT
- ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.100.0.1/24 -j MASQUERADE
- '';
-
- peers = [
- {
- # Thinkpad
- publicKey = "3owbBawNk7IEI8sHmJjr+Wna7QH5WxYuRGjU+OP4i2M=";
- allowedIPs = [ "10.100.0.2/32" ];
- }
- {
- # Pixel
- publicKey = "pTuUtRphmXYKhwInaCIQY7nXRgftNzgKJxleACutrFc=";
- allowedIPs = [ "10.100.0.3/32" ];
- }
- {
- # Desktop
- publicKey = "F/UODKJ2SQaRUOb7my2LtvRaN4p/khfW8bstO+09/zo=";
- allowedIPs = [ "10.100.0.4/32" ];
- }
- {
- # iPad
- publicKey = "KwQo8pMz0JYHaEV3sbezmpD8jwgbXjaumIKeqWYaeiE=";
- allowedIPs = [ "10.100.0.5/32" ];
- }
- {
- # Lolo's Macbook Pro
- publicKey = "qmch+NjlcVOxBcOsfwk2SJIcpN58i54131OS9J2yvmM=";
- allowedIPs = [ "10.100.0.6/32" ];
- }
- {
- # Hetzner server
- publicKey = "QE4UoW2vSQMX8o1liA4MSwe4fE5sUB0+Ari5a/CqkSc=";
- allowedIPs = [ "10.100.0.7/32" ];
- }
- {
- # XPS 13
- publicKey = "ZkRCc3EpJYl0EmRBEAjf9WXCW0JQvlzeq5h7fUn5aEs=";
- allowedIPs = [ "10.100.0.8/32" ];
- }
- {
- # Work Macbook pro
- publicKey = "j+5aLHMTUGQyyjmU55ibA+JXC0pmEvQgQIOOfdMEQUA=";
- allowedIPs = [ "10.100.0.9/32" ];
- }
- ];
- };
- };
-
- users.users = {
- silas = {
- isNormalUser = true;
- description = "Silas Brack";
- home = "/home/silas";
- extraGroups = [
- "networkmanager"
- "wheel"
- ]; # "keys"
- openssh.authorizedKeys.keys = [
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGqHJMc2SCzL50SO9Flfnhn012p1dmp4Iph8lPQ2aoe/ silasbrack@gmail.com" # Desktop
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZ0gNM0od3EE/LL27kYQ/s5MOU7LBNc0pWRWROeINij silas@Lorenzas-MacBook-Pro.local" # Lolo's Macbook Pro
- ];
- };
- };
-
- security.acme = {
- acceptTerms = true;
- defaults.email = "silasbrack@gmail.com";
- };
- services.nginx = {
- enable = true;
- virtualHosts."freshrss.fnarglebeast.com" = {
- forceSSL = true;
- enableACME = true;
- };
- # virtualHosts."livebook.fnarglebeast.com" = {
- # forceSSL = true;
- # enableACME = true;
- # locations."/" = {
- # proxyPass = "http://127.0.0.1:20123";
- # proxyWebsockets = true; # needed if you need to use WebSocket
- # extraConfig =
- # # required when the target is also TLS server with multiple hosts
- # "proxy_ssl_server_name on;"
- # +
- # # required when the server wants to use HTTP Authentication
- # "proxy_pass_header Authorization;";
- # };
- # };
- };
-
- # sops.secrets.livebook_password_env_file = { };
- # services.livebook = {
- # enableUserService = true;
- # environment = {
- # LIVEBOOK_PORT = 20123;
- # LIVEBOOK_IFRAME_PORT = 20124;
- # };
- # environmentFile = "/run/secrets/livebook_password_env_file";
- # extraPackages = with pkgs; [
- # gcc
- # gnumake
- # ];
- # };
-
- sops.secrets.freshrss_password = {
- owner = "freshrss";
- };
- services.freshrss = {
- enable = true;
- defaultUser = "silas";
- passwordFile = "/run/secrets/freshrss_password";
- baseUrl = "http://localhost:8080";
- virtualHost = "freshrss.fnarglebeast.com";
- };
-}