diff --git a/nixos-infra/hosts/servers/dns01/configuration.nix b/nixos-infra/hosts/servers/dns01/configuration.nix index 573cf47..79e647c 100644 --- a/nixos-infra/hosts/servers/dns01/configuration.nix +++ b/nixos-infra/hosts/servers/dns01/configuration.nix @@ -4,6 +4,8 @@ imports = [ # Module for LXC containers ../../../modules/machine-types/lxc + # Technitium DNS Server service module + ../../../modules/services/dns/default.nix ]; # Explicitly enable LXC machine type @@ -13,7 +15,14 @@ networking.hostName = "dns01"; networking.useDHCP = true; - # TODO: Add DNS service module import and configuration - # imports = [ ../../../modules/services/dns/default.nix ]; - # services.dns.enable = true; -} \ No newline at end of file + # Technitium DNS Server — primary DNS server + services.dns = { + enable = true; + recursion = "AllowOnlyForPrivateNetworks"; + forwarders = [ "1.1.1.1" "8.8.8.8" ]; + # Uncomment and configure with agenix secret: + # adminPasswordFile = config.age.secrets.dns-admin-password.path; + allowZoneTransfer = [ "10.40.128.11" ]; # Allow secondary to dns02 + listenAddresses = [ "10.40.128.10" "127.0.0.1" "::1" ]; + }; +} diff --git a/nixos-infra/hosts/servers/dns02/configuration.nix b/nixos-infra/hosts/servers/dns02/configuration.nix index 7943bfd..37cf5e1 100644 --- a/nixos-infra/hosts/servers/dns02/configuration.nix +++ b/nixos-infra/hosts/servers/dns02/configuration.nix @@ -4,6 +4,8 @@ imports = [ # Module for LXC containers ../../../modules/machine-types/lxc + # Technitium DNS Server service module + ../../../modules/services/dns/default.nix ]; # Explicitly enable LXC machine type @@ -13,7 +15,13 @@ networking.hostName = "dns02"; networking.useDHCP = true; - # TODO: Add DNS service module import and configuration - # imports = [ ../../../modules/services/dns/default.nix ]; - # services.dns.enable = true; -} \ No newline at end of file + # Technitium DNS Server — secondary (replica) DNS server + services.dns = { + enable = true; + recursion = "AllowOnlyForPrivateNetworks"; + forwarders = [ "1.1.1.1" "8.8.8.8" ]; + # Uncomment and configure with agenix secret: + # adminPasswordFile = config.age.secrets.dns-admin-password.path; + listenAddresses = [ "10.40.128.11" "127.0.0.1" "::1" ]; + }; +} diff --git a/nixos-infra/hosts/servers/git01/configuration.nix b/nixos-infra/hosts/servers/git01/configuration.nix index 23006c0..970c32b 100644 --- a/nixos-infra/hosts/servers/git01/configuration.nix +++ b/nixos-infra/hosts/servers/git01/configuration.nix @@ -4,7 +4,7 @@ imports = [ # Module for LXC containers ../../../modules/machine-types/lxc - # Module for the git forge service + # Module for the git forge service (Forgejo) ../../../modules/services/git-forge/default.nix ]; @@ -15,6 +15,12 @@ networking.hostName = "git01"; networking.useDHCP = true; - # TODO: Enable and configure the git forge service - # services.git-forge.enable = true; -} \ No newline at end of file + # Forgejo — self-hosted git forge + services.git-forge = { + enable = true; + domain = "git.lagraula.fr"; + sshPort = 2222; + httpPort = 3000; + databaseType = "sqlite3"; + }; +} diff --git a/nixos-infra/hosts/servers/hyper01/configuration.nix b/nixos-infra/hosts/servers/hyper01/configuration.nix index e69de29..0226d0a 100644 --- a/nixos-infra/hosts/servers/hyper01/configuration.nix +++ b/nixos-infra/hosts/servers/hyper01/configuration.nix @@ -0,0 +1,3 @@ +# Do not modify!! +# Proxmox hypervisor on NixOS is not mature enough. +# Hypervisor are here for future reference only. \ No newline at end of file diff --git a/nixos-infra/hosts/servers/hyper02/configuration.nix b/nixos-infra/hosts/servers/hyper02/configuration.nix index e69de29..0226d0a 100644 --- a/nixos-infra/hosts/servers/hyper02/configuration.nix +++ b/nixos-infra/hosts/servers/hyper02/configuration.nix @@ -0,0 +1,3 @@ +# Do not modify!! +# Proxmox hypervisor on NixOS is not mature enough. +# Hypervisor are here for future reference only. \ No newline at end of file diff --git a/nixos-infra/hosts/servers/pass01/configuration.nix b/nixos-infra/hosts/servers/pass01/configuration.nix index 6c05404..b46285d 100644 --- a/nixos-infra/hosts/servers/pass01/configuration.nix +++ b/nixos-infra/hosts/servers/pass01/configuration.nix @@ -4,7 +4,7 @@ imports = [ # Module for LXC containers ../../../modules/machine-types/lxc - # Module for password manager service + # Module for password manager service (Vaultwarden) ../../../modules/services/password-manager/default.nix ]; @@ -15,6 +15,14 @@ networking.hostName = "pass01"; networking.useDHCP = true; - # TODO: Enable and configure the password manager service - # services.password-manager.enable = true; -} \ No newline at end of file + # Vaultwarden — Bitwarden-compatible password manager + services.password-manager = { + enable = true; + domain = "pass.lagraula.fr"; + port = 8080; + dbBackend = "sqlite"; + signupsAllowed = false; # Only admin creates accounts + # Uncomment and configure with agenix secret: + # adminTokenFile = config.age.secrets.vaultwarden-admin-token.path; + }; +} diff --git a/nixos-infra/modules/services/dns/default.nix b/nixos-infra/modules/services/dns/default.nix index e69de29..1014050 100644 --- a/nixos-infra/modules/services/dns/default.nix +++ b/nixos-infra/modules/services/dns/default.nix @@ -0,0 +1,116 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.dns; + dnsPkg = if cfg.package != null then cfg.package else pkgs.technitium-dns-server; + + # Build the config.json for Technitium DNS Server. + # The server reads this file on startup from its config directory. + configJson = { + WebServicePort = cfg.webPort; + DNSListenerPort = cfg.dnsPort; + Recursion = cfg.recursion; + Forwarders = cfg.forwarders; + Log = false; + CachePrefetch = false; + AllowTtlOverride = true; + } // lib.optionalAttrs (cfg.adminPasswordFile != null) { + # Password hash will be set by the activation script on first run + # using the value from adminPasswordFile. + } // lib.optionalAttrs (cfg.listenAddresses != [ ]) { + ListenAddresses = cfg.listenAddresses; + } // lib.optionalAttrs (cfg.allowZoneTransfer != [ ]) { + AllowZoneTransfer = cfg.allowZoneTransfer; + } // cfg.extraConfig; + + configFile = pkgs.writeText "technitium-dns-config.json" + (builtins.toJSON configJson); +in + +{ + imports = [ + ./options.nix + ]; + + config = lib.mkIf cfg.enable { + + environment.systemPackages = [ dnsPkg ]; + + # Create the config directory and deploy initial config.json + systemd.tmpfiles.rules = [ + "d ${cfg.configDir} 0750 dns dns - -" + ]; + + systemd.services.technitium-dns-server = { + description = "Technitium DNS Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + wants = [ "network-online.target" ]; + + # Generate a password hash if adminPasswordFile is provided. + # The server is stopped on first run if no password hash exists, + # so we pre-seed the config with the hashed password. + preStart = '' + if [ -f "${cfg.configDir}/config.json" ]; then + # Config already exists, do not overwrite + true + else + install -m 0640 ${configFile} ${cfg.configDir}/config.json + ${lib.optionalString (cfg.adminPasswordFile != null) '' + if [ -f "${cfg.adminPasswordFile}" ]; then + # .NET-compatible SHA256 hash of the password + PASSWORD=$(cat "${cfg.adminPasswordFile}" | tr -d '\n') + HASH=$(echo -n "$PASSWORD" | ${pkgs.openssl}/bin/openssl dgst -sha256 -hex | cut -d' ' -f2) + ${pkgs.jq}/bin/jq \ + ".AdminPassword = \"$HASH\" | .Pbkdf2Iterations = 600000" \ + ${cfg.configDir}/config.json > ${cfg.configDir}/config.json.tmp + mv ${cfg.configDir}/config.json.tmp ${cfg.configDir}/config.json + fi + ''} + fi + ''; + + serviceConfig = { + Type = "simple"; + ExecStart = "${dnsPkg}/bin/technitium-dns-server ${cfg.configDir}"; + User = "dns"; + Group = "dns"; + Restart = "on-failure"; + RestartSec = "5s"; + LimitNOFILE = 1048576; + # Protect the system + ProtectSystem = "full"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = [ cfg.configDir ]; + }; + }; + + # Create the dns system user and group + users.users.dns = { + description = "Technitium DNS Server daemon user"; + group = "dns"; + isSystemUser = true; + home = cfg.configDir; + createHome = true; + }; + + users.groups.dns = { }; + + # Open firewall ports for DNS (UDP/TCP 53) and optionally the web interface + networking.firewall = lib.mkMerge [ + { + allowedTCPPorts = [ cfg.dnsPort ]; + allowedUDPPorts = [ cfg.dnsPort ]; + } + # Allow web admin access only if listenAddresses restricts it to localhost + (lib.mkIf (cfg.listenAddresses == [ ] || builtins.elem "127.0.0.1" cfg.listenAddresses) { + allowedTCPPorts = [ cfg.webPort ]; + }) + ]; + + # Ensure DNS resolution is available locally before starting + networking.nameservers = lib.mkAfter [ "127.0.0.1" ]; + }; +} \ No newline at end of file diff --git a/nixos-infra/modules/services/dns/options.nix b/nixos-infra/modules/services/dns/options.nix index e69de29..8d57cc2 100644 --- a/nixos-infra/modules/services/dns/options.nix +++ b/nixos-infra/modules/services/dns/options.nix @@ -0,0 +1,79 @@ +{ config, lib, ... }: + +let + inherit (lib) types mkOption; +in + +{ + options.services.dns = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the Technitium DNS Server"; + }; + + package = mkOption { + type = types.package; + default = null; + description = "Technitium DNS Server package to use. Defaults to pkgs.technitium-dns-server."; + }; + + webPort = mkOption { + type = types.port; + default = 5380; + description = "HTTP port for the Technitium web administration interface"; + }; + + dnsPort = mkOption { + type = types.port; + default = 53; + description = "DNS server port (both TCP and UDP)"; + }; + + recursion = mkOption { + type = types.enum [ "AllowOnlyForPrivateNetworks" "AllowAll" "DenyAll" ]; + default = "AllowOnlyForPrivateNetworks"; + description = "Recursion policy for DNS queries"; + }; + + forwarders = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Upstream DNS forwarders (e.g. [ \"1.1.1.1\" \"8.8.8.8\" ]). Empty means use root hints"; + }; + + configDir = mkOption { + type = types.str; + default = "/etc/dns"; + description = "Directory for persistent Technitium DNS configuration and zone data"; + }; + + adminPasswordFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to a file containing the admin password for the web interface. + If not set, the default credentials (admin/admin) are used. + Use agenix or sops-nix to provide this file securely. + ''; + }; + + listenAddresses = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "IP addresses to listen on. Empty means listen on all interfaces"; + }; + + allowZoneTransfer = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "IP addresses or subnets allowed to request zone transfers (AXFR/IXFR)"; + }; + + extraConfig = mkOption { + type = types.attrsOf types.anything; + default = { }; + description = "Additional Technitium DNS configuration options as an attribute set"; + }; + }; +} \ No newline at end of file diff --git a/nixos-infra/modules/services/git-forge/default.nix b/nixos-infra/modules/services/git-forge/default.nix index e69de29..7e4e4cd 100644 --- a/nixos-infra/modules/services/git-forge/default.nix +++ b/nixos-infra/modules/services/git-forge/default.nix @@ -0,0 +1,90 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.git-forge; + inherit (lib) mkIf mkOption types; +in + +{ + options.services.git-forge = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the git forge service (Forgejo)"; + }; + + domain = mkOption { + type = types.str; + default = "git.lagraula.fr"; + description = "Domain name for the Forgejo instance"; + }; + + sshPort = mkOption { + type = types.port; + default = 2222; + description = "SSH port for Git operations (avoid conflict with host SSH on 22)"; + }; + + httpPort = mkOption { + type = types.port; + default = 3000; + description = "HTTP port for the Forgejo web interface"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/forgejo"; + description = "Data directory for Forgejo repositories and database"; + }; + + databaseType = mkOption { + type = types.enum [ "sqlite3" "postgres" "mysql" ]; + default = "sqlite3"; + description = "Database backend type"; + }; + + settings = mkOption { + type = types.attrsOf types.anything; + default = { }; + description = "Additional Forgejo settings (merged into services.forgejo.settings)"; + }; + }; + + config = mkIf cfg.enable { + # Use the built-in NixOS forgejo module + services.forgejo = { + enable = true; + package = pkgs.forgejo; + settings = lib.recursiveUpdate { + server = { + DOMAIN = cfg.domain; + HTTP_PORT = cfg.httpPort; + HTTP_ADDR = "0.0.0.0"; + ROOT_URL = "https://${cfg.domain}"; + SSH_PORT = cfg.sshPort; + SSH_LISTEN_PORT = cfg.sshPort; + }; + service = { + DISABLE_REGISTRATION = false; + }; + "repository".ROOT = "${cfg.dataDir}/repos"; + } (lib.mapAttrs (section: values: lib.mapAttrs (key: value: lib.mkDefault value) values) cfg.settings); + + database = { + type = cfg.databaseType; + }; + + dump = { + type = "tar.zst"; + }; + + # LXC container specifics - use the existing forgejo user + stateDir = cfg.dataDir; + }; + + # Open firewall ports for HTTP and SSH (git protocol) + networking.firewall = lib.mkIf config.services.forgejo.enable { + allowedTCPPorts = [ cfg.httpPort cfg.sshPort ]; + }; + }; +} \ No newline at end of file diff --git a/nixos-infra/modules/services/password-manager/default.nix b/nixos-infra/modules/services/password-manager/default.nix index e69de29..3cf690f 100644 --- a/nixos-infra/modules/services/password-manager/default.nix +++ b/nixos-infra/modules/services/password-manager/default.nix @@ -0,0 +1,90 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.password-manager; + inherit (lib) mkIf mkOption types; +in + +{ + options.services.password-manager = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the password manager service (Vaultwarden)"; + }; + + domain = mkOption { + type = types.str; + default = "pass.lagraula.fr"; + description = "Domain name for the Vaultwarden instance"; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "HTTP port for the Vaultwarden web interface"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/vaultwarden"; + description = "Data directory for Vaultwarden persistent state"; + }; + + dbBackend = mkOption { + type = types.enum [ "sqlite" "mysql" "postgresql" ]; + default = "sqlite"; + description = "Database backend type"; + }; + + adminTokenFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to a file containing the admin token for the /admin panel. + Use agenix or sops-nix to provide this file securely. + ''; + }; + + signupsAllowed = mkOption { + type = types.bool; + default = true; + description = "Allow new user registration"; + }; + + extraConfig = mkOption { + type = types.attrsOf (types.nullOr (types.oneOf [ types.bool types.str types.int types.port ])); + default = { }; + description = "Additional Vaultwarden config options as attribute set (mapped to env vars)"; + }; + }; + + config = mkIf cfg.enable { + # Use the built-in NixOS vaultwarden module + services.vaultwarden = { + enable = true; + package = pkgs.vaultwarden; + webVaultPackage = pkgs.vaultwarden-webvault; + inherit (cfg) dbBackend; + config = { + DOMAIN = "https://${cfg.domain}"; + PORT = cfg.port; + SIGNUPS_ALLOWED = cfg.signupsAllowed; + } // (lib.mapAttrs (name: value: + if value == true then "true" + else if value == false then "false" + else toString value + ) cfg.extraConfig); + } // lib.optionalAttrs (cfg.adminTokenFile != null) { + environmentFile = cfg.adminTokenFile; + config = { + ADMIN_TOKEN = null; # Will be read from environmentFile + }; + }; + + # Open firewall port + networking.firewall = mkIf config.services.vaultwarden.enable { + allowedTCPPorts = [ cfg.port ]; + }; + }; +} \ No newline at end of file