{ lib, pkgs, config, namespace, ... }: with lib; with lib.${namespace}; let cfg = config.${namespace}.services.ente; cfgWeb = cfg.web; cfgApi = cfg.api; defaultUser = "ente"; defaultGroup = "ente"; dataDir = "/var/lib/ente"; yamlFormat = pkgs.formats.yaml { }; enteApp = cfgWeb.package.override { extraBuildEnv = { NEXT_PUBLIC_ENTE_ENDPOINT = "https://ente-api.monapona.dev"; NEXT_TELEMETRY_DISABLED = "1"; }; }; in { options.${namespace}.services.ente = { nginx = { enable = mkEnableOption "Enable nginx for this service." // { default = true; }; }; web = { enable = mkEnableOption "Ente-Auth-Web"; package = mkOption { description = "The package of Ente-Auth to use."; type = types.package; default = pkgs.awesome-flake.ente-web-auth; }; domain = mkOption { description = "The domain to serve ente-auth on."; type = types.nullOr types.str; default = "ente.stahl.sh"; }; }; api = { enable = mkEnableOption "Ente-API"; package = mkOption { description = "The package of Ente-API to use."; type = types.package; default = pkgs.museum; }; user = mkOption { type = types.str; default = defaultUser; description = "User under which museum runs."; }; group = mkOption { type = types.str; default = defaultGroup; description = "Group under which museum runs."; }; domain = mkOption { description = "The domain to serve the API on."; type = types.nullOr types.str; default = "ente-api.stahl.sh"; }; enableLocalDB = mkEnableOption "the automatic creation of a local postgres database for museum."; settings = mkOption { description = '' Museum yaml configuration. Refer to upstream [local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml) for more information. You can specify secret values in this configuration by setting `somevalue._secret = "/path/to/file"` instead of setting `somevalue` directly. ''; default = { }; type = types.submodule { freeformType = yamlFormat.type; options = { db = { host = mkOption { type = types.str; default = "/run/postgresql"; description = "The database host"; }; port = mkOption { type = types.port; default = 5432; description = "The database port"; }; name = mkOption { type = types.str; default = "ente"; description = "The database name"; }; user = mkOption { type = types.str; default = "ente"; description = "The database user"; }; }; }; }; }; }; }; config = mkMerge [ (mkIf cfgApi.enable { services.postgresql = mkIf cfgApi.enableLocalDB { enable = true; ensureUsers = [ { name = "ente"; ensureDBOwnership = true; } ]; ensureDatabases = [ "ente" ]; }; ${namespace}.services.ente.api.settings = { log-file = mkDefault ""; db = mkIf cfgApi.enableLocalDB { host = "/run/postgresql"; port = 5432; name = "ente"; user = "ente"; }; }; systemd.services.ente = { description = "Ente.io Museum API Server"; after = [ "network.target" ] ++ optional cfgApi.enableLocalDB "postgresql.service"; requires = optional cfgApi.enableLocalDB "postgresql.service"; wantedBy = [ "multi-user.target" ]; preStart = '' # Setup paths mkdir -p ${dataDir}/configurations cp ${yamlFormat.generate "local.yaml" cfgApi.settings} ${dataDir}/configurations/local.yml ''; serviceConfig = { ExecStart = getExe cfgApi.package; Type = "simple"; Restart = "on-failure"; AmbientCapablities = [ ]; CapabilityBoundingSet = [ ]; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateMounts = true; PrivateTmp = true; PrivateUsers = false; ProcSubset = "pid"; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "strict"; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = "@system-service"; UMask = "077"; BindReadOnlyPaths = [ "${cfgApi.package}/share/museum/migrations:${dataDir}/migrations" "${cfgApi.package}/share/museum/mail-templates:${dataDir}/mail-templates" ]; User = cfgApi.user; Group = cfgApi.group; SyslogIdentifier = "ente"; StateDirectory = "ente"; WorkingDirectory = dataDir; RuntimeDirectory = "ente"; }; # Environment MUST be called local, otherwise we cannot log to stdout environment = { ENVIRONMENT = "local"; GIN_MODE = "release"; }; }; users = { users = mkIf (cfgApi.user == defaultUser) { ${defaultUser} = { description = "ente.io museum service user"; inherit (cfgApi) group; isSystemUser = true; home = dataDir; }; }; groups = mkIf (cfgApi.group == defaultGroup) { ${defaultGroup} = { }; }; }; services.nginx = mkIf cfg.nginx.enable { enable = true; upstreams.museum = { servers."localhost:8080" = { }; extraConfig = '' zone museum 64k; keepalive 20; ''; }; virtualHosts.${cfgApi.domain} = { forceSSL = true; useACMEHost = "stahl.sh"; locations."/".proxyPass = "http://museum"; extraConfig = '' client_max_body_size 4M; ''; }; }; }) (mkIf cfgWeb.enable { networking.firewall.allowedTCPPorts = mkIf cfg.nginx.enable [ 80 443 ]; awesome-flake.services.acme.enable = mkIf cfg.nginx.enable true; services.nginx = mkIf cfg.nginx.enable { enable = true; virtualHosts."${cfgWeb.domain}" = { forceSSL = true; useACMEHost = "stahl.sh"; locations."/".root = enteApp; }; }; }) ]; }