diff --git a/flake.nix b/flake.nix index 738c34a1..fea58010 100644 --- a/flake.nix +++ b/flake.nix @@ -222,6 +222,7 @@ self.nixosModules.wallabag self.nixosModules.gonic self.nixosModules.ulogger-server + self.nixosModules.photoprism grasp.nixosModule home-manager.nixosModules.home-manager { diff --git a/hosts/profiles/photoprism/default.nix b/hosts/profiles/photoprism/default.nix new file mode 100644 index 00000000..3cdf8bc0 --- /dev/null +++ b/hosts/profiles/photoprism/default.nix @@ -0,0 +1,51 @@ +{ self, pkgs, config, lib, ... }: + +{ + services.postgresql = { + enable = true; + ensureDatabases = [ "photoprism" ]; + ensureUsers = [{ + name = "photoprism"; + ensurePermissions = { + "DATABASE photoprism" = "ALL PRIVILEGES"; + }; + }]; + }; + + services.photoprism = { + enable = true; + port = 2342; + originalsPath = "/var/lib/private/photoprism/originals"; + address = "0.0.0.0"; + settings = { + PHOTOPRISM_ADMIN_USER = "admin"; + PHOTOPRISM_ADMIN_PASSWORD = "..."; + PHOTOPRISM_DEFAULT_LOCALE = "en"; + PHOTOPRISM_DATABASE_DRIVER = "postgres"; + PHOTOPRISM_DATABASE_NAME = "photoprism"; + PHOTOPRISM_DATABASE_SERVER = "/run/postgresql/"; + PHOTOPRISM_DATABASE_USER = "photoprism"; + PHOTOPRISM_SITE_URL = "http://photos.mossnet.lan:2342"; + PHOTOPRISM_SITE_TITLE = "mossnet photos"; + }; + }; + + services.nginx.virtualHosts."photos.mossnet.lan" = { + enableACME = false; + forceSSL = false; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + clientMaxBodySize = "500m"; + locations."/" = { + proxyPass = "http://127.0.0.1:2342"; + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ''; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index a4e14ac8..50a1b364 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -10,4 +10,5 @@ backup = import ./backup.nix; ulogger-server = import ./ulogger.nix; microbin = import ./microbin.nix; + photoprism = import ./photoprism.nix; #TODO remove once updated to 23.05, stolen from nixpkgs anyway } diff --git a/modules/nixos/photoprism.nix b/modules/nixos/photoprism.nix new file mode 100644 index 00000000..5c902436 --- /dev/null +++ b/modules/nixos/photoprism.nix @@ -0,0 +1,154 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.photoprism; + + env = { + PHOTOPRISM_ORIGINALS_PATH = cfg.originalsPath; + PHOTOPRISM_STORAGE_PATH = cfg.storagePath; + PHOTOPRISM_IMPORT_PATH = cfg.importPath; + PHOTOPRISM_HTTP_HOST = cfg.address; + PHOTOPRISM_HTTP_PORT = toString cfg.port; + } // ( + lib.mapAttrs (_: toString) cfg.settings + ); + + manage = + let + setupEnv = lib.concatStringsSep "\n" (lib.mapAttrsToList (name: val: "export ${name}=${lib.escapeShellArg val}") env); + in + pkgs.writeShellScript "manage" '' + ${setupEnv} + exec ${cfg.package}/bin/photoprism "$@" + ''; +in +{ + meta.maintainers = with lib.maintainers; [ stunkymonkey ]; + + options.services.photoprism = { + + enable = lib.mkEnableOption (lib.mdDoc "Photoprism web server"); + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = lib.mdDoc '' + Admin password file. + ''; + }; + + address = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = lib.mdDoc '' + Web interface address. + ''; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 2342; + description = lib.mdDoc '' + Web interface port. + ''; + }; + + originalsPath = lib.mkOption { + type = lib.types.path; + default = null; + example = "/data/photos"; + description = lib.mdDoc '' + Storage path of your original media files (photos and videos). + ''; + }; + + importPath = lib.mkOption { + type = lib.types.str; + default = "import"; + description = lib.mdDoc '' + Relative or absolute to the `originalsPath` from where the files should be imported. + ''; + }; + + storagePath = lib.mkOption { + type = lib.types.path; + default = "/var/lib/photoprism"; + description = lib.mdDoc '' + Location for sidecar, cache, and database files. + ''; + }; + + package = lib.mkPackageOptionMD pkgs "photoprism" { }; + + settings = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = lib.mdDoc '' + See [the getting-started guide](https://docs.photoprism.app/getting-started/config-options/) for available options. + ''; + example = { + PHOTOPRISM_DEFAULT_LOCALE = "de"; + PHOTOPRISM_ADMIN_USER = "root"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.photoprism = { + description = "Photoprism server"; + + serviceConfig = { + Restart = "on-failure"; + User = "photoprism"; + Group = "photoprism"; + DynamicUser = true; + StateDirectory = "photoprism"; + WorkingDirectory = "/var/lib/photoprism"; + RuntimeDirectory = "photoprism"; + + LoadCredential = lib.optionalString (cfg.passwordFile != null) + "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}"; + + CapabilityBoundingSet = ""; + LockPersonality = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ]; + UMask = "0066"; + } // lib.optionalAttrs (cfg.port < 1024) { + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + }; + + wantedBy = [ "multi-user.target" ]; + environment = env; + + # reminder: easier password configuration will come in https://github.com/photoprism/photoprism/pull/2302 + preStart = '' + ln -sf ${manage} photoprism-manage + + ${lib.optionalString (cfg.passwordFile != null) '' + export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD") + ''} + exec ${cfg.package}/bin/photoprism migrations run -f + ''; + + script = '' + ${lib.optionalString (cfg.passwordFile != null) '' + export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD") + ''} + exec ${cfg.package}/bin/photoprism start + ''; + }; + }; +} diff --git a/secrets/photoprism.age b/secrets/photoprism.age new file mode 100644 index 00000000..d7748dcf --- /dev/null +++ b/secrets/photoprism.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 K2vF/Q gbzVy3hrykVeOCwjPo6Ma8tTTemdtiRwAtX96cA2viE +NFSztfWodmZ4TQFBMgx9e1U2ZO37Nt/MKJEYBBrICrA +-> s.pLZ.|-grease %UVo!F YgUB rG`)8.@w +pYerZHERVJx3dldU27I9vIxh5mizU4cW4F1QTRxk646K0abW7H6rqp6Qyr096fLF +c9egbGKMJFNgWoXWaauMaGCAV2D8km5DHSWVoEMZVkskQSprSUWh +--- SANH4pgKXob6sZ/jhnqkgXFC9QzVbI81HqxvwiayK2Y +4ݞ LHj[ Șp(ll@GRr)P0Q?Gaf \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 7292970c..6d419cd4 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -17,6 +17,7 @@ in "box-wg.age".publicKeys = [ mossnet ]; "wallabag-password.age".publicKeys = [ mossnet ]; "wallabag-secret.age".publicKeys = [ mossnet ]; + "photoprism.age".publicKeys = [ mossnet ]; "borg-password.age".publicKeys = systemOnly; "borg-key.age".publicKeys = systemOnly;