From 6d83b2b2f513e2b6a8197b149c2948e54364a931 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 9 Nov 2025 02:42:22 -0600 Subject: [PATCH] test: forgejo microVM --- modules/config/instances/config/forgejo.nix | 13 +- modules/nixos/default.nix | 8 +- modules/nixos/services/forgejo/default.nix | 393 ++++++++---------- modules/nixos/services/jellyfin/default.nix | 46 +- .../services/samba/sambaCeres/default.nix | 16 +- .../services/samba/sambaEris/default.nix | 8 +- modules/nixos/services/template/default.nix | 10 +- systems/ceres/config/filesystem.nix | 28 +- systems/ceres/config/sops.nix | 11 + systems/eris/config/filesystem.nix | 3 +- systems/mars/config/filesystem.nix | 28 +- 11 files changed, 270 insertions(+), 294 deletions(-) mode change 100755 => 100644 modules/nixos/services/forgejo/default.nix create mode 100755 systems/ceres/config/sops.nix diff --git a/modules/config/instances/config/forgejo.nix b/modules/config/instances/config/forgejo.nix index 531fa2a..0e3c864 100755 --- a/modules/config/instances/config/forgejo.nix +++ b/modules/config/instances/config/forgejo.nix @@ -37,13 +37,14 @@ in }; interface = { id = "vm-${name}"; - mac = "02:00:00:00:00:50"; - idUser = "vm-${name}-user"; - macUser = "02:00:00:00:00:02"; - ip = "192.168.50.50"; - gate = "192.168.50.2"; - ssh = 2200; + mac = "03:00:00:00:00:53"; + idUser = "vmuser-${name}"; + macUser = "04:00:00:00:00:05"; + ip = "192.168.50.153"; + gate = "192.168.50.1"; + ssh = 2205; }; + ssl = { path = ssl; cert = "${ssl}/fullchain.pem"; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index f79996f..47b4035 100755 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -50,7 +50,7 @@ in # comfyui # filesorter # firefly-iii - # forgejo + forgejo # glance jellyfin # logrotate @@ -61,7 +61,7 @@ in # postgresCeres # projectSite # prompter - # sambaCeres + sambaCeres # searx vaultwarden # website @@ -73,8 +73,8 @@ in eris = { imports = builtins.attrValues { inherit (modules) - acmeEris - caddyEris + acme + caddy logrotate microvm # opencloud diff --git a/modules/nixos/services/forgejo/default.nix b/modules/nixos/services/forgejo/default.nix old mode 100755 new mode 100644 index fa78f3f..9d556f9 --- a/modules/nixos/services/forgejo/default.nix +++ b/modules/nixos/services/forgejo/default.nix @@ -1,248 +1,203 @@ { + config, flake, ... }: let inherit (flake.config.people) user0; - inherit (flake.config.services.instances) - forgejo - smtp - web - ; - service = forgejo; - host = forgejo.domains.url0; - secrets = service.secretPaths.path0; - localhost = web.localhost.address1; - sshPort = 22; + inherit (flake.config.services) instances; + serviceCfg = flake.config.services.instances.forgejo; + smtpCfg = flake.config.services.instances.smtp; + hostCfg = flake.config.services.instances.web; + host = serviceCfg.domains.url0; + dns0 = instances.web.dns.provider0; + dns0Path = "dns/${dns0}"; in { - microvm = { - vms = { - forgejo = { - autostart = true; - config = - { config, pkgs, ... }: - { - system.stateVersion = "25.05"; - time.timeZone = "America/Winnipeg"; + users.users.caddy.extraGroups = [ "acme" ]; - users.users.root = { - openssh.authorizedKeys.keys = flake.config.people.users.${user0}.sshKeys; + security.acme.certs."${host}" = { + dnsProvider = dns0; + environmentFile = config.sops.secrets.${dns0Path}.path; + group = "caddy"; + }; + + microvm.vms.forgejo = { + autostart = true; + restartIfChanged = true; + config = { + system.stateVersion = "24.05"; + time.timeZone = "America/Winnipeg"; + users.users.root.openssh.authorizedKeys.keys = flake.config.people.users.${user0}.sshKeys; + services = { + forgejo = { + enable = true; + database.type = "postgres"; + lfs.enable = true; + secrets = { + mailer.PASSWD = "/run/secrets/${serviceCfg.name}-smtp"; + database.PASSWD = "/run/secrets/${serviceCfg.name}-database"; + }; + dump = { + interval = "5:00"; + type = "zip"; + file = "forgejo-backup"; + enable = true; + }; + settings = { + server = { + DOMAIN = host; + ROOT_URL = "https://${host}/"; + HTTP_PORT = serviceCfg.ports.port0; }; - - services = { - forgejo = { - enable = true; - database.type = "postgres"; - lfs.enable = true; - - dump = { - interval = "5:00"; - type = "zip"; - file = "forgejo-backup"; - enable = true; - }; - - settings = { - server = { - DOMAIN = host; - ROOT_URL = "https://${host}/"; - HTTP_PORT = service.ports.port0; - HTTP_ADDR = localhost; - }; - - # If you need to start from scratch, don't forget to turn this off again - service.DISABLE_REGISTRATION = false; - - actions = { - ENABLED = true; - DEFAULT_ACTIONS_URL = "github"; - }; - - mirror.ENABLED = true; - - mailer = { - ENABLED = true; - SMTP_ADDR = smtp.hostname; - FROM = smtp.email.address1; - USER = smtp.email.address1; - PROTOCOL = "${smtp.name}+${smtp.records.record1}"; - SMTP_PORT = smtp.ports.port1; - SEND_AS_PLAIN_TEXT = true; - USE_CLIENT_CERT = false; - }; - }; - }; - - openssh = { - enable = true; - settings = { - PasswordAuthentication = false; - PermitRootLogin = "prohibit-password"; - }; - }; + # If you need to start from scratch, don't forget to turn this off again + service.DISABLE_REGISTRATION = false; + actions = { + ENABLED = true; + DEFAULT_ACTIONS_URL = "github"; }; - - systemd = { - tmpfiles.rules = [ - "d ${secrets} 0755 ${service.name} ${service.name} -" - "d /run/forgejo 0755 ${service.name} ${service.name} -" - ]; - - services.copy-forgejo-secrets = { - description = "Prepare Forgejo secrets environment file"; - before = [ "forgejo.service" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - User = service.name; - Group = service.name; - }; - script = '' - cat > /run/forgejo/env << EOF - FORGEJO__database__PASSWD=$(cat /run/secrets/${service.name}-database) - FORGEJO__mailer__PASSWD=$(cat /run/secrets/${service.name}-smtp) - EOF - chmod 600 /run/forgejo/env - ''; - }; - - services.forgejo = { - serviceConfig = { - EnvironmentFile = "/run/forgejo/env"; - }; - }; - - services.forgejo-dump = { - serviceConfig = { - ExecStartPost = "${pkgs.nushell}/bin/nu -c 'ls ${service.varPaths.path0}/dump | where name =~ forgejo-backup and modified < ((date now) - 7day) | each { rm $in.name }'"; - }; - }; - - network = { - enable = true; - networks."10-enp" = { - matchConfig.Name = "enp0s4"; - - # Option 1: Static IP (recommended if you need consistent IP for reverse proxy) - addresses = [ { Address = "${service.interface.ip}/24"; } ]; - routes = [ - { - Destination = "0.0.0.0/0"; - Gateway = "192.168.50.1"; # Your LAN gateway - adjust if different - } - ]; - dns = [ "192.168.50.1" ]; # Your LAN DNS - adjust if different - - # Option 2: DHCP (uncomment below and comment out above if preferred) - # Note: You'll need to update the Caddy reverse_proxy IP or use hostname - # networkConfig.DHCP = "yes"; - }; - }; + mirror = { + ENABLED = true; }; - - networking.firewall.allowedTCPPorts = [ - sshPort - service.ports.port0 - ]; - - microvm = { - vcpu = 2; - mem = 3096; - hypervisor = "qemu"; - - interfaces = [ - { - type = "tap"; - id = service.interface.id; - mac = service.interface.mac; - } - { - type = "user"; - id = service.interface.idUser; - mac = service.interface.macUser; - } - - ]; - - shares = [ - { - mountPoint = "/nix/.ro-store"; - proto = "virtiofs"; - source = "/nix/store"; - tag = "read_only_nix_store"; - } - { - mountPoint = service.varPaths.path0; - proto = "virtiofs"; - source = service.mntPaths.path0; - tag = "${service.name}_data"; - } - { - mountPoint = service.secretPaths.path0; - proto = "virtiofs"; - source = service.secretPaths.path0; - tag = "${service.name}_secrets"; - } - { - mountPoint = service.ssl.path; - proto = "virtiofs"; - source = service.ssl.path; - tag = "acme_certs"; - } - { - mountPoint = "/run/secrets"; - proto = "virtiofs"; - source = "/var/lib/secrets"; - tag = "run_secrets"; - } - ]; - - forwardPorts = [ - { - from = "host"; - host.port = service.interface.ssh; - guest.port = sshPort; - } - ]; + mailer = { + ENABLED = true; + SMTP_ADDR = smtpCfg.hostname; + FROM = smtpCfg.email.address1; + USER = smtpCfg.email.address1; + PROTOCOL = "${smtpCfg.name}+${smtpCfg.records.record1}"; + SMTP_PORT = smtpCfg.ports.port1; + SEND_AS_PLAIN_TEXT = true; + USE_CLIENT_CERT = false; }; }; + }; + + openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "prohibit-password"; + }; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 22 # SSH + 25 # SMTP + 139 # SMTP + 587 # SMTP + 2525 # SMTP + serviceCfg.ports.port0 + ]; + + fileSystems."/tmp" = { + device = "tmpfs"; + fsType = "tmpfs"; + options = [ + "size=4G" + "mode=1777" + ]; + }; + + systemd = { + network = { + enable = true; + networks."20-lan" = { + matchConfig.Name = "enp0s5"; + addresses = [ { Address = "${serviceCfg.interface.ip}/24"; } ]; + routes = [ + { + Destination = "${hostCfg.localhost.address1}/0"; + Gateway = serviceCfg.interface.gate; + } + ]; + dns = [ + "1.1.1.1" + "8.8.8.8" + ]; + }; + + }; + tmpfiles.rules = [ + "d ${serviceCfg.varPaths.path0} 0755 ${serviceCfg.name} ${serviceCfg.name} -" + ]; + }; + + systemd.services.systemd-networkd.wantedBy = [ "multi-user.target" ]; + + microvm = { + vcpu = 2; + mem = 3072; + hypervisor = "qemu"; + interfaces = [ + { + type = "tap"; + id = serviceCfg.interface.id; + mac = serviceCfg.interface.mac; + } + { + type = "user"; + id = serviceCfg.interface.idUser; + mac = serviceCfg.interface.macUser; + } + ]; + forwardPorts = [ + { + from = "host"; + host.port = serviceCfg.interface.ssh; + guest.port = 22; + } + ]; + shares = [ + { + mountPoint = "/nix/.ro-store"; + proto = "virtiofs"; + source = "/nix/store"; + tag = "read_only_nix_store"; + } + { + mountPoint = "/var/lib/${serviceCfg.name}"; + proto = "virtiofs"; + source = serviceCfg.mntPaths.path0; + tag = "${serviceCfg.name}_data"; + } + { + mountPoint = "/run/secrets"; + proto = "virtiofs"; + source = "/run/secrets"; + tag = "host_secrets"; + } + ]; }; }; }; systemd.tmpfiles.rules = [ - "d ${service.mntPaths.path0} 0755 root root -" - "d ${service.secretPaths.path0} 0755 root root -" + "d ${serviceCfg.mntPaths.path0} 0755 root root -" ]; services.caddy.virtualHosts."${host}" = { extraConfig = '' - reverse_proxy ${service.interface.ip}:${toString service.ports.port0} + reverse_proxy ${serviceCfg.interface.ip}:${toString serviceCfg.ports.port0} { + header_up X-Real-IP {remote_host} + } - tls ${service.ssl.cert} ${service.ssl.key} + tls ${serviceCfg.ssl.cert} ${serviceCfg.ssl.key} + + encode zstd gzip ''; }; - sops = - let - sopsPath = secret: { - path = "${secrets}/${service.name}-${secret}"; - owner = "root"; - mode = "600"; - }; - in - { - secrets = builtins.listToAttrs ( - map - (secret: { - name = "${service.name}/${secret}"; # it's vaultwarden/env, not vaultwarden-env - value = sopsPath secret; - }) - [ - "database" - "smtp" - ] - ); + sops.secrets = { + "${serviceCfg.name}-smtp" = { + owner = "root"; + mode = "0600"; }; + "${serviceCfg.name}-database" = { + owner = "root"; + mode = "0600"; + }; + + }; + } diff --git a/modules/nixos/services/jellyfin/default.nix b/modules/nixos/services/jellyfin/default.nix index 52d5269..e8960df 100755 --- a/modules/nixos/services/jellyfin/default.nix +++ b/modules/nixos/services/jellyfin/default.nix @@ -42,31 +42,35 @@ in serviceCfg.ports.port2 ]; - systemd.network = { - enable = true; - networks."20-lan" = { - matchConfig.Name = "enp0s5"; - addresses = [ { Address = "${serviceCfg.interface.ip}/24"; } ]; - routes = [ - { - Destination = "0.0.0.0/0"; - Gateway = serviceCfg.interface.gate; - } - ]; - dns = [ - "1.1.1.1" - "8.8.8.8" - ]; + systemd = { + network = { + enable = true; + networks."20-lan" = { + matchConfig.Name = "enp0s5"; + addresses = [ { Address = "${serviceCfg.interface.ip}/24"; } ]; + routes = [ + { + Destination = "${hostCfg.localhost.address1}/0"; + Gateway = serviceCfg.interface.gate; + } + ]; + dns = [ + "1.1.1.1" + "8.8.8.8" + ]; + }; }; - }; + services.systemd-networkd.wantedBy = [ "multi-user.target" ]; + tmpfiles.rules = [ + "d ${serviceCfg.varPaths.path0}/media 0755 ${serviceCfg.name} ${serviceCfg.name} -" + ]; - systemd.services.systemd-networkd.wantedBy = [ "multi-user.target" ]; + }; microvm = { vcpu = 4; mem = 4096; hypervisor = "qemu"; - interfaces = [ { type = "tap"; @@ -79,7 +83,6 @@ in mac = serviceCfg.interface.macUser; } ]; - forwardPorts = [ { from = "host"; @@ -87,7 +90,6 @@ in guest.port = 22; } ]; - shares = [ { mountPoint = "/nix/.ro-store"; @@ -110,13 +112,11 @@ in ]; }; - # Increase tmpfs size for /tmp to satisfy Jellyfin's 2GB requirement - # By default, tmpfs is limited to 50% of RAM, but we need more space fileSystems."/tmp" = { device = "tmpfs"; fsType = "tmpfs"; options = [ - "size=3G" + "size=4G" "mode=1777" ]; }; diff --git a/modules/nixos/services/samba/sambaCeres/default.nix b/modules/nixos/services/samba/sambaCeres/default.nix index a98f3d9..ac42d8a 100755 --- a/modules/nixos/services/samba/sambaCeres/default.nix +++ b/modules/nixos/services/samba/sambaCeres/default.nix @@ -18,20 +18,8 @@ in enable = true; openFirewall = true; settings = { - ${instances.jellyfin.name} = { - path = instances.jellyfin.paths.path0; - writable = "true"; - }; - ${instances.audiobookshelf.name} = { - path = instances.audiobookshelf.paths.path0; - writable = "true"; - }; - ${instances.comfyui.name} = { - path = instances.comfyui.paths.path0; - writable = "true"; - }; - ${instances.opencloud.name} = { - path = instances.opencloud.paths.path0; + "storage" = { + path = "/mnt/storage"; writable = "true"; }; }; diff --git a/modules/nixos/services/samba/sambaEris/default.nix b/modules/nixos/services/samba/sambaEris/default.nix index c2e54c3..6681084 100755 --- a/modules/nixos/services/samba/sambaEris/default.nix +++ b/modules/nixos/services/samba/sambaEris/default.nix @@ -17,10 +17,10 @@ in enable = true; openFirewall = true; settings = { - # ${instances.opencloud.name} = { - # path = instances.opencloud.paths.path0; - # writable = "true"; - # }; + "raid0" = { + path = "/mnt/raid0"; + writable = "true"; + }; }; }; }; diff --git a/modules/nixos/services/template/default.nix b/modules/nixos/services/template/default.nix index 22e6e8d..bcf5798 100755 --- a/modules/nixos/services/template/default.nix +++ b/modules/nixos/services/template/default.nix @@ -125,11 +125,6 @@ in }; }; - # Open firewall ports inside the VM - networking.firewall.allowedTCPPorts = [ - serviceCfg.ports.port0 # Service web interface port number - ]; - # ============================================================================ # VM NETWORK CONFIGURATION (systemd-networkd) # ============================================================================ @@ -137,6 +132,11 @@ in # The VM sees a network interface called "enp0s5" which connects to the # host's TAP interface (vm-service) via the bridge (br-vms) }; + + # Open firewall ports inside the VM + networking.firewall.allowedTCPPorts = [ + serviceCfg.ports.port0 # Service web interface port number + ]; systemd.network = { enable = true; # Enable systemd-networkd for network management diff --git a/systems/ceres/config/filesystem.nix b/systems/ceres/config/filesystem.nix index a375523..aa0e4ea 100755 --- a/systems/ceres/config/filesystem.nix +++ b/systems/ceres/config/filesystem.nix @@ -6,9 +6,31 @@ }: let inherit (flake.config.people) user0; + inherit (flake.config.machines.devices) eris; + erisSecrets = config.sops.secrets."network/server".path; rootDevice = "/dev/disk/by-label/root"; bootDevice = "/dev/disk/by-label/BOOT"; + + sambaDrives = [ + "samba0" + ]; + + sambaFolders = [ + "raid0" + ]; + + sambaMounts = sambaDrive: folder: { + name = "${eris.${sambaDrive}.mount}/${folder}"; + value = { + device = "${eris.${sambaDrive}.device}/${folder}"; + fsType = "cifs"; + options = eris.${sambaDrive}.options ++ [ + "credentials=${erisSecrets}" + ]; + }; + }; + in { fileSystems = { @@ -54,7 +76,11 @@ in "/etc/ssh" = { neededForBoot = true; }; - }; + } + // (builtins.listToAttrs ( + builtins.concatMap (drive: map (folder: sambaMounts drive folder) sambaFolders) sambaDrives + )); + boot.initrd.postResumeCommands = lib.mkAfter '' mkdir /btrfs_tmp mount -o subvolid=5 ${rootDevice} /btrfs_tmp diff --git a/systems/ceres/config/sops.nix b/systems/ceres/config/sops.nix new file mode 100755 index 0000000..1437f5b --- /dev/null +++ b/systems/ceres/config/sops.nix @@ -0,0 +1,11 @@ +{ + sops = { + secrets = { + "network/server" = { + path = "/var/lib/secrets/server"; + owner = "root"; + mode = "600"; + }; + }; + }; +} diff --git a/systems/eris/config/filesystem.nix b/systems/eris/config/filesystem.nix index c7f264a..98353a1 100755 --- a/systems/eris/config/filesystem.nix +++ b/systems/eris/config/filesystem.nix @@ -5,7 +5,6 @@ }: let inherit (flake.config.machines.devices) ceres; - inherit (flake.config.services) instances; ceresSecrets = config.sops.secrets."network/server".path; sambaDrives = [ @@ -13,7 +12,7 @@ let ]; sambaFolders = [ - instances.jellyfin.name + "storage" ]; sambaMounts = sambaDrive: folder: { diff --git a/systems/mars/config/filesystem.nix b/systems/mars/config/filesystem.nix index 602eaac..73801e0 100755 --- a/systems/mars/config/filesystem.nix +++ b/systems/mars/config/filesystem.nix @@ -5,9 +5,8 @@ }: let inherit (flake.config.people) user0; - inherit (flake.config.machines.devices) mars ceres; - inherit (flake.config.services) instances; - ceresSecrets = config.sops.secrets."network/server".path; + inherit (flake.config.machines.devices) mars eris; + erisSecrets = config.sops.secrets."network/server".path; in { fileSystems = @@ -23,10 +22,7 @@ in ]; sambaFolders = [ - instances.audiobookshelf.name - instances.jellyfin.name - instances.comfyui.name - # instances.opencloud.name + "raid0" ]; storageMounts = storageDrive: { @@ -39,12 +35,12 @@ in }; sambaMounts = sambaDrive: folder: { - name = "${ceres.${sambaDrive}.mount}/${folder}"; + name = "${eris.${sambaDrive}.mount}/${folder}"; value = { - device = "${ceres.${sambaDrive}.device}/${folder}"; + device = "${eris.${sambaDrive}.device}/${folder}"; fsType = "cifs"; - options = ceres.${sambaDrive}.options ++ [ - "credentials=${ceresSecrets}" + options = eris.${sambaDrive}.options ++ [ + "credentials=${erisSecrets}" ]; }; }; @@ -59,11 +55,11 @@ in fsType = "vfat"; options = mars.boot.options; }; - }; - # // (builtins.listToAttrs (map storageMounts storageDrives)) - # // (builtins.listToAttrs ( - # builtins.concatMap (drive: map (folder: sambaMounts drive folder) sambaFolders) sambaDrives - # )); + } + // (builtins.listToAttrs (map storageMounts storageDrives)) + // (builtins.listToAttrs ( + builtins.concatMap (drive: map (folder: sambaMounts drive folder) sambaFolders) sambaDrives + )); swapDevices = [ { device = "/dev/disk/by-uuid/6b56990c-545d-4d00-b93b-8ca1f143882e"; }