diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-apps')
16 files changed, 747 insertions, 566 deletions
diff --git a/nixpkgs/nixos/modules/services/web-apps/akkoma.md b/nixpkgs/nixos/modules/services/web-apps/akkoma.md index 83dd1a8b35f2..13b074b228a4 100644 --- a/nixpkgs/nixos/modules/services/web-apps/akkoma.md +++ b/nixpkgs/nixos/modules/services/web-apps/akkoma.md @@ -19,21 +19,23 @@ be run behind a HTTP proxy on `fediverse.example.com`. ```nix -services.akkoma.enable = true; -services.akkoma.config = { - ":pleroma" = { - ":instance" = { - name = "My Akkoma instance"; - description = "More detailed description"; - email = "admin@example.com"; - registration_open = false; - }; - - "Pleroma.Web.Endpoint" = { - url.host = "fediverse.example.com"; +{ + services.akkoma.enable = true; + services.akkoma.config = { + ":pleroma" = { + ":instance" = { + name = "My Akkoma instance"; + description = "More detailed description"; + email = "admin@example.com"; + registration_open = false; + }; + + "Pleroma.Web.Endpoint" = { + url.host = "fediverse.example.com"; + }; }; }; -}; +} ``` Please refer to the [configuration cheat sheet](https://docs.akkoma.dev/stable/configuration/cheatsheet/) @@ -55,19 +57,21 @@ Although it is possible to expose Akkoma directly, it is common practice to oper HTTP reverse proxy such as nginx. ```nix -services.akkoma.nginx = { - enableACME = true; - forceSSL = true; -}; - -services.nginx = { - enable = true; - - clientMaxBodySize = "16m"; - recommendedTlsSettings = true; - recommendedOptimisation = true; - recommendedGzipSettings = true; -}; +{ + services.akkoma.nginx = { + enableACME = true; + forceSSL = true; + }; + + services.nginx = { + enable = true; + + clientMaxBodySize = "16m"; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + }; +} ``` Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate. @@ -78,51 +82,53 @@ Without the media proxy function, Akkoma does not store any remote media like pi locally, and clients have to fetch them directly from the source server. ```nix -# Enable nginx slice module distributed with Tengine -services.nginx.package = pkgs.tengine; - -# Enable media proxy -services.akkoma.config.":pleroma".":media_proxy" = { - enabled = true; - proxy_opts.redirect_on_failure = true; -}; - -# Adjust the persistent cache size as needed: -# Assuming an average object size of 128 KiB, around 1 MiB -# of memory is required for the key zone per GiB of cache. -# Ensure that the cache directory exists and is writable by nginx. -services.nginx.commonHttpConfig = '' - proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache - levels= keys_zone=akkoma_media_cache:16m max_size=16g - inactive=1y use_temp_path=off; -''; - -services.akkoma.nginx = { - locations."/proxy" = { - proxyPass = "http://unix:/run/akkoma/socket"; - - extraConfig = '' - proxy_cache akkoma_media_cache; - - # Cache objects in slices of 1 MiB - slice 1m; - proxy_cache_key $host$uri$is_args$args$slice_range; - proxy_set_header Range $slice_range; - - # Decouple proxy and upstream responses - proxy_buffering on; - proxy_cache_lock on; - proxy_ignore_client_abort on; - - # Default cache times for various responses - proxy_cache_valid 200 1y; - proxy_cache_valid 206 301 304 1h; - - # Allow serving of stale items - proxy_cache_use_stale error timeout invalid_header updating; - ''; +{ + # Enable nginx slice module distributed with Tengine + services.nginx.package = pkgs.tengine; + + # Enable media proxy + services.akkoma.config.":pleroma".":media_proxy" = { + enabled = true; + proxy_opts.redirect_on_failure = true; }; -}; + + # Adjust the persistent cache size as needed: + # Assuming an average object size of 128 KiB, around 1 MiB + # of memory is required for the key zone per GiB of cache. + # Ensure that the cache directory exists and is writable by nginx. + services.nginx.commonHttpConfig = '' + proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache + levels= keys_zone=akkoma_media_cache:16m max_size=16g + inactive=1y use_temp_path=off; + ''; + + services.akkoma.nginx = { + locations."/proxy" = { + proxyPass = "http://unix:/run/akkoma/socket"; + + extraConfig = '' + proxy_cache akkoma_media_cache; + + # Cache objects in slices of 1 MiB + slice 1m; + proxy_cache_key $host$uri$is_args$args$slice_range; + proxy_set_header Range $slice_range; + + # Decouple proxy and upstream responses + proxy_buffering on; + proxy_cache_lock on; + proxy_ignore_client_abort on; + + # Default cache times for various responses + proxy_cache_valid 200 1y; + proxy_cache_valid 206 301 304 1h; + + # Allow serving of stale items + proxy_cache_use_stale error timeout invalid_header updating; + ''; + }; + }; +} ``` #### Prefetch remote media {#modules-services-akkoma-prefetch-remote-media} @@ -132,10 +138,12 @@ fetches all media associated with a post through the media proxy, as soon as the received by the instance. ```nix -services.akkoma.config.":pleroma".":mrf".policies = - map (pkgs.formats.elixirConf { }).lib.mkRaw [ - "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" -]; +{ + services.akkoma.config.":pleroma".":mrf".policies = + map (pkgs.formats.elixirConf { }).lib.mkRaw [ + "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" + ]; +} ``` #### Media previews {#modules-services-akkoma-media-previews} @@ -143,11 +151,13 @@ services.akkoma.config.":pleroma".":mrf".policies = Akkoma can generate previews for media. ```nix -services.akkoma.config.":pleroma".":media_preview_proxy" = { - enabled = true; - thumbnail_max_width = 1920; - thumbnail_max_height = 1080; -}; +{ + services.akkoma.config.":pleroma".":media_preview_proxy" = { + enabled = true; + thumbnail_max_width = 1920; + thumbnail_max_height = 1080; + }; +} ``` ## Frontend management {#modules-services-akkoma-frontend-management} @@ -160,29 +170,31 @@ The following example overrides the primary frontend’s default configuration u derivation. ```nix -services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" { - config = builtins.toJSON { - expertLevel = 1; - collapseMessageWithSubject = false; - stopGifs = false; - replyVisibility = "following"; - webPushHideIfCW = true; - hideScopeNotice = true; - renderMisskeyMarkdown = false; - hideSiteFavicon = true; - postContentType = "text/markdown"; - showNavShortcuts = false; - }; - nativeBuildInputs = with pkgs; [ jq xorg.lndir ]; - passAsFile = [ "config" ]; -} '' - mkdir $out - lndir ${pkgs.akkoma-frontends.akkoma-fe} $out - - rm $out/static/config.json - jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \ - >$out/static/config.json -''; +{ + services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" { + config = builtins.toJSON { + expertLevel = 1; + collapseMessageWithSubject = false; + stopGifs = false; + replyVisibility = "following"; + webPushHideIfCW = true; + hideScopeNotice = true; + renderMisskeyMarkdown = false; + hideSiteFavicon = true; + postContentType = "text/markdown"; + showNavShortcuts = false; + }; + nativeBuildInputs = with pkgs; [ jq xorg.lndir ]; + passAsFile = [ "config" ]; + } '' + mkdir $out + lndir ${pkgs.akkoma-frontends.akkoma-fe} $out + + rm $out/static/config.json + jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \ + >$out/static/config.json + ''; +} ``` ## Federation policies {#modules-services-akkoma-federation-policies} @@ -198,28 +210,30 @@ of the fediverse and providing a pleasant experience to the users of an instance ```nix -services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; { - ":mrf".policies = map mkRaw [ - "Pleroma.Web.ActivityPub.MRF.SimplePolicy" - ]; - - ":mrf_simple" = { - # Tag all media as sensitive - media_nsfw = mkMap { - "nsfw.weird.kinky" = "Untagged NSFW content"; - }; - - # Reject all activities except deletes - reject = mkMap { - "kiwifarms.cc" = "Persistent harassment of users, no moderation"; - }; - - # Force posts to be visible by followers only - followers_only = mkMap { - "beta.birdsite.live" = "Avoid polluting timelines with Twitter posts"; +{ + services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; { + ":mrf".policies = map mkRaw [ + "Pleroma.Web.ActivityPub.MRF.SimplePolicy" + ]; + + ":mrf_simple" = { + # Tag all media as sensitive + media_nsfw = mkMap { + "nsfw.weird.kinky" = "Untagged NSFW content"; + }; + + # Reject all activities except deletes + reject = mkMap { + "kiwifarms.cc" = "Persistent harassment of users, no moderation"; + }; + + # Force posts to be visible by followers only + followers_only = mkMap { + "beta.birdsite.live" = "Avoid polluting timelines with Twitter posts"; + }; }; }; -}; +} ``` ## Upload filters {#modules-services-akkoma-upload-filters} @@ -228,12 +242,14 @@ This example strips GPS and location metadata from uploads, deduplicates them an the file name. ```nix -services.akkoma.config.":pleroma"."Pleroma.Upload".filters = - map (pkgs.formats.elixirConf { }).lib.mkRaw [ - "Pleroma.Upload.Filter.Exiftool" - "Pleroma.Upload.Filter.Dedupe" - "Pleroma.Upload.Filter.AnonymizeFilename" - ]; +{ + services.akkoma.config.":pleroma"."Pleroma.Upload".filters = + map (pkgs.formats.elixirConf { }).lib.mkRaw [ + "Pleroma.Upload.Filter.Exiftool" + "Pleroma.Upload.Filter.Dedupe" + "Pleroma.Upload.Filter.AnonymizeFilename" + ]; +} ``` ## Migration from Pleroma {#modules-services-akkoma-migration-pleroma} @@ -286,9 +302,11 @@ To re‐use the Pleroma data in place, disable Pleroma and enable Akkoma, pointi Pleroma database and upload directory. ```nix -# Adjust these settings according to the database name and upload directory path used by Pleroma -services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma"; -services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads"; +{ + # Adjust these settings according to the database name and upload directory path used by Pleroma + services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma"; + services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads"; +} ``` Please keep in mind that after the Akkoma service has been started, any migrations applied by @@ -304,7 +322,9 @@ details. The Akkoma systemd service may be confined to a chroot with ```nix -services.systemd.akkoma.confinement.enable = true; +{ + services.systemd.akkoma.confinement.enable = true; +} ``` Confinement of services is not generally supported in NixOS and therefore disabled by default. diff --git a/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md index 236953bd4ff7..d8e59b3ad210 100644 --- a/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md +++ b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md @@ -4,7 +4,7 @@ c2FmZQ is an application that can securely encrypt, store, and share files, including but not limited to pictures and videos. The service `c2fmzq-server` can be enabled by setting -``` +```nix { services.c2fmzq-server.enable = true; } @@ -17,7 +17,7 @@ In principle the server can be exposed directly on a public interface and there are command line options to manage HTTPS certificates directly, but the module is designed to be served behind a reverse proxy or only accessed via localhost. -``` +```nix { services.c2fmzq-server = { enable = true; diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.md b/nixpkgs/nixos/modules/services/web-apps/discourse.md index 35180bea87d9..d4b9c93c4ead 100644 --- a/nixpkgs/nixos/modules/services/web-apps/discourse.md +++ b/nixpkgs/nixos/modules/services/web-apps/discourse.md @@ -6,20 +6,22 @@ modern and open source discussion platform. ## Basic usage {#module-services-discourse-basic-usage} A minimal configuration using Let's Encrypt for TLS certificates looks like this: -``` -services.discourse = { - enable = true; - hostname = "discourse.example.com"; - admin = { - email = "admin@example.com"; - username = "admin"; - fullName = "Administrator"; - passwordFile = "/path/to/password_file"; +```nix +{ + services.discourse = { + enable = true; + hostname = "discourse.example.com"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; }; - secretKeyBaseFile = "/path/to/secret_key_base_file"; -}; -security.acme.email = "me@example.com"; -security.acme.acceptTerms = true; + security.acme.email = "me@example.com"; + security.acme.acceptTerms = true; +} ``` Provided a proper DNS setup, you'll be able to connect to the @@ -34,20 +36,22 @@ the [](#opt-services.discourse.sslCertificate) and [](#opt-services.discourse.sslCertificateKey) options: -``` -services.discourse = { - enable = true; - hostname = "discourse.example.com"; - sslCertificate = "/path/to/ssl_certificate"; - sslCertificateKey = "/path/to/ssl_certificate_key"; - admin = { - email = "admin@example.com"; - username = "admin"; - fullName = "Administrator"; - passwordFile = "/path/to/password_file"; +```nix +{ + services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; }; - secretKeyBaseFile = "/path/to/secret_key_base_file"; -}; +} ``` ## Database access {#module-services-discourse-database} @@ -80,27 +84,29 @@ A basic setup which assumes you want to use your configured [hostname](#opt-services.discourse.hostname) as email domain can be done like this: -``` -services.discourse = { - enable = true; - hostname = "discourse.example.com"; - sslCertificate = "/path/to/ssl_certificate"; - sslCertificateKey = "/path/to/ssl_certificate_key"; - admin = { - email = "admin@example.com"; - username = "admin"; - fullName = "Administrator"; - passwordFile = "/path/to/password_file"; - }; - mail.outgoing = { - serverAddress = "smtp.emailprovider.com"; - port = 587; - username = "user@emailprovider.com"; - passwordFile = "/path/to/smtp_password_file"; +```nix +{ + services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; + }; + mail.incoming.enable = true; + secretKeyBaseFile = "/path/to/secret_key_base_file"; }; - mail.incoming.enable = true; - secretKeyBaseFile = "/path/to/secret_key_base_file"; -}; +} ``` This assumes you have set up an MX record for the address you've @@ -162,44 +168,46 @@ The following example sets the title and description of the Discourse instance and enables GitHub login in the site settings, and changes a few request limits in the backend settings: -``` -services.discourse = { - enable = true; - hostname = "discourse.example.com"; - sslCertificate = "/path/to/ssl_certificate"; - sslCertificateKey = "/path/to/ssl_certificate_key"; - admin = { - email = "admin@example.com"; - username = "admin"; - fullName = "Administrator"; - passwordFile = "/path/to/password_file"; - }; - mail.outgoing = { - serverAddress = "smtp.emailprovider.com"; - port = 587; - username = "user@emailprovider.com"; - passwordFile = "/path/to/smtp_password_file"; - }; - mail.incoming.enable = true; - siteSettings = { - required = { - title = "My Cats"; - site_description = "Discuss My Cats (and be nice plz)"; +```nix +{ + services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; }; - login = { - enable_github_logins = true; - github_client_id = "a2f6dfe838cb3206ce20"; - github_client_secret._secret = /run/keys/discourse_github_client_secret; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; }; + mail.incoming.enable = true; + siteSettings = { + required = { + title = "My Cats"; + site_description = "Discuss My Cats (and be nice plz)"; + }; + login = { + enable_github_logins = true; + github_client_id = "a2f6dfe838cb3206ce20"; + github_client_secret._secret = /run/keys/discourse_github_client_secret; + }; + }; + backendSettings = { + max_reqs_per_ip_per_minute = 300; + max_reqs_per_ip_per_10_seconds = 60; + max_asset_reqs_per_ip_per_10_seconds = 250; + max_reqs_per_ip_mode = "warn+block"; + }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; }; - backendSettings = { - max_reqs_per_ip_per_minute = 300; - max_reqs_per_ip_per_10_seconds = 60; - max_asset_reqs_per_ip_per_10_seconds = 250; - max_reqs_per_ip_mode = "warn+block"; - }; - secretKeyBaseFile = "/path/to/secret_key_base_file"; -}; +} ``` In the resulting site settings file, the @@ -253,34 +261,36 @@ and [discourse-solved](https://github.com/discourse/discourse-solved) plugins, and disable `discourse-spoiler-alert` by default: -``` -services.discourse = { - enable = true; - hostname = "discourse.example.com"; - sslCertificate = "/path/to/ssl_certificate"; - sslCertificateKey = "/path/to/ssl_certificate_key"; - admin = { - email = "admin@example.com"; - username = "admin"; - fullName = "Administrator"; - passwordFile = "/path/to/password_file"; - }; - mail.outgoing = { - serverAddress = "smtp.emailprovider.com"; - port = 587; - username = "user@emailprovider.com"; - passwordFile = "/path/to/smtp_password_file"; - }; - mail.incoming.enable = true; - plugins = with config.services.discourse.package.plugins; [ - discourse-spoiler-alert - discourse-solved - ]; - siteSettings = { - plugins = { - spoiler_enabled = false; +```nix +{ + services.discourse = { + enable = true; + hostname = "discourse.example.com"; + sslCertificate = "/path/to/ssl_certificate"; + sslCertificateKey = "/path/to/ssl_certificate_key"; + admin = { + email = "admin@example.com"; + username = "admin"; + fullName = "Administrator"; + passwordFile = "/path/to/password_file"; + }; + mail.outgoing = { + serverAddress = "smtp.emailprovider.com"; + port = 587; + username = "user@emailprovider.com"; + passwordFile = "/path/to/smtp_password_file"; + }; + mail.incoming.enable = true; + plugins = with config.services.discourse.package.plugins; [ + discourse-spoiler-alert + discourse-solved + ]; + siteSettings = { + plugins = { + spoiler_enabled = false; + }; }; + secretKeyBaseFile = "/path/to/secret_key_base_file"; }; - secretKeyBaseFile = "/path/to/secret_key_base_file"; -}; +} ``` diff --git a/nixpkgs/nixos/modules/services/web-apps/gotosocial.md b/nixpkgs/nixos/modules/services/web-apps/gotosocial.md index a290d7d1893a..b3540f0d5811 100644 --- a/nixpkgs/nixos/modules/services/web-apps/gotosocial.md +++ b/nixpkgs/nixos/modules/services/web-apps/gotosocial.md @@ -8,17 +8,19 @@ The following configuration sets up the PostgreSQL as database backend and binds GoToSocial to `127.0.0.1:8080`, expecting to be run behind a HTTP proxy on `gotosocial.example.com`. ```nix -services.gotosocial = { - enable = true; - setupPostgresqlDB = true; - settings = { - application-name = "My GoToSocial"; - host = "gotosocial.example.com"; - protocol = "https"; - bind-address = "127.0.0.1"; - port = 8080; +{ + services.gotosocial = { + enable = true; + setupPostgresqlDB = true; + settings = { + application-name = "My GoToSocial"; + host = "gotosocial.example.com"; + protocol = "https"; + bind-address = "127.0.0.1"; + port = 8080; + }; }; -}; +} ``` Please refer to the [GoToSocial Documentation](https://docs.gotosocial.org/en/latest/configuration/general/) @@ -30,24 +32,26 @@ Although it is possible to expose GoToSocial directly, it is common practice to HTTP reverse proxy such as nginx. ```nix -networking.firewall.allowedTCPPorts = [ 80 443 ]; -services.nginx = { - enable = true; - clientMaxBodySize = "40M"; - virtualHosts = with config.services.gotosocial.settings; { - "${host}" = { - enableACME = true; - forceSSL = true; - locations = { - "/" = { - recommendedProxySettings = true; - proxyWebsockets = true; - proxyPass = "http://${bind-address}:${toString port}"; +{ + networking.firewall.allowedTCPPorts = [ 80 443 ]; + services.nginx = { + enable = true; + clientMaxBodySize = "40M"; + virtualHosts = with config.services.gotosocial.settings; { + "${host}" = { + enableACME = true; + forceSSL = true; + locations = { + "/" = { + recommendedProxySettings = true; + proxyWebsockets = true; + proxyPass = "http://${bind-address}:${toString port}"; + }; }; }; }; }; -}; +} ``` Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate. diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.md b/nixpkgs/nixos/modules/services/web-apps/grocy.md index 62aad4b103df..f4b5769c2479 100644 --- a/nixpkgs/nixos/modules/services/web-apps/grocy.md +++ b/nixpkgs/nixos/modules/services/web-apps/grocy.md @@ -6,7 +6,7 @@ ## Basic usage {#module-services-grocy-basic-usage} A very basic configuration may look like this: -``` +```nix { pkgs, ... }: { services.grocy = { @@ -29,7 +29,7 @@ of the application. The configuration for `grocy` is located at `/etc/grocy/config.php`. By default, the following settings can be defined in the NixOS-configuration: -``` +```nix { pkgs, ... }: { services.grocy.settings = { @@ -56,7 +56,7 @@ By default, the following settings can be defined in the NixOS-configuration: If you want to alter the configuration file on your own, you can do this manually with an expression like this: -``` +```nix { lib, ... }: { environment.etc."grocy/config.php".text = lib.mkAfter '' diff --git a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md index 060ef9752650..577f82e315be 100644 --- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md +++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md @@ -6,7 +6,7 @@ private, self-hosted video conferencing solution. ## Basic usage {#module-services-jitsi-basic-usage} A minimal configuration using Let's Encrypt for TLS certificates looks like this: -``` +```nix { services.jitsi-meet = { enable = true; @@ -22,7 +22,7 @@ A minimal configuration using Let's Encrypt for TLS certificates looks like this ## Configuration {#module-services-jitsi-configuration} Here is the minimal configuration with additional configurations: -``` +```nix { services.jitsi-meet = { enable = true; diff --git a/nixpkgs/nixos/modules/services/web-apps/kavita.nix b/nixpkgs/nixos/modules/services/web-apps/kavita.nix index c3e39f0b5476..c90697bcfa8b 100644 --- a/nixpkgs/nixos/modules/services/web-apps/kavita.nix +++ b/nixpkgs/nixos/modules/services/web-apps/kavita.nix @@ -2,7 +2,18 @@ let cfg = config.services.kavita; -in { + settingsFormat = pkgs.formats.json { }; + appsettings = settingsFormat.generate "appsettings.json" ({ TokenKey = "@TOKEN@"; } // cfg.settings); +in +{ + imports = [ + (lib.mkChangedOptionModule [ "services" "kavita" "ipAdresses" ] [ "services" "kavita" "settings" "IpAddresses" ] (config: + let value = lib.getAttrFromPath [ "services" "kavita" "ipAdresses" ] config; in + lib.concatStringsSep "," value + )) + (lib.mkRenamedOptionModule [ "services" "kavita" "port" ] [ "services" "kavita" "settings" "Port" ]) + ]; + options.services.kavita = { enable = lib.mkEnableOption (lib.mdDoc "Kavita reading server"); @@ -27,16 +38,31 @@ in { It can be generated with `head -c 32 /dev/urandom | base64`. ''; }; - port = lib.mkOption { - default = 5000; - type = lib.types.port; - description = lib.mdDoc "Port to bind to."; - }; - ipAdresses = lib.mkOption { - default = ["0.0.0.0" "::"]; - type = lib.types.listOf lib.types.str; - description = lib.mdDoc "IP Addresses to bind to. The default is to bind - to all IPv4 and IPv6 addresses."; + + settings = lib.mkOption { + default = { }; + description = lib.mdDoc '' + Kavita configuration options, as configured in {file}`appsettings.json`. + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options = { + Port = lib.mkOption { + default = 5000; + type = lib.types.port; + description = lib.mdDoc "Port to bind to."; + }; + + IpAddresses = lib.mkOption { + default = "0.0.0.0,::"; + type = lib.types.commas; + description = lib.mdDoc '' + IP Addresses to bind to. The default is to bind to all IPv4 and IPv6 addresses. + ''; + }; + }; + }; }; }; @@ -46,18 +72,15 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; preStart = '' - umask u=rwx,g=rx,o= - cat > "${cfg.dataDir}/config/appsettings.json" <<EOF - { - "TokenKey": "$(cat ${cfg.tokenKeyFile})", - "Port": ${toString cfg.port}, - "IpAddresses": "${lib.concatStringsSep "," cfg.ipAdresses}" - } - EOF + install -m600 ${appsettings} ${lib.escapeShellArg cfg.dataDir}/config/appsettings.json + ${pkgs.replace-secret}/bin/replace-secret '@TOKEN@' \ + ''${CREDENTIALS_DIRECTORY}/token \ + '${cfg.dataDir}/config/appsettings.json' ''; serviceConfig = { WorkingDirectory = cfg.dataDir; - ExecStart = "${lib.getExe cfg.package}"; + LoadCredential = [ "token:${cfg.tokenKeyFile}" ]; + ExecStart = lib.getExe cfg.package; Restart = "always"; User = cfg.user; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/keycloak.md b/nixpkgs/nixos/modules/services/web-apps/keycloak.md index aa8de40d642b..020bee400348 100644 --- a/nixpkgs/nixos/modules/services/web-apps/keycloak.md +++ b/nixpkgs/nixos/modules/services/web-apps/keycloak.md @@ -126,16 +126,18 @@ should be set to. See the description of ## Example configuration {#module-services-keycloak-example-config} A basic configuration with some custom settings could look like this: -``` -services.keycloak = { - enable = true; - settings = { - hostname = "keycloak.example.com"; - hostname-strict-backchannel = true; +```nix +{ + services.keycloak = { + enable = true; + settings = { + hostname = "keycloak.example.com"; + hostname-strict-backchannel = true; + }; + initialAdminPassword = "e6Wcm0RrtegMEHl"; # change on first login + sslCertificate = "/run/keys/ssl_cert"; + sslCertificateKey = "/run/keys/ssl_key"; + database.passwordFile = "/run/keys/db_password"; }; - initialAdminPassword = "e6Wcm0RrtegMEHl"; # change on first login - sslCertificate = "/run/keys/ssl_cert"; - sslCertificateKey = "/run/keys/ssl_key"; - database.passwordFile = "/run/keys/db_password"; -}; +} ``` diff --git a/nixpkgs/nixos/modules/services/web-apps/lemmy.md b/nixpkgs/nixos/modules/services/web-apps/lemmy.md index faafe096d138..0ed23607d00b 100644 --- a/nixpkgs/nixos/modules/services/web-apps/lemmy.md +++ b/nixpkgs/nixos/modules/services/web-apps/lemmy.md @@ -7,13 +7,15 @@ Lemmy is a federated alternative to reddit in rust. the minimum to start lemmy is ```nix -services.lemmy = { - enable = true; - settings = { - hostname = "lemmy.union.rocks"; - database.createLocally = true; +{ + services.lemmy = { + enable = true; + settings = { + hostname = "lemmy.union.rocks"; + database.createLocally = true; + }; + caddy.enable = true; }; - caddy.enable = true; } ``` diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.md b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md index 5db83d7e4463..06a8712b0b8a 100644 --- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.md +++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md @@ -25,7 +25,7 @@ to `true`, Nextcloud will automatically be configured to connect to it through socket. A very basic configuration may look like this: -``` +```nix { pkgs, ... }: { services.nextcloud = { @@ -130,7 +130,7 @@ settings `listen.owner` & `listen.group` in the [corresponding `phpfpm` pool](#opt-services.phpfpm.pools). An exemplary configuration may look like this: -``` +```nix { config, lib, pkgs, ... }: { services.nginx.enable = false; services.nextcloud = { @@ -205,7 +205,7 @@ If major-releases will be abandoned by upstream, we should check first if those in NixOS for a safe upgrade-path before removing those. In that case we should keep those packages, but mark them as insecure in an expression like this (in `<nixpkgs/pkgs/servers/nextcloud/default.nix>`): -``` +```nix /* ... */ { nextcloud17 = generic { diff --git a/nixpkgs/nixos/modules/services/web-apps/peertube.nix b/nixpkgs/nixos/modules/services/web-apps/peertube.nix index 39c02c81c423..76f869913592 100644 --- a/nixpkgs/nixos/modules/services/web-apps/peertube.nix +++ b/nixpkgs/nixos/modules/services/web-apps/peertube.nix @@ -61,18 +61,16 @@ let eval -- "\$@" ''; - peertubeCli = pkgs.writeShellScriptBin "peertube" '' - node ~/dist/server/tools/peertube.js $@ + nginxCommonHeaders = lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.forceSSL '' + add_header Strict-Transport-Security 'max-age=31536000'; + '' + lib.optionalString (config.services.nginx.virtualHosts.${cfg.localDomain}.quic && config.services.nginx.virtualHosts.${cfg.localDomain}.http3) '' + add_header Alt-Svc 'h3=":$server_port"; ma=604800'; ''; - nginxCommonHeaders = lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' - add_header Alt-Svc 'h3=":443"; ma=86400'; - '' + '' - add_header Access-Control-Allow-Origin '*'; - add_header Access-Control-Allow-Methods 'GET, OPTIONS'; - add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + nginxCommonHeadersExtra = '' + add_header Access-Control-Allow-Origin '*'; + add_header Access-Control-Allow-Methods 'GET, OPTIONS'; + add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; ''; in { @@ -330,6 +328,8 @@ in { } ]; + environment.systemPackages = [ cfg.package.cli ]; + services.peertube.settings = lib.mkMerge [ { listen = { @@ -355,12 +355,13 @@ in { tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/"; bin = lib.mkDefault "/var/lib/peertube/storage/bin/"; avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/"; - videos = lib.mkDefault "/var/lib/peertube/storage/videos/"; + web_videos = lib.mkDefault "/var/lib/peertube/storage/web-videos/"; streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/"; redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/"; logs = lib.mkDefault "/var/lib/peertube/storage/logs/"; previews = lib.mkDefault "/var/lib/peertube/storage/previews/"; thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/"; + storyboards = lib.mkDefault "/var/lib/peertube/storage/storyboards/"; torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/"; captions = lib.mkDefault "/var/lib/peertube/storage/captions/"; cache = lib.mkDefault "/var/lib/peertube/storage/cache/"; @@ -428,7 +429,7 @@ in { environment = env; - path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ]; + path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ]; script = '' #!/bin/sh @@ -456,7 +457,7 @@ in { ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides - npm start + node dist/server ''; serviceConfig = { Type = "simple"; @@ -488,6 +489,9 @@ in { services.nginx = lib.mkIf cfg.configureNginx { enable = true; + upstreams."peertube".servers = { + "127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0"; + }; virtualHosts."${cfg.localDomain}" = { root = "/var/lib/peertube/www"; @@ -497,14 +501,14 @@ in { priority = 1110; }; - locations."= /api/v1/videos/upload-resumable" = { + locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = { tryFiles = "/dev/null @api"; priority = 1120; extraConfig = '' - client_max_body_size 0; - proxy_request_buffering off; - ''; + client_max_body_size 0; + proxy_request_buffering off; + '' + nginxCommonHeaders; }; locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = { @@ -513,13 +517,11 @@ in { priority = 1130; extraConfig = '' - client_max_body_size 12G; - add_header X-File-Maximum-Size 8G always; - '' + lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' - add_header Alt-Svc 'h3=":443"; ma=86400'; - ''; + limit_except POST HEAD { deny all; } + + client_max_body_size 12G; + add_header X-File-Maximum-Size 8G always; + '' + nginxCommonHeaders; }; locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = { @@ -528,13 +530,9 @@ in { priority = 1135; extraConfig = '' - client_max_body_size 12G; - add_header X-File-Maximum-Size 8G always; - '' + lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' - add_header Alt-Svc 'h3=":443"; ma=86400'; - ''; + client_max_body_size 12G; + add_header X-File-Maximum-Size 8G always; + '' + nginxCommonHeaders; }; locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = { @@ -542,32 +540,28 @@ in { priority = 1140; extraConfig = '' - client_max_body_size 6M; - add_header X-File-Maximum-Size 4M always; - '' + lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' - add_header Alt-Svc 'h3=":443"; ma=86400'; - ''; + client_max_body_size 6M; + add_header X-File-Maximum-Size 4M always; + '' + nginxCommonHeaders; }; locations."@api" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + proxyPass = "http://peertube"; priority = 1150; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_connect_timeout 10m; + proxy_connect_timeout 10m; - proxy_send_timeout 10m; - proxy_read_timeout 10m; + proxy_send_timeout 10m; + proxy_read_timeout 10m; - client_max_body_size 100k; - send_timeout 10m; - ''; + client_max_body_size 100k; + send_timeout 10m; + ''+ nginxCommonHeaders; }; # Websocket @@ -581,7 +575,7 @@ in { priority = 1220; extraConfig = '' - proxy_read_timeout 15m; + proxy_read_timeout 15m; ''; }; @@ -591,84 +585,82 @@ in { }; locations."@api_websocket" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + proxyPass = "http://peertube"; priority = 1240; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - - proxy_http_version 1.1; - ''; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + '' + nginxCommonHeaders; }; # Bypass PeerTube for performance reasons. locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = { tryFiles = "/client-overrides/$1 /client/$1 $1"; priority = 1310; + + extraConfig = nginxCommonHeaders; }; locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = { alias = "${cfg.package}/client/dist/$1"; priority = 1320; extraConfig = '' - add_header Cache-Control 'public, max-age=604800, immutable'; - '' + lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' - add_header Alt-Svc 'h3=":443"; ma=86400'; - ''; + add_header Cache-Control 'public, max-age=604800, immutable'; + '' + nginxCommonHeaders; }; locations."^~ /download/" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + proxyPass = "http://peertube"; priority = 1410; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_limit_rate 5M; - ''; + proxy_limit_rate 5M; + '' + nginxCommonHeaders; }; - locations."^~ /static/streaming-playlists/private/" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + locations."^~ /static/streaming-playlists/hls/private/" = { + proxyPass = "http://peertube"; priority = 1420; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_limit_rate 5M; - ''; + proxy_limit_rate 5M; + '' + nginxCommonHeaders; }; locations."^~ /static/web-videos/private/" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + proxyPass = "http://peertube"; priority = 1430; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_limit_rate 5M; - ''; + proxy_limit_rate 5M; + '' + nginxCommonHeaders; }; locations."^~ /static/webseed/private/" = { - proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + proxyPass = "http://peertube"; priority = 1440; extraConfig = '' - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_limit_rate 5M; - ''; + proxy_limit_rate 5M; + '' + nginxCommonHeaders; }; locations."^~ /static/redundancy/" = { @@ -676,33 +668,35 @@ in { root = cfg.settings.storage.redundancy; priority = 1450; extraConfig = '' - set $peertube_limit_rate 800k; + set $peertube_limit_rate 800k; if ($request_uri ~ -fragmented.mp4$) { - set $peertube_limit_rate 5M; + set $peertube_limit_rate 5M; } if ($request_method = 'OPTIONS') { ${nginxCommonHeaders} - add_header Access-Control-Max-Age 1728000; - add_header Content-Type 'text/plain charset=UTF-8'; - add_header Content-Length 0; - return 204; + ${nginxCommonHeadersExtra} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; } if ($request_method = 'GET') { ${nginxCommonHeaders} + ${nginxCommonHeadersExtra} - access_log off; + access_log off; } - aio threads; - sendfile on; - sendfile_max_chunk 1M; + aio threads; + sendfile on; + sendfile_max_chunk 1M; - limit_rate $peertube_limit_rate; - limit_rate_after 5M; + limit_rate $peertube_limit_rate; + limit_rate_after 5M; - rewrite ^/static/redundancy/(.*)$ /$1 break; + rewrite ^/static/redundancy/(.*)$ /$1 break; ''; }; @@ -711,109 +705,111 @@ in { root = cfg.settings.storage.streaming_playlists; priority = 1460; extraConfig = '' - set $peertube_limit_rate 800k; + set $peertube_limit_rate 800k; if ($request_uri ~ -fragmented.mp4$) { - set $peertube_limit_rate 5M; + set $peertube_limit_rate 5M; } if ($request_method = 'OPTIONS') { ${nginxCommonHeaders} - add_header Access-Control-Max-Age 1728000; - add_header Content-Type 'text/plain charset=UTF-8'; - add_header Content-Length 0; - return 204; + ${nginxCommonHeadersExtra} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; } if ($request_method = 'GET') { ${nginxCommonHeaders} + ${nginxCommonHeadersExtra} - access_log off; + access_log off; } - aio threads; - sendfile on; - sendfile_max_chunk 1M; + aio threads; + sendfile on; + sendfile_max_chunk 1M; - limit_rate $peertube_limit_rate; - limit_rate_after 5M; + limit_rate $peertube_limit_rate; + limit_rate_after 5M; - rewrite ^/static/streaming-playlists/(.*)$ /$1 break; + rewrite ^/static/streaming-playlists/(.*)$ /$1 break; ''; }; locations."^~ /static/web-videos/" = { tryFiles = "$uri @api"; - root = cfg.settings.storage.streaming_playlists; + root = cfg.settings.storage.web_videos; priority = 1470; extraConfig = '' - set $peertube_limit_rate 800k; + set $peertube_limit_rate 800k; if ($request_uri ~ -fragmented.mp4$) { - set $peertube_limit_rate 5M; + set $peertube_limit_rate 5M; } if ($request_method = 'OPTIONS') { ${nginxCommonHeaders} - add_header Access-Control-Max-Age 1728000; - add_header Content-Type 'text/plain charset=UTF-8'; - add_header Content-Length 0; - return 204; + ${nginxCommonHeadersExtra} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; } if ($request_method = 'GET') { ${nginxCommonHeaders} + ${nginxCommonHeadersExtra} - access_log off; + access_log off; } - aio threads; - sendfile on; - sendfile_max_chunk 1M; + aio threads; + sendfile on; + sendfile_max_chunk 1M; - limit_rate $peertube_limit_rate; - limit_rate_after 5M; + limit_rate $peertube_limit_rate; + limit_rate_after 5M; - rewrite ^/static/streaming-playlists/(.*)$ /$1 break; + rewrite ^/static/web-videos/(.*)$ /$1 break; ''; }; locations."^~ /static/webseed/" = { tryFiles = "$uri @api"; - root = cfg.settings.storage.videos; + root = cfg.settings.storage.web_videos; priority = 1480; extraConfig = '' - set $peertube_limit_rate 800k; + set $peertube_limit_rate 800k; if ($request_uri ~ -fragmented.mp4$) { - set $peertube_limit_rate 5M; + set $peertube_limit_rate 5M; } if ($request_method = 'OPTIONS') { ${nginxCommonHeaders} - add_header Access-Control-Max-Age 1728000; - add_header Content-Type 'text/plain charset=UTF-8'; - add_header Content-Length 0; - return 204; + ${nginxCommonHeadersExtra} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; } if ($request_method = 'GET') { ${nginxCommonHeaders} + ${nginxCommonHeadersExtra} - access_log off; + access_log off; } - aio threads; - sendfile on; - sendfile_max_chunk 1M; + aio threads; + sendfile on; + sendfile_max_chunk 1M; - limit_rate $peertube_limit_rate; - limit_rate_after 5M; + limit_rate $peertube_limit_rate; + limit_rate_after 5M; - rewrite ^/static/webseed/(.*)$ /$1 break; + rewrite ^/static/webseed/(.*)$ /web-videos/$1 break; ''; }; - - extraConfig = lib.optionalString cfg.enableWebHttps '' - add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; - ''; }; }; @@ -848,7 +844,7 @@ in { home = cfg.package; }; }) - (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ]) + (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ]) (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];}) ]; diff --git a/nixpkgs/nixos/modules/services/web-apps/pict-rs.md b/nixpkgs/nixos/modules/services/web-apps/pict-rs.md index 2fa6bb3aebce..56c51e0d7259 100644 --- a/nixpkgs/nixos/modules/services/web-apps/pict-rs.md +++ b/nixpkgs/nixos/modules/services/web-apps/pict-rs.md @@ -7,7 +7,9 @@ pict-rs is a a simple image hosting service. the minimum to start pict-rs is ```nix -services.pict-rs.enable = true; +{ + services.pict-rs.enable = true; +} ``` this will start the http server on port 8080 by default. diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.md b/nixpkgs/nixos/modules/services/web-apps/plausible.md index 1328ce69441a..d3673eabddd4 100644 --- a/nixpkgs/nixos/modules/services/web-apps/plausible.md +++ b/nixpkgs/nixos/modules/services/web-apps/plausible.md @@ -11,7 +11,7 @@ $ openssl rand -base64 64 ``` After that, `plausible` can be deployed like this: -``` +```nix { services.plausible = { enable = true; diff --git a/nixpkgs/nixos/modules/services/web-apps/pretix.nix b/nixpkgs/nixos/modules/services/web-apps/pretix.nix index 2355f8c450a1..22ee9769aa92 100644 --- a/nixpkgs/nixos/modules/services/web-apps/pretix.nix +++ b/nixpkgs/nixos/modules/services/web-apps/pretix.nix @@ -63,7 +63,7 @@ in }; options.services.pretix = { - enable = mkEnableOption "pretix"; + enable = mkEnableOption "Pretix, a ticket shop application for conferences, festivals, concerts, etc."; package = mkPackageOption pkgs "pretix" { }; diff --git a/nixpkgs/nixos/modules/services/web-apps/slskd.nix b/nixpkgs/nixos/modules/services/web-apps/slskd.nix index 580f66ec3ac9..15a5fd1177ad 100644 --- a/nixpkgs/nixos/modules/services/web-apps/slskd.nix +++ b/nixpkgs/nixos/modules/services/web-apps/slskd.nix @@ -2,120 +2,248 @@ let settingsFormat = pkgs.formats.yaml {}; + defaultUser = "slskd"; in { options.services.slskd = with lib; with types; { enable = mkEnableOption "enable slskd"; - rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs"; + package = mkPackageOptionMD pkgs "slskd" { }; - package = mkPackageOption pkgs "slskd" { }; + user = mkOption { + type = types.str; + default = defaultUser; + description = "User account under which slskd runs."; + }; - nginx = mkOption { - description = lib.mdDoc "options for nginx"; - example = { - enable = true; - domain = "example.com"; - contextPath = "/slskd"; - }; - type = submodule ({name, config, ...}: { - options = { - enable = mkEnableOption "enable nginx as a reverse proxy"; + group = mkOption { + type = types.str; + default = defaultUser; + description = "Group under which slskd runs."; + }; - domainName = mkOption { - type = str; - description = "Domain you want to use"; - }; - contextPath = mkOption { - type = types.path; - default = "/"; - description = lib.mdDoc '' - The context path, i.e., the last part of the slskd - URL. Typically '/' or '/slskd'. Default '/' - ''; - }; - }; - }); + domain = mkOption { + type = types.nullOr types.str; + description = '' + If non-null, enables an nginx reverse proxy virtual host at this FQDN, + at the path configurated with `services.slskd.web.url_base`. + ''; + example = "slskd.example.com"; + }; + + nginx = mkOption { + type = types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }); + default = {}; + example = lib.literalExpression '' + { + enableACME = true; + forceHttps = true; + } + ''; + description = '' + This option customizes the nginx virtual host set up for slskd. + ''; }; environmentFile = mkOption { type = path; description = '' - Path to a file containing secrets. - It must at least contain the variable `SLSKD_SLSK_PASSWORD` + Path to the environment file sourced on startup. + It must at least contain the variables `SLSKD_SLSK_USERNAME` and `SLSKD_SLSK_PASSWORD`. + Web interface credentials should also be set here in `SLSKD_USERNAME` and `SLSKD_PASSWORD`. + Other, optional credentials like SOCKS5 with `SLSKD_SLSK_PROXY_USERNAME` and `SLSKD_SLSK_PROXY_PASSWORD` + should all reside here instead of in the world-readable nix store. + Variables are documented at https://github.com/slskd/slskd/blob/master/docs/config.md ''; }; openFirewall = mkOption { type = bool; - description = '' - Whether to open the firewall for services.slskd.settings.listen_port"; - ''; + description = "Whether to open the firewall for the soulseek network listen port (not the web interface port)."; default = false; }; settings = mkOption { - description = lib.mdDoc '' - Configuration for slskd, see - [available options](https://github.com/slskd/slskd/blob/master/docs/config.md) - `APP_DIR` is set to /var/lib/slskd, where default download & incomplete directories, - log and databases will be created. + description = '' + Application configuration for slskd. See + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md). ''; default = {}; type = submodule { freeformType = settingsFormat.type; options = { + remote_file_management = mkEnableOption "modification of share contents through the web ui"; + + flags = { + force_share_scan = mkOption { + type = bool; + description = "Force a rescan of shares on every startup."; + }; + no_version_check = mkOption { + type = bool; + default = true; + visible = false; + description = "Don't perform a version check on startup."; + }; + }; + + directories = { + incomplete = mkOption { + type = nullOr path; + description = "Directory where incomplete downloading files are stored."; + defaultText = "/var/lib/slskd/incomplete"; + default = null; + }; + downloads = mkOption { + type = nullOr path; + description = "Directory where downloaded files are stored."; + defaultText = "/var/lib/slskd/downloads"; + default = null; + }; + }; + + shares = { + directories = mkOption { + type = listOf str; + description = '' + Paths to shared directories. See + [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) + for advanced usage. + ''; + example = lib.literalExpression ''[ "/home/John/Music" "!/home/John/Music/Recordings" "[Music Drive]/mnt" ]''; + }; + filters = mkOption { + type = listOf str; + example = lib.literalExpression ''[ "\.ini$" "Thumbs.db$" "\.DS_Store$" ]''; + description = "Regular expressions of files to exclude from sharing."; + }; + }; + + rooms = mkOption { + type = listOf str; + description = "Chat rooms to join on startup."; + }; soulseek = { - username = mkOption { + description = mkOption { type = str; - description = "Username on the Soulseek Network"; + description = "The user description for the Soulseek network."; + defaultText = "A slskd user. https://github.com/slskd/slskd"; }; listen_port = mkOption { type = port; - description = "Port to use for communication on the Soulseek Network"; - default = 50000; + description = "The port on which to listen for incoming connections."; + default = 50300; }; }; + global = { + # TODO speed units + upload = { + slots = mkOption { + type = ints.unsigned; + description = "Limit of the number of concurrent upload slots."; + }; + speed_limit = mkOption { + type = ints.unsigned; + description = "Total upload speed limit."; + }; + }; + download = { + slots = mkOption { + type = ints.unsigned; + description = "Limit of the number of concurrent download slots."; + }; + speed_limit = mkOption { + type = ints.unsigned; + description = "Total upload download limit"; + }; + }; + }; + + filters.search.request = mkOption { + type = listOf str; + example = lib.literalExpression ''[ "^.{1,2}$" ]''; + description = "Incoming search requests which match this filter are ignored."; + }; + web = { port = mkOption { type = port; - default = 5001; - description = "The HTTP listen port"; + default = 5030; + description = "The HTTP listen port."; }; url_base = mkOption { type = path; - default = config.services.slskd.nginx.contextPath; - defaultText = "config.services.slskd.nginx.contextPath"; - description = lib.mdDoc '' - The context path, i.e., the last part of the slskd URL - ''; + default = "/"; + description = "The base path in the url for web requests."; + }; + # Users should use a reverse proxy instead for https + https.disabled = mkOption { + type = bool; + default = true; + description = "Disable the built-in HTTPS server"; }; }; - shares = { - directories = mkOption { - type = listOf str; - description = lib.mdDoc '' - Paths to your shared directories. See - [documentation](https://github.com/slskd/slskd/blob/master/docs/config.md#directories) - for advanced usage - ''; + retention = { + transfers = { + upload = { + succeeded = mkOption { + type = ints.unsigned; + description = "Lifespan of succeeded upload tasks."; + defaultText = "(indefinite)"; + }; + errored = mkOption { + type = ints.unsigned; + description = "Lifespan of errored upload tasks."; + defaultText = "(indefinite)"; + }; + cancelled = mkOption { + type = ints.unsigned; + description = "Lifespan of cancelled upload tasks."; + defaultText = "(indefinite)"; + }; + }; + download = { + succeeded = mkOption { + type = ints.unsigned; + description = "Lifespan of succeeded download tasks."; + defaultText = "(indefinite)"; + }; + errored = mkOption { + type = ints.unsigned; + description = "Lifespan of errored download tasks."; + defaultText = "(indefinite)"; + }; + cancelled = mkOption { + type = ints.unsigned; + description = "Lifespan of cancelled download tasks."; + defaultText = "(indefinite)"; + }; + }; + }; + files = { + complete = mkOption { + type = ints.unsigned; + description = "Lifespan of completely downloaded files in minutes."; + example = 20160; + defaultText = "(indefinite)"; + }; + incomplete = mkOption { + type = ints.unsigned; + description = "Lifespan of incomplete downloading files in minutes."; + defaultText = "(indefinite)"; + }; }; }; - directories = { - incomplete = mkOption { - type = nullOr path; - description = "Directory where downloading files are stored"; - defaultText = "<APP_DIR>/incomplete"; - default = null; - }; - downloads = mkOption { - type = nullOr path; - description = "Directory where downloaded files are stored"; - defaultText = "<APP_DIR>/downloads"; - default = null; + logger = { + # Disable by default, journald already retains as needed + disk = mkOption { + type = bool; + description = "Whether to log to the application directory."; + default = false; + visible = false; }; }; }; @@ -126,51 +254,42 @@ in { config = let cfg = config.services.slskd; - confWithoutNullValues = (lib.filterAttrs (key: value: value != null) cfg.settings); + confWithoutNullValues = (lib.filterAttrsRecursive (key: value: (builtins.tryEval value).success && value != null) cfg.settings); configurationYaml = settingsFormat.generate "slskd.yml" confWithoutNullValues; in lib.mkIf cfg.enable { - users = { - users.slskd = { + # Force off, configuration file is in nix store and is immutable + services.slskd.settings.remote_configuration = lib.mkForce false; + + users.users = lib.optionalAttrs (cfg.user == defaultUser) { + "${defaultUser}" = { + group = cfg.group; isSystemUser = true; - group = "slskd"; }; - groups.slskd = {}; }; - # Reverse proxy configuration - services.nginx.enable = true; - services.nginx.virtualHosts."${cfg.nginx.domainName}" = { - forceSSL = true; - enableACME = true; - locations = { - "${cfg.nginx.contextPath}" = { - proxyPass = "http://localhost:${toString cfg.settings.web.port}"; - proxyWebsockets = true; - }; - }; + users.groups = lib.optionalAttrs (cfg.group == defaultUser) { + "${defaultUser}" = {}; }; - # Hide state & logs - systemd.tmpfiles.rules = [ - "d /var/lib/slskd/data 0750 slskd slskd - -" - "d /var/lib/slskd/logs 0750 slskd slskd - -" - ]; - systemd.services.slskd = { description = "A modern client-server application for the Soulseek file sharing network"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; - User = "slskd"; + User = cfg.user; + Group = cfg.group; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; - StateDirectory = "slskd"; + StateDirectory = "slskd"; # Creates /var/lib/slskd and manages permissions ExecStart = "${cfg.package}/bin/slskd --app-dir /var/lib/slskd --config ${configurationYaml}"; Restart = "on-failure"; ReadOnlyPaths = map (d: builtins.elemAt (builtins.split "[^/]*(/.+)" d) 1) cfg.settings.shares.directories; + ReadWritePaths = + (lib.optional (cfg.settings.directories.incomplete != null) cfg.settings.directories.incomplete) ++ + (lib.optional (cfg.settings.directories.downloads != null) cfg.settings.directories.downloads); LockPersonality = true; NoNewPrivileges = true; PrivateDevices = true; @@ -194,18 +313,21 @@ in { networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.settings.soulseek.listen_port; - systemd.services.slskd-rotatelogs = lib.mkIf cfg.rotateLogs { - description = "Rotate slskd logs"; - serviceConfig = { - Type = "oneshot"; - User = "slskd"; - ExecStart = [ - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +10 -delete" - "${pkgs.findutils}/bin/find /var/lib/slskd/logs/ -type f -mtime +1 -exec ${pkgs.gzip}/bin/gzip -q {} ';'" - ]; - }; - startAt = "daily"; + services.nginx = lib.mkIf (cfg.domain != null) { + enable = lib.mkDefault true; + virtualHosts."${cfg.domain}" = lib.mkMerge [ + cfg.nginx + { + locations."${cfg.settings.web.url_base}" = { + proxyPass = "http://127.0.0.1:${toString cfg.settings.web.port}"; + proxyWebsockets = true; + }; + } + ]; }; + }; + meta = { + maintainers = with lib.maintainers; [ ppom melvyn2 ]; }; } diff --git a/nixpkgs/nixos/modules/services/web-apps/suwayomi-server.md b/nixpkgs/nixos/modules/services/web-apps/suwayomi-server.md index 18e7a631443f..2185556a8721 100644 --- a/nixpkgs/nixos/modules/services/web-apps/suwayomi-server.md +++ b/nixpkgs/nixos/modules/services/web-apps/suwayomi-server.md @@ -100,7 +100,7 @@ Not all the configuration options are available directly in this module, but you server = { port = 4567; autoDownloadNewChapters = false; - maxSourcesInParallel" = 6; + maxSourcesInParallel = 6; extensionRepos = [ "https://raw.githubusercontent.com/MY_ACCOUNT/MY_REPO/repo/index.min.json" ]; |