From 645e86a443d3f74cc8f9c95cb433cbefed5263cb Mon Sep 17 00:00:00 2001 From: Jakub Stachurski Date: Sun, 25 Jan 2026 14:11:14 +0100 Subject: Inital commit for working omega-relay host. This commit includes: - Mysql module from umbriel - Disko configuration for the Ferox VM - Freshrss module - Stalwart module - Vaultwarden module - Wakapi module - Uptime Kuma module - Support for using mysql socket - Server user that does not depend on full home-manager preset. - ACME for wilkuu.xyz domains, including all the services. --- .sops.yaml | 5 ++ certs/wilkuu_rsa.pub | 1 + flake.lock | 22 ++++++ flake.nix | 7 ++ hosts/apocalypse/default.nix | 3 + hosts/omega-relay/default.nix | 74 +++++++++++++++++- hosts/omega-relay/disko.nix | 52 +++++++++++++ hosts/omega-relay/firewall.nix | 2 +- hosts/omega-relay/vm.nix | 18 +++-- modules/default.nix | 2 +- secrets/omega-relay/freshrss.yaml | 27 +++++++ secrets/omega-relay/stalwart.yaml | 26 +++++++ secrets/omega-relay/vaultwarden.yaml | 27 +++++++ secrets/omega-relay/wakapi.yaml | 26 +++++++ services/email.nix | 142 +++++++++++++++++++++++++++++++++++ services/freshrss.nix | 81 ++++++++++++++++++++ services/mysql.nix | 125 ++++++++++++++++++++++++++++++ services/uptimekuma.nix | 78 +++++++++++++++++++ services/vaultwarden.nix | 76 +++++++++++++++++++ services/wakapi.nix | 110 +++++++++++++++++++++++++++ users/wilkuu-server.nix | 19 ++--- 21 files changed, 902 insertions(+), 21 deletions(-) create mode 100644 certs/wilkuu_rsa.pub create mode 100644 hosts/omega-relay/disko.nix create mode 100644 secrets/omega-relay/freshrss.yaml create mode 100644 secrets/omega-relay/stalwart.yaml create mode 100644 secrets/omega-relay/vaultwarden.yaml create mode 100644 secrets/omega-relay/wakapi.yaml create mode 100644 services/email.nix create mode 100644 services/freshrss.nix create mode 100644 services/mysql.nix create mode 100644 services/uptimekuma.nix create mode 100644 services/vaultwarden.nix create mode 100644 services/wakapi.nix diff --git a/.sops.yaml b/.sops.yaml index 1bb53dd..0903cbd 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,6 +1,7 @@ keys: - &admin_wilkuu age1hsxrd9shau86k7zz9wfdeplldyg5c2al6umnr7rn6tf7ewus64uswufr0a - &server_apocalypse age19cw4x4ycrdhfdkwvp009czmvfhucpdtn8lme4ja0rvq7er87z3wsd2fxz7 + - &server_omega_relay age13x9grwfpqjgvjfcnwg5snuntp5emze85hqc8qwuz9nfphfe2pg4skl29r4 creation_rules: - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$ @@ -12,4 +13,8 @@ creation_rules: age: - *admin_wilkuu - *server_apocalypse + - path_regex: secrets/omega-relay/[^/]+\.(yaml|json|env|ini)$ + age: + - *admin_wilkuu + - *server_omega_relay diff --git a/certs/wilkuu_rsa.pub b/certs/wilkuu_rsa.pub new file mode 100644 index 0000000..de478a5 --- /dev/null +++ b/certs/wilkuu_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCc+ueAAfocDsWyNVyzW6NK59I0v79xUALL9gX2OAmh3zKdpoMq1/MiVvdI73HWs9pRzaeH1JrySjENdvScoj1FeEIu2Q1D8bJ9WlTZzXNwwGsVd9IyCPWwwQYah6toZXlpSrpC9pHwGvnjzN6Yz6ka1BDnZTa1bGrGEKMiADIL9yaRAObHcjIVJ9eonViUn3wjyZr3FWUuXXJtfROOAxvWH1OdVJK39Y1QFgp0H4FjAJ3dfJlV81qDMy1+uVTzEfatrm8DUnfmS9xFsTn++KLKUmUXknyeQQ0c0dh/RkG6vbzaNOVZ7YMyFyiJsXM2xLNbWxMDBx+71fYWjR+5I4zBpe19EoARf1ZB78GMOxOJcX0PS1c8jDoLUzZKII3QjDZi81OvhApno6wvyHXHk/3edh5pxCae/rMa1OH9W6Ncdk5CE2JCozR7vdm20NzeEjXtJJzN3pPzm0CrOeQRIsodzV7c9OBKDJz7NjjD9aZxnf9X50edslMxz0Xw0AkWgac= j.stachurski@student.utwente.nl diff --git a/flake.lock b/flake.lock index 3e3351a..f8248ba 100644 --- a/flake.lock +++ b/flake.lock @@ -15,6 +15,27 @@ "type": "github" } }, + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1768920986, + "narHash": "sha256-CNzzBsRhq7gg4BMBuTDObiWDH/rFYHEuDRVOwCcwXw4=", + "owner": "nix-community", + "repo": "disko", + "rev": "de5708739256238fb912c62f03988815db89ec9a", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "latest", + "repo": "disko", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -87,6 +108,7 @@ }, "root": { "inputs": { + "disko": "disko", "home-manager": "home-manager", "nixpkgs": "nixpkgs", "nixpkgs-stable": "nixpkgs-stable", diff --git a/flake.nix b/flake.nix index 8148ee3..aacd740 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,11 @@ url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + disko = { + url = "github:nix-community/disko/latest"; + inputs.nixpkgs.follows = "nixpkgs"; + }; home-manager = { url = "github:nix-community/home-manager"; @@ -31,6 +36,7 @@ self, nixpkgs, treefmt-nix, + disko, ... }@inputs: let @@ -112,6 +118,7 @@ ./users/wilkuu-server.nix ./hosts/omega-relay inputs.home-manager.nixosModules.default + disko.nixosModules.disko inputs.sops-nix.nixosModules.sops ]; diff --git a/hosts/apocalypse/default.nix b/hosts/apocalypse/default.nix index 5e13448..6882cbb 100644 --- a/hosts/apocalypse/default.nix +++ b/hosts/apocalypse/default.nix @@ -9,6 +9,9 @@ ## TODO REMOVE LATER TO PREVENT ELI FROM BUILDING REMOTELY boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; + networking.hosts = { + "127.0.0.1" = ["omega-relay.local"]; + }; ## Addons for this system addons = { diff --git a/hosts/omega-relay/default.nix b/hosts/omega-relay/default.nix index 25a8f30..4154e2c 100644 --- a/hosts/omega-relay/default.nix +++ b/hosts/omega-relay/default.nix @@ -1,18 +1,87 @@ { pkgs, lib, config, ...}: { imports = [ ./firewall.nix + ./disko.nix ./vm.nix + ../../services/mysql.nix + ../../services/email.nix + ../../services/vaultwarden.nix + ../../services/uptimekuma.nix + ../../services/freshrss.nix + ../../services/wakapi.nix ]; + addons = { desktop.hyprland.enable = lib.mkForce false; + # desktop.cosmic.enable = lib.mkForce false; desktop.xfce.enable = lib.mkForce false; gpg.enable = true; }; - boot.loader.grub = { - useOSProber = true; + enable = true; + efiSupport = false; + }; + + environment.systemPackages = with pkgs; [ + lynx + chawan + ]; + + wilkuu.services = let + isVM = config.addons.virtualisation.isTestVM; + in { + stalwart = { + enable = true; + domain = if isVM then "mail.omega-relay.local" else "mail.wilkuu.xyz"; + doACME = !isVM; + }; + + vaultwarden = { + enable = true; + signupWhitelist = ["wilkuu.xyz" "omega-relay.local"]; + backupDir = "/srv/data/vaultwarden"; + domain = if isVM then "vaultwarden.omega-relay.local" else "vaultwarden.wilkuu.xyz"; + doACME = !isVM; + }; + uptimekuma = { + enable = true; + domain = if isVM then "uptime.omega-relay.local" else "uptime.wilkuu.xyz"; + dataDir = "/srv/data/uptimekuma"; + doACME = !isVM; + }; + freshrss = { + enable = true; + domain = if isVM then "rss.omega-relay.local" else "rss.wilkuu.xyz"; + doACME = !isVM; + }; + wakapi = { + enable = true; + domain = if isVM then "wakapi.omega-relay.local" else "wakapi.wilkuu.xyz"; + doACME = !isVM; + }; + }; + + # TODO: Make a nginx module + security.acme = lib.mkIf (!config.addons.virtualisation.isTestVM) { + acceptTerms = true; + defaults.email = "jakub@wilkuu.xyz"; + }; + services.nginx = let + isVM = config.addons.virtualisation.isTestVM; + domain = if isVM then "omega-relay.local" else "wilkuu.xyz"; + in { + enable = true; + virtualHosts."${domain}" = { + enableACME = !isVM; + addSSL = !isVM; + locations."/" = { + root = "/srv/www/wilkuu.xyz/"; + index = "index.html"; + tryFiles = "$uri $uri/ =404"; + }; + }; }; networking.hostName = "omega-relay"; @@ -35,7 +104,6 @@ }; }; - diff --git a/hosts/omega-relay/disko.nix b/hosts/omega-relay/disko.nix new file mode 100644 index 0000000..9694e2a --- /dev/null +++ b/hosts/omega-relay/disko.nix @@ -0,0 +1,52 @@ +{pkgs, config, lib, ...}: { + config = lib.mkIf (!config.addons.virtualisation.isTestVM) ({ + services.btrfs.autoScrub = { + enable = true; + interval = "weekly"; + }; + + boot.loader.grub.device = "/dev/sda"; + # Workaround + boot.loader.grub.devices = lib.mkForce ["/dev/sda"]; + boot.loader.efi.canTouchEfiVariables = false; + + # TODO: Mount points + + # Disko for formatting + disko.devices = { + disk = { + main-disk = { + device = "/dev/sda"; + type = "disk"; + content = { + type = "gpt"; + partitions = { + BOOT = { + type = "EF02"; + size = "1M"; + }; + ESP = { + type = "EF00"; + size = "128M"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "btrfs"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; + }); +} diff --git a/hosts/omega-relay/firewall.nix b/hosts/omega-relay/firewall.nix index 7b04813..6311337 100644 --- a/hosts/omega-relay/firewall.nix +++ b/hosts/omega-relay/firewall.nix @@ -1,7 +1,7 @@ { ... }: let baseTCP = [ - 20 22 433 + 20 22 25 80 143 443 993 465 ]; baseUDP = [ 16555 # Wireguard diff --git a/hosts/omega-relay/vm.nix b/hosts/omega-relay/vm.nix index a0da57b..06310f9 100644 --- a/hosts/omega-relay/vm.nix +++ b/hosts/omega-relay/vm.nix @@ -1,17 +1,18 @@ {lib, config,...}: let - tcpFromHost = gport: hport: { + forward = proto: gport: hport: { from = "host"; - proto = "tcp"; + proto = proto; host = { port = hport; - address = "10.0.69.1"; + # address = "10.0.69.1"; }; guest = { port = gport; - address = "10.0.69.2"; + # address = "10.0.69.2"; }; }; in { + # TODO: Make this into a more global module. virtualisation.vmVariant = { addons.virtualisation.isTestVM = true; addons.virtualisation.guest = true; @@ -26,12 +27,15 @@ in { }; virtualisation = { forwardPorts = [ - # (tcpFromHost 80 8080) - # (tcpFromHost 22 8022) + (forward "tcp" 80 9080) + (forward "tcp" 443 9443) + (forward "tcp" 143 9143) + (forward "tcp" 25 9025) + (forward "tcp" 22 9022) ]; memorySize = 2048; # Use 2048MiB memory. cores = 3; graphics = false; }; - }; + }; } diff --git a/modules/default.nix b/modules/default.nix index 774b40d..90f65d8 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -36,7 +36,7 @@ time.timeZone = "Europe/Amsterdam"; # Set timezone sops.defaultSopsFile = ./secrets/secrets.yaml; - sops.age.sshKeyPaths = [ "/etc/ssh_host_ed25519_key" ]; + sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.age.keyFile = "/var/lib/sops-nix/key.txt"; sops.age.generateKey = true; diff --git a/secrets/omega-relay/freshrss.yaml b/secrets/omega-relay/freshrss.yaml new file mode 100644 index 0000000..d6ad4b6 --- /dev/null +++ b/secrets/omega-relay/freshrss.yaml @@ -0,0 +1,27 @@ +fresh-rss: + admin_password: ENC[AES256_GCM,data:aAKl2jCR4w==,iv:xYQXINEeywuyg8+guhS+kv79L/crXLquJp+k6l4vUkI=,tag:4UA4D9jcYtIGHh1FHBv7Nw==,type:str] + db_password: ENC[AES256_GCM,data:Ov79Vw1I8A==,iv:T7oJEszUQoNyK8xxSg9jUEEmGlAVHTm98idrcLBWC3E=,tag:dow8CF+HwZFDh7PjF1dSUg==,type:str] +sops: + age: + - recipient: age1hsxrd9shau86k7zz9wfdeplldyg5c2al6umnr7rn6tf7ewus64uswufr0a + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxNjgybzlud0F0Tnd5UzA2 + YkNTTGsyR1EvZGM3bGdQY3V5cnZWbWtiVGd3CnZac1RER3NhYisrVm9TVDcva1Nx + Z2RBdi9TUWxkY2lTYWlMVFVqYXNGN0kKLS0tIDZtd3hmZGdkMVRSZ0NsY0N6Nk16 + cWs5SElDSjF1RytaTWZlMHl6OUxWRWsK6EVAusVlZb2skuQBa8hfgc9uH3MBgwWE + UoLJgeZ+7NLvj0SY6XbLJVm7mzY9BH1ezSU0ZZlJEJyUdgwc+HdV9w== + -----END AGE ENCRYPTED FILE----- + - recipient: age13x9grwfpqjgvjfcnwg5snuntp5emze85hqc8qwuz9nfphfe2pg4skl29r4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTbTcyelc1YTgvYytESnNy + TnFkNGFhbUg2djdqK3dUeFFpRlVjd2tENW04CnRNUmtmc0NBeWFwYzlNTUNwTmJy + L3RNMDk2S3ZRYUVNUENUSWVNOFpkQTgKLS0tIE56RW95bVRYR0NWdGQrUlRaSFlu + alBWOE94TnA2ZnFEOHg2dElDS0ZuZ3MKem45+6LBH4GXrZpbAn9RUrbF/8A8Ecyd + oOjFC7bRxEDSbq1bU9LV4PurnjIFXJjrZbFlIZkiqQz8IfievsKY/w== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-01-25T12:43:00Z" + mac: ENC[AES256_GCM,data:SzUHVvZ9eDMMuXFSsmqxEPvpbLDd+b0x8c9vkypCduLyoi7W2kvM4hT/F5hWbdDcCcI262BETceMq19a3hkH9H0I2U8zxnYkPZuPjCI53v9Rb4tQkhrSc35afHijcVOsQmfqoP/olvESdNi+Db1ghkay8C3Dl7/SJ+J6chClmFs=,iv:ayViaJ5Ammcqi2mmf1y6W2BcLcheoMwlPN7CHEFaXYc=,tag:harswwEu4WVISwz6KyxDdg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.11.0 diff --git a/secrets/omega-relay/stalwart.yaml b/secrets/omega-relay/stalwart.yaml new file mode 100644 index 0000000..6cb5cba --- /dev/null +++ b/secrets/omega-relay/stalwart.yaml @@ -0,0 +1,26 @@ +stalwart: + user_admin_password: ENC[AES256_GCM,data:oHKLJRLYCbVO7AQuzqwOd40jTV5Y7M76do9EduXpbas=,iv:u+jwIZL8c603XlbuANNYjXTY1vKan0xO3eWTiW6NlQo=,tag:C81dfQOuqtqnXPxJv5QuHw==,type:str] +sops: + age: + - recipient: age1hsxrd9shau86k7zz9wfdeplldyg5c2al6umnr7rn6tf7ewus64uswufr0a + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnc0NEREVWOVNCemtja1hx + RTZNMFFhdllrNTUrRzIxUCtJYW9YaStzMXlZCk5BOEFDRWJ4d0wrNVdRZmNxSUtq + OTV5Y1p5Q1hrdm4vci9STHRtSzQzbEEKLS0tIFJXdWxjUTQ1WVlJZW1aQU5MZXk0 + anNhdWx0dnNML2JOd3A0K2pUMGp3THcKFULx1PTapR1/6f5SnLTdmrj1SIsqUkFS + oQ66VJrsJTUAtYkTPADyLwvTwTrhlGVOLw1S7iuSvvzgkb9j5k8lIg== + -----END AGE ENCRYPTED FILE----- + - recipient: age13x9grwfpqjgvjfcnwg5snuntp5emze85hqc8qwuz9nfphfe2pg4skl29r4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByZ0ZFdVR3QlN6ajJjSDdz + RnlYL1FXaTl5cmxJYW9wYVVlMGNXNDUwNFFBCmRocUM5S3o1aWNBSjNBNmdXYTBh + Z0paUkNldzlkRWw2cnI0cGZwZGw3RzgKLS0tIFpwRGgzYzhCUUljOHZhY2g0cUc3 + L3FBTTEyYU10c3pJUFdodTRwM0RkTVEK5DwewdrHKP8REJN3dCrx2Swkcat/15ep + wH4bb+3fy6jndgaczNpI9MUr6OfsS904mItw879OCIiRrxcjnuN5nw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-01-17T21:45:45Z" + mac: ENC[AES256_GCM,data:ZN8Zmo6yvhuyJKrnAw4btIf6FxEWiQuCCBpWedYGj0S973YRgEbl7Q9HKsYrVOwNwGIMS5MBv+hjjvOMZvJU0331V7PefRy4yzqN10pJLgLwyB63ZrhOdY87q9U8Gz3K1qOoPJievJmyxBwUvbdwO7JWoeiNVxsC+YwGZiJ19yg=,iv:OillC3jS5bILDrZahmsN/sztSlFqrr2BpqzUYjdd7vU=,tag:28az0UH1hVcEq9532yY8nw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.11.0 diff --git a/secrets/omega-relay/vaultwarden.yaml b/secrets/omega-relay/vaultwarden.yaml new file mode 100644 index 0000000..69d49d1 --- /dev/null +++ b/secrets/omega-relay/vaultwarden.yaml @@ -0,0 +1,27 @@ +vaultwarden: + admin_token: ENC[AES256_GCM,data:d37MTTlBAelmO1ZAv9Qg5DlCUVF1YkqzvkCAq4/CPObvyIi7Km7u1zJGb8lCZx8p9qJRGaJQz6CRhusIH4iGWvg317j9WtO2kUYHhrduXkk9/BIDoW/BaN+9FmxEPSmH7Xpyoss/HnGNNce03KCUjqs7ZA81rXfPa6suVn4HV4+GnoiZJBs=,iv:gQncevzr8VBTLaA9Zd2zWH8xKYzuy8kR04xpwFwYNdM=,tag:H4MQfWrOQGkbY2Ghpd0/XA==,type:str] + admin_pass: ENC[AES256_GCM,data:/Acr0LuuWBzMx4Vcz/lV/5HUgNqxyAbhxVpvBFyo,iv:kANWTVPsPo+1VFxSsPqH9sCaH2lFJ5p9X15j12jZ5iU=,tag:4zsUNvFDADvFmW9DmQ/9cw==,type:str] +sops: + age: + - recipient: age1hsxrd9shau86k7zz9wfdeplldyg5c2al6umnr7rn6tf7ewus64uswufr0a + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzSTdRN1EvdG1DMzhMaDRV + VGdQaXE4SjZSVkJFNkRxbEE4blJPREM4Mmh3CmkrZFRvUytaUmpkNGdOSGhlWTJM + RWRRY0piOHBoQTg5cFUyOW03RDZKN1EKLS0tIFJKb0t2YXlvWTMvMm1BRWZRRE9G + NUhUUlUzUkxZNFlBa1lscU53K2RFRTQKrhP4y8tqkwXaSqoYzRNrAVQ9TFrxKOCM + PD3nI/R64Smou/ZHsXqv3EbdCeiccXH9WPoN/BNAh36Y3VsqlHSk9A== + -----END AGE ENCRYPTED FILE----- + - recipient: age13x9grwfpqjgvjfcnwg5snuntp5emze85hqc8qwuz9nfphfe2pg4skl29r4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5K201WTQrOWM3OEQwaUlp + RXlXMDExL0ZWcnR6aU0wNWQrRS9zZG5Ec2tJCnVLb1RTQkh0UkpDckFnRlc5RVo3 + M2dKQjh2NU5rZGNRN281RGR0dC9qbWsKLS0tIDExbldNY1hNenBvNTdHcWVjRzl5 + aUt2YTF4WkE0clFDMWhUSTJsNlhsMHMKtlGJ1nIhBtZ8OM9bbLyinroS1dUpPAmI + EnUnl2X+bACL0OK125T5SNyC2T/tNjO9TOzc9YlaJSlGLUbfwb1U1Q== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-01-24T19:54:55Z" + mac: ENC[AES256_GCM,data:7xNKZSR9CD0U7LhQ0ziHhgafNcvwZdWx+bQAffEVr+zk/1uC4LZn8vpab9Mwkma4ycW0w++LZZxNWx4lDJTLSl/+bGYb13pBGKiai5+4Bi7JIt5z9UAjLasPKTNCZXvXKv4Y2QLGT+Jqqkaa+e92RG3WSdnLwfZpoGUu89v/ifo=,iv:cwYU8650Ro437NpamndB6yMGUsGFxmtmWlwPwyC4VKE=,tag:qJlnztMaE6DhZ2WBQpCzLw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.11.0 diff --git a/secrets/omega-relay/wakapi.yaml b/secrets/omega-relay/wakapi.yaml new file mode 100644 index 0000000..06d0112 --- /dev/null +++ b/secrets/omega-relay/wakapi.yaml @@ -0,0 +1,26 @@ +wakapi: + password_salt: ENC[AES256_GCM,data:u8gNo915MwX9nWxJHsxI1xx+S7384EahefW5SrdqJVQ=,iv:1SCDw0CwgTIPEICmHwWlrzsguDmlBWSw7yWhw5aguJw=,tag:LvJwUGpNVHaj1EWx8XFXbQ==,type:str] +sops: + age: + - recipient: age1hsxrd9shau86k7zz9wfdeplldyg5c2al6umnr7rn6tf7ewus64uswufr0a + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCZ3Y4NkdXeFM2U2svMXdu + U044QlpPQjBsN3VhREtnTTJhempqWmNpNVNZClVGK1JNWUUvODBBSmpleXdqeEtz + YWM5ZVVodGJzZGV5QjBzN1BTRUFiOEEKLS0tIE9FOXFEa3NEd1BHeUIrcW95UUlm + NnFhSFJUZ0Q5bHBJVW95R1VHenU0bjgKHG88y2VtLZ7XoG3iNLda48jk+MvlQg+T + vRfjQIvcChsxZbttZk78DXmz2Z+wj+Cr0kyuZWAhjVJgCRV9owGp1w== + -----END AGE ENCRYPTED FILE----- + - recipient: age13x9grwfpqjgvjfcnwg5snuntp5emze85hqc8qwuz9nfphfe2pg4skl29r4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvblNnQzdLZnV5bFoyYjhL + enBPaHYvUXhNbWdEMy9LYk9aTVZWMkVobTBJClpuNVpTWmVnKzYwNUhFSTJlSDlW + bkpHbDRJelMyaldkcmc0ZVFxQnFNaTAKLS0tIGNKRXZodFBUditMWmd3M3h3L1lS + dHlSTnY0a21xa2FSUFAvNmRZQXZGZ1UKWzPXR/HiynJDO8AHNZn/kEfM3lynsVsp + 9zHFzqeywBwPkqYrxEABVXASk7k9AXw8MTL/mgvUcATkKCWjic6Geg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-01-25T12:52:57Z" + mac: ENC[AES256_GCM,data:tkmq5bwnqmucoRJuD867q4TL/GWPjDvkQO4LUIsVwXtIj+CUmrh3CS9rAMMS+dcMGuG9e9H3hJX3LKWNWIdHTuA6GqIAU94LmmKI57gm1EPmaakJWB9P9gIP/N1IVQS3fc451RSU1cd94Dy1rApTjsspzpBxeed24KqoY67/76E=,iv:cfQqv2s2bSiB7+T/9OZcaA7N05LqvWIBlMr6NfLoTWE=,tag:4/o3V1CS7urudjqUjN5FkQ==,type:str] + unencrypted_suffix: _unencrypted + version: 3.11.0 diff --git a/services/email.nix b/services/email.nix new file mode 100644 index 0000000..d3f60ab --- /dev/null +++ b/services/email.nix @@ -0,0 +1,142 @@ +{config, pkgs, lib, ...}: +let + cfg = config.wilkuu.services.stalwart; + hostname = config.networking.hostName; +in +{ + options.wilkuu.services.stalwart = with lib; { + domain = mkOption { + type = types.str; + default = "mail.${hostname}.local"; + example = "mail.wilkuu.xyz"; + description = "Domain for http connections."; + }; + wellKnownDomains = mkOption { + type = types.listOf types.str; + default = [ "${hostname}.local" ]; + example = [ "wilkuu.xyz"] ; + description = "Domain for well-known items"; + }; + doACME = mkEnableOption "Enable ACME for stalwart here"; + enable = mkEnableOption "Enable the email service"; + dataDir = mkOption { + type = types.path; + description = "Storage localtion for Stalwart user data"; + default = "/srv/data/stalwart"; + example = "/srv/data/stalwart"; + }; + }; + + config = lib.mkIf cfg.enable (let + sopsPath = ../secrets/${hostname}/stalwart.yaml; + secrets = ["user_admin_password"]; + toSops = (sname: "stalwart/${sname}"); + toCredfilePath = (name: config.sops.secrets.${toSops name}.path); + toStalwartCred = name: "%{file:/run/credentials/stalwart-mail.service/${name}}%"; + + basicListener = proto: port: tls: { + bind = ["[::]:${toString port}"]; + protocol = proto; + tls.implicit = tls; + }; + + proxyWellKnown = names: let + uris = map (n: "/.well-known/${n}") names; + in (lib.genAttrs uris (uri: { + proxyPass = "http://localhost:3080${uri}"; + recommendedProxySettings = true; + })); + + makeHttpRedirect = target: https: { return = "302 ${if https then "https" else "http"}://${target}";}; + + in { + networking.hosts = { + "127.0.0.1" = [cfg.domain]; + }; + + # Need this bc otherwise sops will complain for some reason + users = { + groups.stalwart-mail = { }; + users.stalwart-mail = { + isSystemUser = true; + group = "stalwart-mail"; + }; + }; + + + # TODO: Move this into a util function or option; + sops.secrets = + (lib.genAttrs (map toSops secrets) + (name: { + sopsFile = sopsPath; + mode = "0440"; + owner = "stalwart-mail"; + })); + + + services.nginx.virtualHosts = (lib.genAttrs cfg.wellKnownDomains ( + (wdomain: { + locations = (proxyWellKnown ["jmap" "mta-sts.txt" "mail-v1.xml" "autoconfig/mail"]) + // (lib.genAttrs ["/.well_known/caldav" "/.well_known/webdav" ] (uri: (makeHttpRedirect "${cfg.domain}${uri}") cfg.doACME )); + }))) + // + + (lib.genAttrs (map (x: "${x}${cfg.domain}") ["" "autodiscover." "autoconfig."]) (domain: { + addSSL = cfg.doACME; + enableACME = cfg.doACME; + serverName = "${domain}"; + locations."/" = { + proxyPass = "http://localhost:3080"; + recommendedProxySettings = true; + }; + })); + + + services.stalwart-mail = { + enable = true; + dataDir = cfg.dataDir; + openFirewall = false; + credentials = lib.genAttrs secrets toCredfilePath; + settings = { + server.listener = { + smtp = basicListener "smtp" 25 false; + submission = basicListener "smtp" 465 true; + imaptls = basicListener "imap" 993 true; + imap = basicListener "imap" 143 true; + webdav = basicListener "http" 3080 false; + jmap = basicListener "http" 3080 false; + http = basicListener "http" 3080 false; + }; + + store.rocksdb = { + type = "rocksdb"; + path = cfg.dataDir; + compression = "lz4"; + }; + + directory.internal = { + type = "internal"; + store = "rocksdb"; + }; + + storage = { + data = "rocksdb"; + fts = "rocksdb"; + blob = "rocksdb"; + lookup = "rocksdb"; + directory = "internal"; + }; + + authentication.fallback-admin = { + user = "admin"; + secret = toStalwartCred "user_admin_password"; + }; + + http = { + use-x-forwarded = true; + url = "protocol + \"${cfg.domain}\""; + }; + }; + }; + }); +} diff --git a/services/freshrss.nix b/services/freshrss.nix new file mode 100644 index 0000000..0b66755 --- /dev/null +++ b/services/freshrss.nix @@ -0,0 +1,81 @@ +{config, lib, pkgs, ...}: +let + cfg = config.wilkuu.services.freshrss; + hostname = config.networking.hostName; +in +{ + + options.wilkuu.services.freshrss = with lib; { + domain = mkOption { + type = types.str; + default = "rss.${hostname}.local"; + example = "rss.wilkuu.xyz"; + description = "Domain for http connections."; + }; + doACME = mkEnableOption "Enable ACME for fresh-rss here"; + enable = mkEnableOption "Enable the fresh-rss service"; + dataDir = mkOption { + type = types.path; + description = "Storage localtion for fresh-rss data"; + default = "/srv/data/freshrss"; + example = "/srv/data/freshrss"; + }; + }; + + config = lib.mkIf cfg.enable (let + sopsPath = ../secrets/${hostname}/freshrss.yaml; + secrets = ["admin_password" "db_pass"]; + toSops = (sname: "fresh-rss/${sname}"); + in { + networking.hosts = { + "127.0.0.1" = [cfg.domain]; + }; + + sops.secrets = + (lib.genAttrs (map toSops secrets) + (name: { + sopsFile = sopsPath; + mode = "0440"; + owner = config.services.freshrss.user; + })); + + services.nginx.virtualHosts."${cfg.domain}" = { + addSSL = cfg.doACME; + enableACME = cfg.doACME; + }; + + wilkuu.services.mysql = { + enable = true; + users."freshrss" = { + sopsPlaceholder = config.sops.placeholder."fresh-rss/db_pass"; + }; + databases."freshrss" = { + enable = true; + allowedUsers = ["freshrss"]; + }; + }; + + services.freshrss = { + enable = true; + # api.enable = true; + dataDir = cfg.dataDir; + baseUrl = "https://${cfg.domain}"; + extensions = with pkgs.freshrss-extensions; [ + youtube + title-wrap + auto-ttl + reading-time + ]; + passwordFile = config.sops.secrets."fresh-rss/admin_password".path; + virtualHost = cfg.domain; + database = { + passFile = config.sops.secrets."fresh-rss/db_pass".path; + host = "localhost"; + port = config.wilkuu.services.mysql.port; + name = "freshrss"; + user = "freshrss"; + type = "mysql"; + }; + }; + }); +} diff --git a/services/mysql.nix b/services/mysql.nix new file mode 100644 index 0000000..8a7b5e2 --- /dev/null +++ b/services/mysql.nix @@ -0,0 +1,125 @@ +{ + pkgs, + config, + lib, + ... +}: +let + cfg = config.wilkuu.services.mysql; + + create_users_ensure = + uname: + (lib.genAttrs (lib.map (dn: "${dn}.*") ( + lib.attrNames (lib.filterAttrs (_: dcfg: (builtins.elem uname dcfg.allowedUsers)) cfg.databases) + )) (_: "ALL PRIVILEGES")); + + priviledge_clause = + name: db: priv: + ("GRANT ${priv} ON ${db} TO ${name};"); + + add-user-clauses = + name: ucfg: + if (!isNull ucfg.sopsPlaceholder) then + ( + '' + -- Clauses for user ${name} + ALTER USER IF EXISTS '${name}'@'%' IDENTIFIED BY '${ucfg.sopsPlaceholder}'; + CREATE USER IF NOT EXISTS '${name}'@'%' IDENTIFIED BY '${ucfg.sopsPlaceholder}'; + '' + + (lib.concatMapAttrsStringSep "\n" (priviledge_clause name) (create_users_ensure name)) + ) + else + " -- Ommitted user ${name}"; + + add-unix-user-clauses = + name: '' + -- Clauses for user ${name} + ALTER USER IF EXISTS '${name}'@'localhost' IDENTIFIED BY unix_socket'; + CREATE USER IF NOT EXISTS '${name}'@'localhost' IDENTIFIED BY unix_socket'; + '' + + (lib.concatMapAttrsStringSep "\n" (priviledge_clause name) (create_users_ensure name)); + +in +{ + options.wilkuu.services.mysql = { + enable = lib.mkEnableOption "Enable database for containers"; + port = + with lib; + mkOption { + type = types.port; + default = 3306; + }; + databases = + with lib; + mkOption { + type = types.attrsOf ( + types.submodule { + options = { + enable = mkEnableOption "Enable the database"; + allowedUsers = mkOption { + type = types.listOf types.str; + }; + }; + } + ); + default = { }; + }; + users = + with lib; + mkOption { + type = types.attrsOf ( + types.submodule { + options = { + scramPassword = mkOption { + type = types.nullOr types.str; + default = null; + }; + sopsPlaceholder = mkOption { + type = types.nullOr types.str; + default = null; + }; + allowedRanges = mkOption { + type = types.listOf types.str; + }; + }; + } + ); + default = { }; + }; + unix_users = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Users that can identify using the unix socket"; + default = []; + example = ["wakapi"]; + }; + }; + # config.sops.secrets = lib.mkIf cfg.enable { + # "database/root_pass" = { + # sopsFile = ../secrets/${config.networking.hostName}/secrets.yaml; + # }; + # }; + config.sops.templates."init-mysql" = { + owner = config.systemd.services.mysql.serviceConfig.User; + content = (lib.concatLines ((builtins.attrValues (builtins.mapAttrs add-user-clauses cfg.users)) ++ (map add-unix-user-clauses cfg.unix_users))); + }; + + config.services.mysql = { + enable = cfg.enable; + ensureDatabases = builtins.attrNames cfg.databases; + initialScript = config.sops.templates."init-mysql".path; + package = pkgs.mariadb; + settings = { + mysqld = { + log_error = "/var/log/mysql_err.log"; + log_warnings = 2; + }; + }; + }; + + # config.host-config.utilpkgs = lib.mkIf (cfg.enable) ( + # with pkgs; + # [ + # mycli + # ] + # ); +} diff --git a/services/uptimekuma.nix b/services/uptimekuma.nix new file mode 100644 index 0000000..4cdd5f2 --- /dev/null +++ b/services/uptimekuma.nix @@ -0,0 +1,78 @@ +{config, lib, pkgs, ...}: +let + cfg = config.wilkuu.services.uptimekuma; + hostname = config.networking.hostName; +in +{ + options.wilkuu.services.uptimekuma = with lib; { + domain = mkOption { + type = types.str; + default = "uptime.${hostname}.local"; + example = "uptime.wilkuu.xyz"; + description = "Domain for http connections."; + }; + doACME = mkEnableOption "Enable ACME for uptime kuma here"; + enable = mkEnableOption "Enable the uptime-kuma service"; + dataDir = mkOption { + type = types.path; + description = "Storage localtion for uptime kuma data, currently ignored, because nixpkgs sucks"; + default = "/srv/data/uptimekuma"; + example = "/srv/data/uptimekuma"; + }; + }; + + config = lib.mkIf cfg.enable (let + # sopsPath = ../secrets/${hostname}/vaultwarden.yaml; + # secrets = []; + # toSops = (sname: "uptime-kuma/${sname}"); + in { + networking.hosts = { + "127.0.0.1" = [cfg.domain]; + }; + + users.users.uptimekuma = { + isSystemUser = true; + group = "uptimekuma"; + }; + users.groups.uptimekuma = {}; + + systemd.services.uptime-kuma.serviceConfig.User = "uptimekuma"; + + # sops.secrets = + # (lib.genAttrs (map toSops secrets) + # (name: { + # sopsFile = sopsPath; + # mode = "0440"; + # owner = "uptime-kuma"; + # })); + + services.nginx.virtualHosts."${cfg.domain}" = { + addSSL = cfg.doACME; + enableACME = cfg.doACME; + locations."/" = { + proxyPass = "http://localhost:3111"; + recommendedProxySettings = true; + }; + }; + + wilkuu.services.mysql = let + user = config.systemd.services.uptime-kuma.serviceConfig.User; + in { + unix_users = [user]; + databases.uptimekuma = { + enable = true; + allowedUsers = [user]; + }; + }; + + services.uptime-kuma = { + enable = true; + settings = { + UPTIME_KUMA_PORT = "3111"; + UPTIME_KUMA_HOST="127.0.0.1"; + UPTIME_KUMA_DB_TYPE = "sqlite"; + UPTIME_KUMA_DB_SOCKET = "/var/lib/mysql/mysql.sock"; + }; + }; + }); +} diff --git a/services/vaultwarden.nix b/services/vaultwarden.nix new file mode 100644 index 0000000..f68a2fa --- /dev/null +++ b/services/vaultwarden.nix @@ -0,0 +1,76 @@ +{config, pkgs, lib, ...}: +let + cfg = config.wilkuu.services.vaultwarden; + hostname = config.networking.hostName; +in +{ + options.wilkuu.services.vaultwarden = with lib; { + domain = mkOption { + type = types.str; + default = "bitwarden.${hostname}.local"; + example = "bitwarden.wilkuu.xyz"; + description = "Domain for http connections."; + }; + doACME = mkEnableOption "Enable ACME for vaultwarden here"; + enable = mkEnableOption "Enable the vaultwarden service"; + backupDir = mkOption { + type = types.path; + description = "Storage localtion for Vaultwarden user data backup"; + default = "/srv/data/vaultwarden"; + example = "/srv/data/vaultwarden"; + }; + signupWhitelist = mkOption { + type = types.listOf types.str; + default = []; + example = ["wilkuu.xyz"]; + description = "Domains that can sign up on vaultwarden"; + }; + }; + + config = lib.mkIf cfg.enable (let + sopsPath = ../secrets/${hostname}/vaultwarden.yaml; + secrets = ["admin_token"]; + toSops = (sname: "vaultwarden/${sname}"); + toCredfilePath = (name: config.sops.secrets.${toSops name}.path); + in { + networking.hosts = { + "127.0.0.1" = [cfg.domain]; + }; + + sops.secrets = + (lib.genAttrs (map toSops secrets) + (name: { + sopsFile = sopsPath; + mode = "0440"; + owner = "vaultwarden"; + })); + + sops.templates.vaultwardenEnvFile.content = '' + ADMIN_TOKEN=${config.sops.placeholder."vaultwarden/admin_token"} + ''; + + services.nginx.virtualHosts."${cfg.domain}" = { + enableACME = cfg.doACME; + addSSL = cfg.doACME; + locations."/" = { + proxyPass = "http://localhost:3222"; + recommendedProxySettings = true; + }; + }; + + services.vaultwarden = { + enable = cfg.enable; + backupDir = "/srv/data/vaultwarden"; + config = { + DOMAIN=cfg.domain; + ROCKET_ADDRESS = "127.0.0.1"; + ROCKET_PORT ="3222"; + SIGNUPS_DOMAINS_WHITELIST=(lib.concatStringsSep "," cfg.signupWhitelist); + SIGNUPS_ALLOWED="false"; + IP_HEADER="X-Forwarded-For"; + }; + environmentFile = config.sops.templates.vaultwardenEnvFile.path; + }; + + }); +} diff --git a/services/wakapi.nix b/services/wakapi.nix new file mode 100644 index 0000000..1b1e312 --- /dev/null +++ b/services/wakapi.nix @@ -0,0 +1,110 @@ +{config, lib, pkgs, ...}: +let + cfg = config.wilkuu.services.wakapi; + hostname = config.networking.hostName; + service_user = config.systemd.services.wakapi.serviceConfig.User; +in +{ + options.wilkuu.services.wakapi = with lib; { + domain = mkOption { + type = types.str; + default = "wakapi.${hostname}.local"; + example = "wakapi.wilkuu.xyz"; + description = "Domain for http connections."; + }; + email = mkOption { + type = types.str; + default = "wakapi@${hostname}.local"; + example = "noreply@wilkuu.xyz"; + description = "Mailer address"; + }; + doACME = mkEnableOption "Enable ACME for wakapi here"; + enable = mkEnableOption "Enable the wakapi service"; + dataDir = mkOption { + type = types.path; + description = "Storage localtion for wakapi data"; + default = "/srv/data/wakapi"; + example = "/srv/data/wakapi"; + }; + }; + + config = lib.mkIf cfg.enable (let + sopsPath = ../secrets/${hostname}/wakapi.yaml; + secrets = ["password_salt"]; + toSops = (sname: "wakapi/${sname}"); + in { + networking.hosts = { + "127.0.0.1" = [cfg.domain]; + }; + + sops.secrets = + (lib.genAttrs (map toSops secrets) + (name: { + sopsFile = sopsPath; + mode = "0440"; + owner = service_user; + })); + + services.nginx.virtualHosts."${cfg.domain}" = { + enableACME = cfg.doACME; + addSSL = cfg.doACME; + locations."/" = { + proxyPass = "http://localhost:3111"; + recommendedProxySettings = true; + }; + }; + + wilkuu.services.mysql = let + in { + unix_users = [service_user]; + databases.wakapi = { + enable = true; + allowedUsers = [service_user]; + }; + }; + + services.wakapi = { + enable = true; + stateDir = cfg.dataDir; + passwordSaltFile = config.sops.secrets.wakapi/password_salt; + settings = { + server = { + port = 3111; + public_url = cfg.domain; + }; + app = { + leaderboard_enabled = false; + leaderboard_require_auth = true; + inactive_days = 7; # time of previous days within a user must have logged in to be considered active + # go time format strings to format human-readable dates + # for details, check https://pkg.go.dev/time#Time.Format + date_format= "Mon, 02 Jan 2006"; + datetime_format= "Mon, 02 Jan 2006 15:04"; + }; + db = { + socket= "/var/lib/mysql/mysql.sock"; + name = "wakapi"; + dialect = "mysql"; + charset = "utf8mb4"; + }; + security = { + insecure_cookies = false; + trust_reverse_proxy_ips= "127.0.0.1"; + }; + mail = { + # FIXME: Add email + enabled = false; + provider = "smtp"; + sender = ""; + smtp = { + }; + }; + }; + database = { + dialect = "mysql"; + createLocally = false; + }; + + }; + }); +} diff --git a/users/wilkuu-server.nix b/users/wilkuu-server.nix index 732366f..ba5fec2 100644 --- a/users/wilkuu-server.nix +++ b/users/wilkuu-server.nix @@ -9,6 +9,7 @@ programs.adb.enable = true; users.users.wilkuu = { shell = pkgs.zsh; + openssh.authorizedKeys.keyFiles = [../certs/wilkuu_rsa.pub ]; isNormalUser = true; initialPassword = "PleazeChangeThis123"; extraGroups = [ @@ -34,15 +35,15 @@ (_: {enable = true; }); # homeapps.vnc = true; - services.wayvnc = { - enable = true; - autoStart = false; - settings = { - # Todo, bind to vpn and LAN explicitly somehow - address = "0.0.0.0"; - port = 5900; - }; - }; + # services.wayvnc = { + # enable = true; + # autoStart = false; + # settings = { + # # Todo, bind to vpn and LAN explicitly somehow + # address = "0.0.0.0"; + # port = 5900; + # }; + # }; home.username = "wilkuu"; home.homeDirectory = "/home/wilkuu"; -- cgit v1.3.1