diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml index e1317621418d..a65e29614c3f 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml @@ -99,6 +99,14 @@ services.ulogd. + + + photoprism, + a AI-Powered Photos App for the Decentralized Web. Available + as + services.photoprism. + +
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index 1620e98f3aa3..e520a08960e3 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -34,6 +34,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable). +- [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable). + ## Backward Incompatibilities {#sec-release-23.05-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index f0ee3fc93972..a28c5d0309c1 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1165,6 +1165,7 @@ ./services/web-apps/peertube.nix ./services/web-apps/pgpkeyserver-lite.nix ./services/web-apps/phylactery.nix + ./services/web-apps/photoprism.nix ./services/web-apps/pict-rs.nix ./services/web-apps/plantuml-server.nix ./services/web-apps/plausible.nix diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix new file mode 100644 index 000000000000..d5ca6014780a --- /dev/null +++ b/nixos/modules/services/web-apps/photoprism.nix @@ -0,0 +1,155 @@ +{ 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/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index acc42acf37a6..e6c786744d9d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -513,6 +513,7 @@ in { pgjwt = handleTest ./pgjwt.nix {}; pgmanage = handleTest ./pgmanage.nix {}; phosh = handleTest ./phosh.nix {}; + photoprism = handleTest ./photoprism.nix {}; php = handleTest ./php {}; php80 = handleTest ./php { php = pkgs.php80; }; php81 = handleTest ./php { php = pkgs.php81; }; diff --git a/nixos/tests/photoprism.nix b/nixos/tests/photoprism.nix new file mode 100644 index 000000000000..a77ab59f5c9a --- /dev/null +++ b/nixos/tests/photoprism.nix @@ -0,0 +1,23 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "photoprism"; + meta.maintainers = with lib.maintainers; [ stunkymonkey ]; + + nodes.machine = { pkgs, ... }: { + services.photoprism = { + enable = true; + port = 8080; + originalsPath = "/media/photos/"; + passwordFile = pkgs.writeText "password" "secret"; + }; + environment.extraInit = '' + mkdir -p /media/photos + ''; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.wait_for_open_port(8080) + response = machine.succeed("curl -vvv -s -H 'Host: photoprism' http://127.0.0.1:8080/library/login") + assert 'PhotoPrism' in response, "Login page didn't load successfully" + ''; +})