{ inputs, lib, namespace, }: let inherit (inputs.nixpkgs.lib) mapAttrs mkOption types toUpper substring stringLength mkDefault mkForce ; base64Lib = import ../base64 { inherit inputs; }; in rec { # Conditionally enable modules based on system enableForSystem = system: modules: builtins.filter ( mod: mod.systems or [ ] == [ ] || builtins.elem system (mod.systems or [ ]) ) modules; # Create a module with common options mkModule = { name, description ? "", options ? { }, moduleConfig ? { }, domain ? "services", config, serviceName ? name, }: let cfg = config.${namespace}.${domain}.${name}; upstreamUrl = if cfg.reverseProxy.upstreamUrl != null then cfg.reverseProxy.upstreamUrl else "http://127.0.0.1:${toString cfg.port}"; fqdn = "${cfg.reverseProxy.subdomain}.${cfg.reverseProxy.domain}"; defaultConfig = { # Caddy reverse proxy: when reverseProxy.enable = true, contribute this # service's virtual host block to the Caddy config. The TLS wildcard # cert is handled via a (cloudflare_tls) snippet defined in globalConfig. # services.caddy.virtualHosts.${fqdn} = lib.mkIf cfg.reverseProxy.enable { # extraConfig = '' # import cloudflare_tls # reverse_proxy ${upstreamUrl} # ${cfg.reverseProxy.extraCaddyConfig} # ''; # }; # Open firewall networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; allowedUDPPorts = [ cfg.port ]; }; users = lib.mkIf cfg.createUser { users.${name} = { isSystemUser = true; group = name; home = cfg.configDir; }; groups.${name} = { }; }; systemd.services.${serviceName} = { requires = [ "media-nas-main.mount" # "openvpn-us.protonvpn.udp.service" ]; after = lib.mkForce [ "media-nas-main.mount" # "openvpn-us.protonvpn.udp.service" ]; # serviceConfig = { # NetworkNamespacePath = lib.mkIf cfg.enableVpn "/run/netns/vpn"; # # Consider also setting DNS *inside* the netns (see note below). # }; }; services = { postgresql = lib.mkIf cfg.configureDb { enable = true; ensureDatabases = [ name ]; ensureUsers = [ { name = name; ensureDBOwnership = true; } ]; }; redis.servers.${name} = lib.mkIf cfg.redis.enable { enable = true; port = cfg.redis.port; }; }; # systemd.tmpfiles.rules = [ # "d ${cfg.configDir} 0700 ${name} ${name} - -" # # "d ${cfg.configDir}/server-files 0775 ${name} ${name} - -" # # "d ${cfg.configDir}/user-files 0775 ${name} ${name} - -" # ]; } // moduleConfig; in { lib, ... }: { options.${namespace}.${domain}.${name} = lib.mkOption { type = lib.types.submodule { options = { enable = lib.mkEnableOption description; port = mkOpt types.int 80 "Port for ${name} to be hosted on"; configDir = mkOpt types.str "/media/nas/main/appdata" "Path to the config dir"; dataDir = mkOpt types.str "/media/nas/main" "Path to the data dir"; createUser = mkBoolOpt false "create a user for this module/service"; configureDb = mkBoolOpt false "Manage db for this service"; environmentFile = mkOpt types.str "" "Environment File"; puid = mkOpt types.str "911" "default user id"; pgid = mkOpt types.str "1000" "default group id"; timeZone = mkOpt types.str "America/Chicago" "default timezone"; listenAddress = mkOpt types.str "0.0.0.0" "Environment File"; openFirewall = mkBoolOpt true "Open the firewall"; enableVpn = mkBoolOpt true "Enable routing through VPN"; redis = { enable = lib.mkEnableOption "enable redis"; port = mkOpt types.int 80 "Port for ${name} redis to be hosted on"; }; hashedPassword = mkOpt (types.nullOr types.str) "$y$j9T$EkPXmsmIMFFZ.WRrBYCxS1$P0kwo6e4.WM5DsqUcEqWC3MrZp5KfCjxffraMFZWu06" "Hashed password for code-server authentication"; extraEnvironment = mkOpt (types.attrsOf types.str) { } "Extra environment variables for code-server"; reverseProxy = mkReverseProxyOpt name; } // options; }; default = { }; }; config = lib.mkIf cfg.enable defaultConfig; }; # container mkContainer = { name, localAddress ? "127.0.0.1", ports ? [ 80 ], bindMounts ? { }, config ? { }, }: { lib, ... }: { containers.${name} = { inherit localAddress bindMounts; config = config // { networking = { firewall = { enable = true; allowedTCPPorts = ports; }; # Use systemd-resolved inside the container # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 useHostResolvConf = lib.mkForce false; }; services.resolved.enable = true; system.stateVersion = "23.11"; }; autoStart = lib.mkDefault true; privateNetwork = lib.mkDefault true; hostAddress = lib.mkDefault "10.0.1.3"; }; networking = { nat.forwardPorts = map (port: { destination = lib.mkDefault "${localAddress}:${toString port}"; sourcePort = lib.mkDefault port; }) ports; firewall = { allowedTCPPorts = ports; allowedUDPPorts = ports; }; }; }; # Migrated mjallen utilities # Option creation helpers mkOpt = type: default: description: mkOption { inherit type default description; }; mkOpt' = type: default: mkOpt type default null; mkBoolOpt = mkOpt types.bool; mkBoolOpt' = mkOpt' types.bool; mkReverseProxyOpt = name: { enable = mkBoolOpt false "Enable Caddy reverse proxy for this service"; subdomain = mkOpt types.str name "Subdomain for the service (default: service name)"; domain = mkOpt types.str "mjallen.dev" "Base domain for the reverse proxy"; # Override the upstream URL if the backend is not on localhost at cfg.port. # Leave empty to use http://127.0.0.1: automatically. upstreamUrl = mkOpt (types.nullOr types.str) null "Override upstream URL (e.g. for services on a different host). Defaults to http://127.0.0.1:."; # Extra Caddyfile directives inserted inside the virtual host block. extraCaddyConfig = mkOpt types.lines "" "Extra Caddyfile directives for this virtual host block"; }; # Standard enable/disable patterns enabled = { enable = true; }; disabled = { enable = false; }; # String utilities capitalize = s: let len = stringLength s; in if len == 0 then "" else (toUpper (substring 0 1 s)) + (substring 1 len s); # Boolean utilities boolToNum = bool: if bool then 1 else 0; # Attribute manipulation utilities default-attrs = mapAttrs (_key: mkDefault); force-attrs = mapAttrs (_key: mkForce); nested-default-attrs = mapAttrs (_key: default-attrs); nested-force-attrs = mapAttrs (_key: force-attrs); } // base64Lib