diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-apps')
24 files changed, 1107 insertions, 1192 deletions
diff --git a/nixpkgs/nixos/modules/services/web-apps/audiobookshelf.nix b/nixpkgs/nixos/modules/services/web-apps/audiobookshelf.nix new file mode 100644 index 000000000000..84dffc5f9d3c --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/audiobookshelf.nix @@ -0,0 +1,90 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.audiobookshelf; +in +{ + options = { + services.audiobookshelf = { + enable = mkEnableOption "Audiobookshelf, self-hosted audiobook and podcast server."; + + package = mkPackageOption pkgs "audiobookshelf" { }; + + dataDir = mkOption { + description = "Path to Audiobookshelf config and metadata inside of /var/lib."; + default = "audiobookshelf"; + type = types.str; + }; + + host = mkOption { + description = "The host Audiobookshelf binds to."; + default = "127.0.0.1"; + example = "0.0.0.0"; + type = types.str; + }; + + port = mkOption { + description = "The TCP port Audiobookshelf will listen on."; + default = 8000; + type = types.port; + }; + + user = mkOption { + description = "User account under which Audiobookshelf runs."; + default = "audiobookshelf"; + type = types.str; + }; + + group = mkOption { + description = "Group under which Audiobookshelf runs."; + default = "audiobookshelf"; + type = types.str; + }; + + openFirewall = mkOption { + description = "Open ports in the firewall for the Audiobookshelf web interface."; + default = false; + type = types.bool; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.audiobookshelf = { + description = "Audiobookshelf is a self-hosted audiobook and podcast server"; + + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + StateDirectory = cfg.dataDir; + WorkingDirectory = "/var/lib/${cfg.dataDir}"; + ExecStart = "${cfg.package}/bin/audiobookshelf --host ${cfg.host} --port ${toString cfg.port}"; + Restart = "on-failure"; + }; + }; + + users.users = mkIf (cfg.user == "audiobookshelf") { + audiobookshelf = { + isSystemUser = true; + group = cfg.group; + home = "/var/lib/${cfg.dataDir}"; + }; + }; + + users.groups = mkIf (cfg.group == "audiobookshelf") { + audiobookshelf = { }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + }; + + meta.maintainers = with maintainers; [ wietsedv ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix b/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix index da2cf93d7f1c..5519d6967a12 100644 --- a/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix +++ b/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix @@ -69,7 +69,7 @@ let in { options.services.cloudlog = with types; { - enable = mkEnableOption (mdDoc "Whether to enable Cloudlog"); + enable = mkEnableOption (mdDoc "Cloudlog"); dataDir = mkOption { type = str; default = "/var/lib/cloudlog"; diff --git a/nixpkgs/nixos/modules/services/web-apps/dex.nix b/nixpkgs/nixos/modules/services/web-apps/dex.nix index bd041db007a1..0c4a71c6dfe4 100644 --- a/nixpkgs/nixos/modules/services/web-apps/dex.nix +++ b/nixpkgs/nixos/modules/services/web-apps/dex.nix @@ -108,8 +108,7 @@ in ProtectClock = true; ProtectHome = true; ProtectHostname = true; - # Would re-mount paths ignored by temporary root - #ProtectSystem = "strict"; + ProtectSystem = "strict"; ProtectControlGroups = true; ProtectKernelLogs = true; ProtectKernelModules = true; @@ -121,9 +120,7 @@ in RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ]; - TemporaryFileSystem = "/:ro"; - # Does not work well with the temporary root - #UMask = "0066"; + UMask = "0066"; } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.nix b/nixpkgs/nixos/modules/services/web-apps/grocy.nix index 4d1084e295ff..fe40a3c20941 100644 --- a/nixpkgs/nixos/modules/services/web-apps/grocy.nix +++ b/nixpkgs/nixos/modules/services/web-apps/grocy.nix @@ -8,6 +8,8 @@ in { options.services.grocy = { enable = mkEnableOption (lib.mdDoc "grocy"); + package = mkPackageOptionMD pkgs "grocy" { }; + hostName = mkOption { type = types.str; description = lib.mdDoc '' @@ -143,7 +145,7 @@ in { services.nginx = { enable = true; virtualHosts."${cfg.hostName}" = mkMerge [ - { root = "${pkgs.grocy}/public"; + { root = "${cfg.package}/public"; locations."/".extraConfig = '' rewrite ^ /index.php; ''; diff --git a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix index bfa5fd5aff25..1a66f077b09d 100644 --- a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix +++ b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }: let - inherit (lib) literalExpression mdDoc mkEnableOption mkIf mkOption mkPackageOptionMD mkRenamedOptionModule types versionAtLeast; + inherit (lib) mkOption types mdDoc literalExpression; cfg = config.services.hedgedoc; @@ -9,990 +9,189 @@ let # versionAtLeast statement remains set to 21.03 for backwards compatibility. # See https://github.com/NixOS/nixpkgs/pull/108899 and # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md. - name = if versionAtLeast config.system.stateVersion "21.03" - then "hedgedoc" - else "codimd"; + name = if lib.versionAtLeast config.system.stateVersion "21.03" then + "hedgedoc" + else + "codimd"; - settingsFormat = pkgs.formats.json {}; - - prettyJSON = conf: - pkgs.runCommandLocal "hedgedoc-config.json" { - nativeBuildInputs = [ pkgs.jq ]; - } '' - jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \ - < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \ - > $out - ''; + settingsFormat = pkgs.formats.json { }; in { + meta.maintainers = with lib.maintainers; [ SuperSandro2000 h7x4 ]; + imports = [ - (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ]) - (mkRenamedOptionModule - [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ]) + (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ]) + (lib.mkRenamedOptionModule [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ]) + (lib.mkRenamedOptionModule [ "services" "hedgedoc" "groups" ] [ "users" "users" "hedgedoc" "extraGroups" ]) + (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] '' + This option has been removed in favor of systemd managing the state directory. + + If you have set this option without specifying `services.settings.uploadsDir`, + please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point + at the correct location. + '') ]; options.services.hedgedoc = { - package = mkPackageOptionMD pkgs "hedgedoc" { }; - enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor"); + package = lib.mkPackageOptionMD pkgs "hedgedoc" { }; + enable = lib.mkEnableOption (mdDoc "the HedgeDoc Markdown Editor"); - groups = mkOption { - type = types.listOf types.str; - default = []; - description = lib.mdDoc '' - Groups to which the service user should be added. - ''; - }; - - workDir = mkOption { - type = types.path; - default = "/var/lib/${name}"; - description = lib.mdDoc '' - Working directory for the HedgeDoc service. - ''; - }; + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + options = { + domain = mkOption { + type = with types; nullOr str; + default = null; + example = "hedgedoc.org"; + description = mdDoc '' + Domain to use for website. - settings = let options = { - debug = mkEnableOption (lib.mdDoc "debug mode"); - domain = mkOption { - type = types.nullOr types.str; - default = null; - example = "hedgedoc.org"; - description = lib.mdDoc '' - Domain name for the HedgeDoc instance. - ''; - }; - urlPath = mkOption { - type = types.nullOr types.str; - default = null; - example = "/url/path/to/hedgedoc"; - description = lib.mdDoc '' - Path under which HedgeDoc is accessible. - ''; - }; - host = mkOption { - type = types.str; - default = "localhost"; - description = lib.mdDoc '' - Address to listen on. - ''; - }; - port = mkOption { - type = types.port; - default = 3000; - example = 80; - description = lib.mdDoc '' - Port to listen on. - ''; - }; - path = mkOption { - type = types.nullOr types.str; - default = null; - example = "/run/hedgedoc.sock"; - description = lib.mdDoc '' - Specify where a UNIX domain socket should be placed. - ''; - }; - allowOrigin = mkOption { - type = types.listOf types.str; - default = []; - example = [ "localhost" "hedgedoc.org" ]; - description = lib.mdDoc '' - List of domains to whitelist. - ''; - }; - useSSL = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable to use SSL server. This will also enable - {option}`protocolUseSSL`. - ''; - }; - enableStatsApi = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enables or disables the /status and /metrics endpoint. - ''; - }; - hsts = { - enable = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to enable HSTS if HTTPS is also enabled. - ''; - }; - maxAgeSeconds = mkOption { - type = types.int; - default = 31536000; - description = lib.mdDoc '' - Max duration for clients to keep the HSTS status. - ''; - }; - includeSubdomains = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to include subdomains in HSTS. - ''; - }; - preload = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to allow preloading of the site's HSTS status. - ''; - }; - }; - csp = mkOption { - type = types.nullOr types.attrs; - default = null; - example = literalExpression '' - { - enable = true; - directives = { - scriptSrc = "trustworthy.scripts.example.com"; - }; - upgradeInsecureRequest = "auto"; - addDefaults = true; - } - ''; - description = lib.mdDoc '' - Specify the Content Security Policy which is passed to Helmet. - For configuration details see <https://helmetjs.github.io/docs/csp/>. - ''; - }; - protocolUseSSL = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable to use TLS for resource paths. - This only applies when {option}`domain` is set. - ''; - }; - urlAddPort = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable to add the port to callback URLs. - This only applies when {option}`domain` is set - and only for ports other than 80 and 443. - ''; - }; - useCDN = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to use CDN resources or not. - ''; - }; - allowAnonymous = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to allow anonymous usage. - ''; - }; - allowAnonymousEdits = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to allow guests to edit existing notes with the `freely` permission, - when {option}`allowAnonymous` is enabled. - ''; - }; - allowFreeURL = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to allow note creation by accessing a nonexistent note URL. - ''; - }; - requireFreeURLAuthentication = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether to require authentication for FreeURL mode style note creation. - ''; - }; - defaultPermission = mkOption { - type = types.enum [ "freely" "editable" "limited" "locked" "private" ]; - default = "editable"; - description = lib.mdDoc '' - Default permissions for notes. - This only applies for signed-in users. - ''; - }; - dbURL = mkOption { - type = types.nullOr types.str; - default = null; - example = '' - postgres://user:pass@host:5432/dbname - ''; - description = lib.mdDoc '' - Specify which database to use. - HedgeDoc supports mysql, postgres, sqlite and mssql. - See [ - https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information. - Note: This option overrides {option}`db`. - ''; - }; - db = mkOption { - type = types.attrs; - default = {}; - example = literalExpression '' - { - dialect = "sqlite"; - storage = "/var/lib/${name}/db.${name}.sqlite"; - } - ''; - description = lib.mdDoc '' - Specify the configuration for sequelize. - HedgeDoc supports mysql, postgres, sqlite and mssql. - See [ - https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information. - Note: This option overrides {option}`db`. - ''; - }; - sslKeyPath= mkOption { - type = types.nullOr types.str; - default = null; - example = "/var/lib/hedgedoc/hedgedoc.key"; - description = lib.mdDoc '' - Path to the SSL key. Needed when {option}`useSSL` is enabled. - ''; - }; - sslCertPath = mkOption { - type = types.nullOr types.str; - default = null; - example = "/var/lib/hedgedoc/hedgedoc.crt"; - description = lib.mdDoc '' - Path to the SSL cert. Needed when {option}`useSSL` is enabled. - ''; - }; - sslCAPath = mkOption { - type = types.listOf types.str; - default = []; - example = [ "/var/lib/hedgedoc/ca.crt" ]; - description = lib.mdDoc '' - SSL ca chain. Needed when {option}`useSSL` is enabled. - ''; - }; - dhParamPath = mkOption { - type = types.nullOr types.str; - default = null; - example = "/var/lib/hedgedoc/dhparam.pem"; - description = lib.mdDoc '' - Path to the SSL dh params. Needed when {option}`useSSL` is enabled. - ''; - }; - tmpPath = mkOption { - type = types.str; - default = "/tmp"; - description = lib.mdDoc '' - Path to the temp directory HedgeDoc should use. - Note that {option}`serviceConfig.PrivateTmp` is enabled for - the HedgeDoc systemd service by default. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - defaultNotePath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/default.md"; - defaultText = literalExpression "\"\${cfg.package}/public/default.md\""; - description = lib.mdDoc '' - Path to the default Note file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - docsPath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/docs"; - defaultText = literalExpression "\"\${cfg.package}/public/docs\""; - description = lib.mdDoc '' - Path to the docs directory. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - indexPath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/views/index.ejs"; - defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\""; - description = lib.mdDoc '' - Path to the index template file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - hackmdPath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/views/hackmd.ejs"; - defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\""; - description = lib.mdDoc '' - Path to the hackmd template file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - errorPath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/views/error.ejs"; - defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\""; - description = lib.mdDoc '' - Path to the error template file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - prettyPath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/views/pretty.ejs"; - defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\""; - description = lib.mdDoc '' - Path to the pretty template file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - slidePath = mkOption { - type = types.nullOr types.str; - default = "${cfg.package}/public/views/slide.hbs"; - defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\""; - description = lib.mdDoc '' - Path to the slide template file. - (Non-canonical paths are relative to HedgeDoc's base directory) - ''; - }; - uploadsPath = mkOption { - type = types.str; - default = "${cfg.workDir}/uploads"; - defaultText = literalExpression "\"\${cfg.workDir}/uploads\""; - description = lib.mdDoc '' - Path under which uploaded files are saved. - ''; - }; - sessionName = mkOption { - type = types.str; - default = "connect.sid"; - description = lib.mdDoc '' - Specify the name of the session cookie. - ''; - }; - sessionSecret = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc '' - Specify the secret used to sign the session cookie. - If unset, one will be generated on startup. - ''; - }; - sessionLife = mkOption { - type = types.int; - default = 1209600000; - description = lib.mdDoc '' - Session life time in milliseconds. - ''; - }; - heartbeatInterval = mkOption { - type = types.int; - default = 5000; - description = lib.mdDoc '' - Specify the socket.io heartbeat interval. - ''; - }; - heartbeatTimeout = mkOption { - type = types.int; - default = 10000; - description = lib.mdDoc '' - Specify the socket.io heartbeat timeout. - ''; - }; - documentMaxLength = mkOption { - type = types.int; - default = 100000; - description = lib.mdDoc '' - Specify the maximum document length. - ''; - }; - email = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to enable email sign-in. - ''; - }; - allowEmailRegister = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to enable email registration. - ''; - }; - allowGravatar = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to use gravatar as profile picture source. - ''; - }; - imageUploadType = mkOption { - type = types.enum [ "imgur" "s3" "minio" "filesystem" ]; - default = "filesystem"; - description = lib.mdDoc '' - Specify where to upload images. - ''; - }; - minio = mkOption { - type = types.nullOr (types.submodule { - options = { - accessKey = mkOption { - type = types.str; - description = lib.mdDoc '' - Minio access key. - ''; - }; - secretKey = mkOption { - type = types.str; - description = lib.mdDoc '' - Minio secret key. - ''; - }; - endPoint = mkOption { - type = types.str; - description = lib.mdDoc '' - Minio endpoint. - ''; - }; - port = mkOption { - type = types.port; - default = 9000; - description = lib.mdDoc '' - Minio listen port. - ''; - }; - secure = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to use HTTPS for Minio. - ''; - }; + This is useful if you are trying to run hedgedoc behind + a reverse proxy. + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the minio third-party integration."; - }; - s3 = mkOption { - type = types.nullOr (types.submodule { - options = { - accessKeyId = mkOption { - type = types.str; - description = lib.mdDoc '' - AWS access key id. - ''; - }; - secretAccessKey = mkOption { - type = types.str; - description = lib.mdDoc '' - AWS access key. - ''; - }; - region = mkOption { - type = types.str; - description = lib.mdDoc '' - AWS S3 region. - ''; - }; - }; - }); - default = null; - description = lib.mdDoc "Configure the s3 third-party integration."; - }; - s3bucket = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc '' - Specify the bucket name for upload types `s3` and `minio`. - ''; - }; - allowPDFExport = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to enable PDF exports. - ''; - }; - imgur.clientId = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc '' - Imgur API client ID. - ''; - }; - azure = mkOption { - type = types.nullOr (types.submodule { - options = { - connectionString = mkOption { - type = types.str; - description = lib.mdDoc '' - Azure Blob Storage connection string. - ''; - }; - container = mkOption { - type = types.str; - description = lib.mdDoc '' - Azure Blob Storage container name. - It will be created if non-existent. - ''; - }; - }; - }); - default = null; - description = lib.mdDoc "Configure the azure third-party integration."; - }; - oauth2 = mkOption { - type = types.nullOr (types.submodule { - options = { - authorizationURL = mkOption { - type = types.str; - description = lib.mdDoc '' - Specify the OAuth authorization URL. - ''; - }; - tokenURL = mkOption { - type = types.str; - description = lib.mdDoc '' - Specify the OAuth token URL. - ''; - }; - baseURL = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the OAuth base URL. - ''; - }; - userProfileURL = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the OAuth userprofile URL. - ''; - }; - userProfileUsernameAttr = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the name of the attribute for the username from the claim. - ''; - }; - userProfileDisplayNameAttr = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the name of the attribute for the display name from the claim. - ''; - }; - userProfileEmailAttr = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the name of the attribute for the email from the claim. - ''; - }; - scope = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the OAuth scope. - ''; - }; - providerName = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the name to be displayed for this strategy. - ''; - }; - rolesClaim = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the role claim name. - ''; - }; - accessRole = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify role which should be included in the ID token roles claim to grant access - ''; - }; - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - Specify the OAuth client ID. - ''; - }; - clientSecret = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - Specify the OAuth client secret. - ''; - }; + urlPath = mkOption { + type = with types; nullOr str; + default = null; + example = "hedgedoc"; + description = mdDoc '' + URL path for the website. + + This is useful if you are hosting hedgedoc on a path like + `www.example.com/hedgedoc` + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the OAuth integration."; - }; - facebook = mkOption { - type = types.nullOr (types.submodule { - options = { - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - Facebook API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Facebook API client secret. - ''; - }; + host = mkOption { + type = with types; nullOr str; + default = "localhost"; + description = mdDoc '' + Address to listen on. + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the facebook third-party integration"; - }; - twitter = mkOption { - type = types.nullOr (types.submodule { - options = { - consumerKey = mkOption { - type = types.str; - description = lib.mdDoc '' - Twitter API consumer key. - ''; - }; - consumerSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Twitter API consumer secret. - ''; - }; + port = mkOption { + type = types.port; + default = 3000; + example = 80; + description = mdDoc '' + Port to listen on. + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the Twitter third-party integration."; - }; - github = mkOption { - type = types.nullOr (types.submodule { - options = { - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - GitHub API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Github API client secret. - ''; - }; + path = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/hedgedoc/hedgedoc.sock"; + description = mdDoc '' + Path to UNIX domain socket to listen on + + ::: {.note} + If specified, {option}`host` and {option}`port` will be ignored. + ::: + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the GitHub third-party integration."; - }; - gitlab = mkOption { - type = types.nullOr (types.submodule { - options = { - baseURL = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - GitLab API authentication endpoint. - Only needed for other endpoints than gitlab.com. - ''; - }; - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - GitLab API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - GitLab API client secret. - ''; - }; - scope = mkOption { - type = types.enum [ "api" "read_user" ]; - default = "api"; - description = lib.mdDoc '' - GitLab API requested scope. - GitLab snippet import/export requires api scope. - ''; - }; + protocolUseSSL = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc '' + Use `https://` for all links. + + This is useful if you are trying to run hedgedoc behind + a reverse proxy. + + ::: {.note} + Only applied if {option}`domain` is set. + ::: + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the GitLab third-party integration."; - }; - mattermost = mkOption { - type = types.nullOr (types.submodule { - options = { - baseURL = mkOption { - type = types.str; - description = lib.mdDoc '' - Mattermost authentication endpoint. - ''; - }; - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - Mattermost API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Mattermost API client secret. - ''; - }; + allowOrigin = mkOption { + type = with types; listOf str; + default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]; + defaultText = literalExpression '' + with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ] + ''; + example = [ "localhost" "hedgedoc.org" ]; + description = mdDoc '' + List of domains to whitelist. + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the Mattermost third-party integration."; - }; - dropbox = mkOption { - type = types.nullOr (types.submodule { - options = { - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - Dropbox API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Dropbox API client secret. - ''; - }; - appKey = mkOption { - type = types.str; - description = lib.mdDoc '' - Dropbox app key. - ''; - }; + db = mkOption { + type = types.attrs; + default = { + dialect = "sqlite"; + storage = "/var/lib/${name}/db.sqlite"; + }; + defaultText = literalExpression '' + { + dialect = "sqlite"; + storage = "/var/lib/hedgedoc/db.sqlite"; + } + ''; + example = literalExpression '' + db = { + username = "hedgedoc"; + database = "hedgedoc"; + host = "localhost:5432"; + # or via socket + # host = "/run/postgresql"; + dialect = "postgresql"; + }; + ''; + description = mdDoc '' + Specify the configuration for sequelize. + HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`. + See <https://sequelize.readthedocs.io/en/v3/> + for more information. + + ::: {.note} + The relevant parts will be overriden if you set {option}`dbURL`. + ::: + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the Dropbox third-party integration."; - }; - google = mkOption { - type = types.nullOr (types.submodule { - options = { - clientID = mkOption { - type = types.str; - description = lib.mdDoc '' - Google API client ID. - ''; - }; - clientSecret = mkOption { - type = types.str; - description = lib.mdDoc '' - Google API client secret. - ''; - }; + useSSL = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Enable to use SSL server. + + ::: {.note} + This will also enable {option}`protocolUseSSL`. + + It will also require you to set the following: + + - {option}`sslKeyPath` + - {option}`sslCertPath` + - {option}`sslCAPath` + - {option}`dhParamPath` + ::: + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the Google third-party integration."; - }; - ldap = mkOption { - type = types.nullOr (types.submodule { - options = { - providerName = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - Optional name to be displayed at login form, indicating the LDAP provider. - ''; - }; - url = mkOption { - type = types.str; - example = "ldap://localhost"; - description = lib.mdDoc '' - URL of LDAP server. - ''; - }; - bindDn = mkOption { - type = types.str; - description = lib.mdDoc '' - Bind DN for LDAP access. - ''; - }; - bindCredentials = mkOption { - type = types.str; - description = lib.mdDoc '' - Bind credentials for LDAP access. - ''; - }; - searchBase = mkOption { - type = types.str; - example = "o=users,dc=example,dc=com"; - description = lib.mdDoc '' - LDAP directory to begin search from. - ''; - }; - searchFilter = mkOption { - type = types.str; - example = "(uid={{username}})"; - description = lib.mdDoc '' - LDAP filter to search with. - ''; - }; - searchAttributes = mkOption { - type = types.nullOr (types.listOf types.str); - default = null; - example = [ "displayName" "mail" ]; - description = lib.mdDoc '' - LDAP attributes to search with. - ''; - }; - userNameField = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - LDAP field which is used as the username on HedgeDoc. - By default {option}`useridField` is used. - ''; - }; - useridField = mkOption { - type = types.str; - example = "uid"; - description = lib.mdDoc '' - LDAP field which is a unique identifier for users on HedgeDoc. - ''; - }; - tlsca = mkOption { - type = types.str; - default = "/etc/ssl/certs/ca-certificates.crt"; - example = "server-cert.pem,root.pem"; - description = lib.mdDoc '' - Root CA for LDAP TLS in PEM format. - ''; - }; + uploadsPath = mkOption { + type = types.path; + default = "/var/lib/${name}/uploads"; + defaultText = "/var/lib/hedgedoc/uploads"; + description = mdDoc '' + Directory for storing uploaded images. + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the LDAP integration."; - }; - saml = mkOption { - type = types.nullOr (types.submodule { - options = { - idpSsoUrl = mkOption { - type = types.str; - example = "https://idp.example.com/sso"; - description = lib.mdDoc '' - IdP authentication endpoint. - ''; - }; - idpCert = mkOption { - type = types.path; - example = "/path/to/cert.pem"; - description = lib.mdDoc '' - Path to IdP certificate file in PEM format. - ''; - }; - issuer = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - Optional identity of the service provider. - This defaults to the server URL. - ''; - }; - identifierFormat = mkOption { - type = types.str; - default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; - description = lib.mdDoc '' - Optional name identifier format. - ''; - }; - groupAttribute = mkOption { - type = types.str; - default = ""; - example = "memberOf"; - description = lib.mdDoc '' - Optional attribute name for group list. - ''; - }; - externalGroups = mkOption { - type = types.listOf types.str; - default = []; - example = [ "Temporary-staff" "External-users" ]; - description = lib.mdDoc '' - Excluded group names. - ''; - }; - requiredGroups = mkOption { - type = types.listOf types.str; - default = []; - example = [ "Hedgedoc-Users" ]; - description = lib.mdDoc '' - Required group names. - ''; - }; - providerName = mkOption { - type = types.str; - default = ""; - example = "My institution"; - description = lib.mdDoc '' - Optional name to be displayed at login form indicating the SAML provider. - ''; - }; - attribute = { - id = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - Attribute map for `id`. - Defaults to `NameID` of SAML response. - ''; - }; - username = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - Attribute map for `username`. - Defaults to `NameID` of SAML response. - ''; - }; - email = mkOption { - type = types.str; - default = ""; - description = lib.mdDoc '' - Attribute map for `email`. - Defaults to `NameID` of SAML response if - {option}`identifierFormat` has - the default value. - ''; - }; - }; + + # Declared because we change the default to false. + allowGravatar = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc '' + Whether to enable [Libravatar](https://wiki.libravatar.org/) as + profile picture source on your instance. + + Despite the naming of the setting, Hedgedoc replaced Gravatar + with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/) + ''; }; - }); - default = null; - description = lib.mdDoc "Configure the SAML integration."; - }; - }; in lib.mkOption { - type = lib.types.submodule { - freeformType = settingsFormat.type; - inherit options; + }; }; - description = lib.mdDoc '' + + description = mdDoc '' HedgeDoc configuration, see <https://docs.hedgedoc.org/configuration/> for documentation. @@ -1003,7 +202,7 @@ in type = with types; nullOr path; default = null; example = "/var/lib/hedgedoc/hedgedoc.env"; - description = lib.mdDoc '' + description = mdDoc '' Environment file as defined in {manpage}`systemd.exec(5)`. Secrets may be passed to the service without adding them to the world-readable @@ -1028,45 +227,94 @@ in }; }; - config = mkIf cfg.enable { - assertions = [ - { assertion = cfg.settings.db == {} -> ( - cfg.settings.dbURL != "" && cfg.settings.dbURL != null - ); - message = "Database configuration for HedgeDoc missing."; } - ]; - users.groups.${name} = {}; + config = lib.mkIf cfg.enable { + users.groups.${name} = { }; users.users.${name} = { description = "HedgeDoc service user"; group = name; - extraGroups = cfg.groups; - home = cfg.workDir; - createHome = true; isSystemUser = true; }; + services.hedgedoc.settings = { + defaultNotePath = lib.mkDefault "${cfg.package}/public/default.md"; + docsPath = lib.mkDefault "${cfg.package}/public/docs"; + viewPath = lib.mkDefault "${cfg.package}/public/views"; + }; + systemd.services.hedgedoc = { description = "HedgeDoc Service"; + documentation = [ "https://docs.hedgedoc.org/" ]; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" ]; - preStart = '' - ${pkgs.envsubst}/bin/envsubst \ - -o ${cfg.workDir}/config.json \ - -i ${prettyJSON cfg.settings} - mkdir -p ${cfg.settings.uploadsPath} - ''; + preStart = + let + configFile = settingsFormat.generate "hedgedoc-config.json" { + production = cfg.settings; + }; + in + '' + ${pkgs.envsubst}/bin/envsubst \ + -o /run/${name}/config.json \ + -i ${configFile} + ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath} + ''; serviceConfig = { - WorkingDirectory = cfg.workDir; - StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ]; - ExecStart = "${lib.getExe cfg.package}"; - EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; + User = name; + Group = name; + + Restart = "always"; + ExecStart = "${cfg.package}/bin/hedgedoc"; + RuntimeDirectory = [ name ]; + StateDirectory = [ name ]; + WorkingDirectory = "/run/${name}"; + ReadWritePaths = [ + "-${cfg.settings.uploadsPath}" + ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ]; + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; Environment = [ - "CMD_CONFIG_FILE=${cfg.workDir}/config.json" + "CMD_CONFIG_FILE=/run/${name}/config.json" "NODE_ENV=production" ]; - Restart = "always"; - User = name; + + # Hardening + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + # Required for connecting to database sockets, + # and listening to unix socket at `cfg.settings.path` + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port; + SocketBindDeny = "any"; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged @obsolete" + "@pkey" + ]; + UMask = "0007"; }; }; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix index 0fc283ff5219..be8ecc645e59 100644 --- a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix +++ b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix @@ -7,7 +7,7 @@ in { enable = mkEnableOption (lib.mdDoc "hledger-web service"); - serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI"); + serveApi = mkEnableOption (lib.mdDoc "serving only the JSON web API, without the web UI"); host = mkOption { type = types.str; diff --git a/nixpkgs/nixos/modules/services/web-apps/honk.nix b/nixpkgs/nixos/modules/services/web-apps/honk.nix index e8718774575b..d47b17e54ffb 100644 --- a/nixpkgs/nixos/modules/services/web-apps/honk.nix +++ b/nixpkgs/nixos/modules/services/web-apps/honk.nix @@ -116,7 +116,7 @@ in unitConfig = { ConditionPathExists = [ # Skip this service if the database already exists - "!$STATE_DIRECTORY/honk.db" + "!%S/honk/honk.db" ]; }; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/isso.nix b/nixpkgs/nixos/modules/services/web-apps/isso.nix index 1a852ec352f2..6cb2d9ec785e 100644 --- a/nixpkgs/nixos/modules/services/web-apps/isso.nix +++ b/nixpkgs/nixos/modules/services/web-apps/isso.nix @@ -12,11 +12,11 @@ in { options = { services.isso = { enable = mkEnableOption (lib.mdDoc '' - A commenting server similar to Disqus. + isso, a commenting server similar to Disqus. Note: The application's author suppose to run isso behind a reverse proxy. The embedded solution offered by NixOS is also only suitable for small installations - below 20 requests per second. + below 20 requests per second ''); settings = mkOption { diff --git a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix index 3825b03c2449..21416be35877 100644 --- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix @@ -105,9 +105,9 @@ in type = bool; default = true; description = lib.mdDoc '' - Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody. + Jitsi Videobridge instance and configure it to connect to Prosody. - Additional configuration is possible with {option}`services.jitsi-videobridge`. + Additional configuration is possible with {option}`services.jitsi-videobridge` ''; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix index 21c587694c6e..c5fb03766899 100644 --- a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix +++ b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix @@ -8,7 +8,12 @@ let cfg = config.services.mediawiki; fpm = config.services.phpfpm.pools.mediawiki; user = "mediawiki"; - group = if cfg.webserver == "apache" then config.services.httpd.group else "mediawiki"; + group = + if cfg.webserver == "apache" then + config.services.httpd.group + else if cfg.webserver == "nginx" then + config.services.nginx.group + else "mediawiki"; cacheDir = "/var/cache/mediawiki"; stateDir = "/var/lib/mediawiki"; @@ -71,7 +76,7 @@ let ## For more information on customizing the URLs ## (like /w/index.php/Page_title to /wiki/Page_title) please see: ## https://www.mediawiki.org/wiki/Manual:Short_URL - $wgScriptPath = ""; + $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}"; ## The protocol and server name to use in fully-qualified URLs $wgServer = "${cfg.url}"; @@ -79,6 +84,11 @@ let ## The URL path to static resources (images, scripts, etc.) $wgResourceBasePath = $wgScriptPath; + ${lib.optionalString (cfg.webserver == "nginx") '' + $wgArticlePath = "/wiki/$1"; + $wgUsePathInfo = true; + ''} + ## The URL path to the logo. Make sure you change this from the default, ## or else you'll overwrite your logo when you upgrade! $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png"; @@ -175,6 +185,7 @@ let ${cfg.extraConfig} ''; + withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/"; in { # interface @@ -209,8 +220,14 @@ in url = mkOption { type = types.str; - default = if cfg.webserver == "apache" then + default = + if cfg.webserver == "apache" then "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}" + else if cfg.webserver == "nginx" then + let + hasSSL = host: host.forceSSL || host.addSSL; + in + "${if hasSSL config.services.nginx.virtualHosts.${cfg.nginx.hostName} then "https" else "http"}://${cfg.nginx.hostName}" else "http://localhost"; defaultText = literalExpression '' @@ -286,7 +303,7 @@ in }; webserver = mkOption { - type = types.enum [ "apache" "none" ]; + type = types.enum [ "apache" "none" "nginx" ]; default = "apache"; description = lib.mdDoc "Webserver to use."; }; @@ -368,6 +385,16 @@ in }; }; + nginx.hostName = mkOption { + type = types.str; + example = literalExpression ''wiki.example.com''; + default = "localhost"; + description = lib.mdDoc '' + The hostname to use for the nginx virtual host. + This is used to generate the nginx configuration. + ''; + }; + httpd.virtualHost = mkOption { type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); example = literalExpression '' @@ -469,6 +496,9 @@ in settings = (if (cfg.webserver == "apache") then { "listen.owner" = config.services.httpd.user; "listen.group" = config.services.httpd.group; + } else if (cfg.webserver == "nginx") then { + "listen.owner" = config.services.nginx.user; + "listen.group" = config.services.nginx.group; } else { "listen.owner" = user; "listen.group" = group; @@ -503,6 +533,62 @@ in } ]; }; + # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx + services.nginx = lib.mkIf (cfg.webserver == "nginx") { + enable = true; + virtualHosts.${config.services.mediawiki.nginx.hostName} = { + root = "${pkg}/share/mediawiki"; + locations = { + "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = '' + rewrite ^/w/(.*) /$1 break; + include ${config.services.nginx.package}/conf/fastcgi_params; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket}; + ''; + "/w/images/".alias = withTrailingSlash cfg.uploadsDir; + # Deny access to deleted images folder + "/w/images/deleted".extraConfig = '' + deny all; + ''; + # MediaWiki assets (usually images) + "~ ^/w/resources/(assets|lib|src)" = { + tryFiles = "$uri =404"; + extraConfig = '' + add_header Cache-Control "public"; + expires 7d; + ''; + }; + # Assets, scripts and styles from skins and extensions + "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$" = { + tryFiles = "$uri =404"; + extraConfig = '' + add_header Cache-Control "public"; + expires 7d; + ''; + }; + + # Handling for Mediawiki REST API, see [[mw:API:REST_API]] + "/w/rest.php".tryFiles = "$uri $uri/ /rest.php?$query_string"; + + # Handling for the article path (pretty URLs) + "/wiki/".extraConfig = '' + rewrite ^/wiki/(?<pagename>.*)$ /w/index.php; + ''; + + # Explicit access to the root website, redirect to main page (adapt as needed) + "= /".extraConfig = '' + return 301 /wiki/Main_Page; + ''; + + # Every other entry point will be disallowed. + # Add specific rules for other entry points/images as needed above this + "/".extraConfig = '' + return 404; + ''; + }; + }; + }; systemd.tmpfiles.rules = [ "d '${stateDir}' 0750 ${user} ${group} - -" diff --git a/nixpkgs/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixpkgs/nixos/modules/services/web-apps/meme-bingo-web.nix index cb864321ef27..652dc8840252 100644 --- a/nixpkgs/nixos/modules/services/web-apps/meme-bingo-web.nix +++ b/nixpkgs/nixos/modules/services/web-apps/meme-bingo-web.nix @@ -8,9 +8,9 @@ in { options = { services.meme-bingo-web = { enable = mkEnableOption (mdDoc '' - A web app for the meme bingo, rendered entirely on the web server and made interactive with forms. + a web app for the meme bingo, rendered entirely on the web server and made interactive with forms. - Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3. + Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3 ''); package = mkOption { diff --git a/nixpkgs/nixos/modules/services/web-apps/microbin.nix b/nixpkgs/nixos/modules/services/web-apps/microbin.nix new file mode 100644 index 000000000000..233bfac6e699 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/microbin.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.microbin; +in +{ + options.services.microbin = { + enable = lib.mkEnableOption (lib.mdDoc "MicroBin is a super tiny, feature rich, configurable paste bin web application"); + + package = lib.mkPackageOption pkgs "microbin" { }; + + settings = lib.mkOption { + type = lib.types.submodule { freeformType = with lib.types; attrsOf (oneOf [ bool int str ]); }; + default = { }; + example = { + MICROBIN_PORT = 8080; + MICROBIN_HIDE_LOGO = false; + }; + description = lib.mdDoc '' + Additional configuration for MicroBin, see + <https://microbin.eu/docs/installation-and-configuration/configuration/> + for supported values. + + For secrets use passwordFile option instead. + ''; + }; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/microbin"; + description = lib.mdDoc "Default data folder for MicroBin."; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/microbin.env"; + description = lib.mdDoc '' + Path to file containing environment variables. + Useful for passing down secrets. + Variables that can be considered secrets are: + - MICROBIN_BASIC_AUTH_USERNAME + - MICROBIN_BASIC_AUTH_PASSWORD + - MICROBIN_ADMIN_USERNAME + - MICROBIN_ADMIN_PASSWORD + - MICROBIN_UPLOADER_PASSWORD + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.microbin.settings = with lib; { + MICROBIN_BIND = mkDefault "0.0.0.0"; + MICROBIN_DISABLE_TELEMETRY = mkDefault true; + MICROBIN_LIST_SERVER = mkDefault false; + MICROBIN_PORT = mkDefault "8080"; + }; + + systemd.services.microbin = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = lib.mapAttrs (_: v: if lib.isBool v then lib.boolToString v else toString v) cfg.settings; + serviceConfig = { + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + DevicePolicy = "closed"; + DynamicUser = true; + EnvironmentFile = lib.optional (cfg.passwordFile != null) cfg.passwordFile; + ExecStart = "${cfg.package}/bin/microbin"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ReadWritePaths = cfg.dataDir; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + StateDirectory = "microbin"; + SystemCallArchitectures = [ "native" ]; + SystemCallFilter = [ "@system-service" ]; + WorkingDirectory = cfg.dataDir; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ surfaceflinger ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/netbox.nix b/nixpkgs/nixos/modules/services/web-apps/netbox.nix index 6d89ffc2a7b7..8ba1852848e5 100644 --- a/nixpkgs/nixos/modules/services/web-apps/netbox.nix +++ b/nixpkgs/nixos/modules/services/web-apps/netbox.nix @@ -74,9 +74,18 @@ in { package = lib.mkOption { type = lib.types.package; - default = if lib.versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3; + default = + if lib.versionAtLeast config.system.stateVersion "23.11" + then pkgs.netbox_3_6 + else if lib.versionAtLeast config.system.stateVersion "23.05" + then pkgs.netbox_3_5 + else pkgs.netbox_3_3; defaultText = lib.literalExpression '' - if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3; + if lib.versionAtLeast config.system.stateVersion "23.11" + then pkgs.netbox_3_6 + else if lib.versionAtLeast config.system.stateVersion "23.05" + then pkgs.netbox_3_5 + else pkgs.netbox_3_3; ''; description = lib.mdDoc '' NetBox package to use. diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.md b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md index cbd7b5b3d066..a25bed30e47f 100644 --- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.md +++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md @@ -119,13 +119,7 @@ Auto updates for Nextcloud apps can be enabled using - **Server-side encryption.** Nextcloud supports [server-side encryption (SSE)](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html). This is not an end-to-end encryption, but can be used to encrypt files that will be persisted - to external storage such as S3. Please note that this won't work anymore when using OpenSSL 3 - for PHP's openssl extension and **Nextcloud 25 or older** because this is implemented using the - legacy cipher RC4. For Nextcloud26 this isn't relevant anymore, because Nextcloud has an RC4 implementation - written in native PHP and thus doesn't need `ext-openssl` for that anymore. - If [](#opt-system.stateVersion) is *above* `22.05`, - this is disabled by default. To turn it on again and for further information please refer to - [](#opt-services.nextcloud.enableBrokenCiphersForSSE). + to external storage such as S3. ## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd} diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix index e0a7e7d4859c..f9713cac47e9 100644 --- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix @@ -27,13 +27,7 @@ let phpPackage = cfg.phpPackage.buildEnv { extensions = { enabled, all }: - (with all; - # disable default openssl extension - (lib.filter (e: e.pname != "php-openssl") enabled) - # use OpenSSL 1.1 for RC4 Nextcloud encryption if user - # has acknowledged the brokenness of the ciphers (RC4). - # TODO: remove when https://github.com/nextcloud/server/issues/32003 is fixed. - ++ (if cfg.enableBrokenCiphersForSSE then [ cfg.phpPackage.extensions.openssl-legacy ] else [ cfg.phpPackage.extensions.openssl ]) + (with all; enabled ++ optional cfg.enableImagemagick imagick # Optionally enabled depending on caching settings ++ optional cfg.caching.apcu apcu @@ -66,6 +60,9 @@ let mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql"; pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql"; + # https://github.com/nextcloud/documentation/pull/11179 + ocmProviderIsNotAStaticDirAnymore = versionAtLeast cfg.package.version "27.1.2"; + in { imports = [ @@ -87,6 +84,10 @@ in { Further details about this can be found in the `Nextcloud`-section of the NixOS-manual (which can be opened e.g. by running `nixos-help`). '') + (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] '' + This option has no effect since there's no supported Nextcloud version packaged here + using OpenSSL for RC4 SSE. + '') (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] '' Use services.nextcloud.enableImagemagick instead. '') @@ -95,39 +96,6 @@ in { options.services.nextcloud = { enable = mkEnableOption (lib.mdDoc "nextcloud"); - enableBrokenCiphersForSSE = mkOption { - type = types.bool; - default = versionOlder stateVersion "22.11"; - defaultText = literalExpression "versionOlder system.stateVersion \"22.11\""; - description = lib.mdDoc '' - This option enables using the OpenSSL PHP extension linked against OpenSSL 1.1 - rather than latest OpenSSL (≥ 3), this is not recommended unless you need - it for server-side encryption (SSE). SSE uses the legacy RC4 cipher which is - considered broken for several years now. See also [RFC7465](https://datatracker.ietf.org/doc/html/rfc7465). - - This cipher has been disabled in OpenSSL ≥ 3 and requires - a specific legacy profile to re-enable it. - - If you deploy Nextcloud using OpenSSL ≥ 3 for PHP and have - server-side encryption configured, you will not be able to access - your files anymore. Enabling this option can restore access to your files. - Upon testing we didn't encounter any data corruption when turning - this on and off again, but this cannot be guaranteed for - each Nextcloud installation. - - It is `true` by default for systems with a [](#opt-system.stateVersion) below - `22.11` to make sure that existing installations won't break on update. On newer - NixOS systems you have to explicitly enable it on your own. - - Please note that this only provides additional value when using - external storage such as S3 since it's not an end-to-end encryption. - If this is not the case, - it is advised to [disable server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption) and set this to `false`. - - In the future, Nextcloud may move to AES-256-GCM, by then, - this option will be removed. - ''; - }; hostName = mkOption { type = types.str; description = lib.mdDoc "FQDN for the nextcloud instance."; @@ -225,7 +193,7 @@ in { package = mkOption { type = types.package; description = lib.mdDoc "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud25" "nextcloud26" "nextcloud27" ]; + relatedPackages = [ "nextcloud26" "nextcloud27" ]; }; phpPackage = mkOption { type = types.package; @@ -740,28 +708,7 @@ in { '') ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) - ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")) - ++ (optional cfg.enableBrokenCiphersForSSE '' - You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud. - This is only necessary if you're using Nextcloud's server-side encryption. - Please keep in mind that it's using the broken RC4 cipher. - - If you don't use that feature, you can switch to OpenSSL 3 and get - rid of this warning by declaring - - services.nextcloud.enableBrokenCiphersForSSE = false; - - If you need to use server-side encryption you can ignore this warning. - Otherwise you'd have to disable server-side encryption first in order - to be able to safely disable this option and get rid of this warning. - See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this. - - For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470 - '') - ++ (optional (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") '' - Nextcloud26 supports RC4 without requiring legacy OpenSSL, so - `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`. - ''); + ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")); services.nextcloud.package = with pkgs; mkDefault ( @@ -1136,10 +1083,6 @@ in { } ''; }; - "/" = { - priority = 900; - extraConfig = "rewrite ^ /index.php;"; - }; "~ ^/store-apps" = { priority = 201; extraConfig = "root ${cfg.home};"; @@ -1164,15 +1107,23 @@ in { try_files $uri $uri/ =404; ''; }; - "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = '' - return 404; - ''; - "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = '' - return 404; - ''; - "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = { + "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = { + priority = 450; + extraConfig = '' + return 404; + ''; + }; + "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = { + priority = 450; + extraConfig = '' + return 404; + ''; + }; + "~ \\.php(?:$|/)" = { priority = 500; extraConfig = '' + # legacy support (i.e. static files and directories in cfg.package) + rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; include ${config.services.nginx.package}/conf/fastcgi.conf; fastcgi_split_path_info ^(.+?\.php)(\\/.*)$; set $path_info $fastcgi_path_info; @@ -1188,19 +1139,30 @@ in { fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s; ''; }; - "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = '' + "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm)$".extraConfig = '' try_files $uri /index.php$request_uri; expires 6M; access_log off; + location ~ \.wasm$ { + default_type application/wasm; + } ''; - "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = '' + "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = '' try_files $uri/ =404; index index.php; ''; - "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = '' - try_files $uri /index.php$request_uri; - access_log off; - ''; + "/remote" = { + priority = 1500; + extraConfig = '' + return 301 /remote.php$request_uri; + ''; + }; + "/" = { + priority = 1600; + extraConfig = '' + try_files $uri $uri/ /index.php$request_uri; + ''; + }; }; extraConfig = '' index index.php index.html /index.php$request_uri; diff --git a/nixpkgs/nixos/modules/services/web-apps/outline.nix b/nixpkgs/nixos/modules/services/web-apps/outline.nix index 1d8298963e6d..0e3bd07c1fc1 100644 --- a/nixpkgs/nixos/modules/services/web-apps/outline.nix +++ b/nixpkgs/nixos/modules/services/web-apps/outline.nix @@ -117,13 +117,14 @@ in storage = lib.mkOption { description = lib.mdDoc '' To support uploading of images for avatars and document attachments an - s3-compatible storage must be provided. AWS S3 is recommended for + s3-compatible storage can be provided. AWS S3 is recommended for redundancy however if you want to keep all file storage local an alternative such as [minio](https://github.com/minio/minio) can be used. + Local filesystem storage can also be used. - A more detailed guide on setting up S3 is available - [here](https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f). + A more detailed guide on setting up storage is available + [here](https://docs.getoutline.com/s/hosting/doc/file-storage-N4M0T6Ypu7). ''; example = lib.literalExpression '' { @@ -136,6 +137,19 @@ in ''; type = lib.types.submodule { options = { + storageType = lib.mkOption { + type = lib.types.enum [ "local" "s3" ]; + description = lib.mdDoc "File storage type, it can be local or s3."; + default = "s3"; + }; + localRootDir = lib.mkOption { + type = lib.types.str; + description = lib.mdDoc '' + If `storageType` is `local`, this sets the parent directory + under which all attachments/images go. + ''; + default = "/var/lib/outline/data"; + }; accessKey = lib.mkOption { type = lib.types.str; description = lib.mdDoc "S3 access key."; @@ -557,7 +571,10 @@ in systemd.tmpfiles.rules = [ "f ${cfg.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -" "f ${cfg.utilsSecretFile} 0600 ${cfg.user} ${cfg.group} -" - "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -" + (if (cfg.storage.storageType == "s3") then + "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -" + else + "d ${cfg.storage.localRootDir} 0700 ${cfg.user} ${cfg.group} - -") ]; services.postgresql = lib.mkIf (cfg.databaseUrl == "local") { @@ -599,14 +616,6 @@ in URL = cfg.publicUrl; PORT = builtins.toString cfg.port; - AWS_ACCESS_KEY_ID = cfg.storage.accessKey; - AWS_REGION = cfg.storage.region; - AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl; - AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName; - AWS_S3_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize; - AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle; - AWS_S3_ACL = cfg.storage.acl; - CDN_URL = cfg.cdnUrl; FORCE_HTTPS = builtins.toString cfg.forceHttps; ENABLE_UPDATES = builtins.toString cfg.enableUpdateCheck; @@ -622,8 +631,21 @@ in RATE_LIMITER_ENABLED = builtins.toString cfg.rateLimiter.enable; RATE_LIMITER_REQUESTS = builtins.toString cfg.rateLimiter.requests; RATE_LIMITER_DURATION_WINDOW = builtins.toString cfg.rateLimiter.durationWindow; + + FILE_STORAGE = cfg.storage.storageType; + FILE_STORAGE_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize; + FILE_STORAGE_LOCAL_ROOT_DIR = cfg.storage.localRootDir; } + (lib.mkIf (cfg.storage.storageType == "s3") { + AWS_ACCESS_KEY_ID = cfg.storage.accessKey; + AWS_REGION = cfg.storage.region; + AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl; + AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName; + AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle; + AWS_S3_ACL = cfg.storage.acl; + }) + (lib.mkIf (cfg.slackAuthentication != null) { SLACK_CLIENT_ID = cfg.slackAuthentication.clientId; }) @@ -676,7 +698,9 @@ in script = '' export SECRET_KEY="$(head -n1 ${lib.escapeShellArg cfg.secretKeyFile})" export UTILS_SECRET="$(head -n1 ${lib.escapeShellArg cfg.utilsSecretFile})" - export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})" + ${lib.optionalString (cfg.storage.storageType == "s3") '' + export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})" + ''} ${lib.optionalString (cfg.slackAuthentication != null) '' export SLACK_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.slackAuthentication.secretFile})" ''} diff --git a/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix b/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix index 641a3644614f..7012df6dffbf 100644 --- a/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix +++ b/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix @@ -2,40 +2,15 @@ let cfg = config.services.peering-manager; - configFile = pkgs.writeTextFile { - name = "configuration.py"; - text = '' - ALLOWED_HOSTS = ['*'] - DATABASE = { - 'NAME': 'peering-manager', - 'USER': 'peering-manager', - 'HOST': '/run/postgresql', - } - - # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate - # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended - # to use two separate database IDs. - REDIS = { - 'tasks': { - 'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}', - 'DATABASE': 0, - }, - 'caching': { - 'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}', - 'DATABASE': 1, - } - } - - with open("${cfg.secretKeyFile}", "r") as file: - SECRET_KEY = file.readline() - '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) '' - with open("${cfg.peeringdbApiKeyFile}", "r") as file: - PEERINGDB_API_KEY = file.readline() - '' + '' - ${cfg.extraConfig} - ''; + pythonFmt = pkgs.formats.pythonVars {}; + settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings; + extraConfigFile = pkgs.writeTextFile { + name = "peering-manager-extraConfig.py"; + text = cfg.extraConfig; }; + configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ]; + pkg = (pkgs.peering-manager.overrideAttrs (old: { postInstall = '' ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py @@ -60,7 +35,15 @@ in { Enable Peering Manager. This module requires a reverse proxy that serves `/static` separately. - See this [example](https://github.com/peering-manager-community/peering-manager/blob/develop/contrib/nginx.conf/) on how to configure this. + See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf on how to configure this. + ''; + }; + + enableScheduledTasks = mkOption { + type = types.bool; + default = true; + description = '' + Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/) ''; }; @@ -106,6 +89,30 @@ in { ''; }; + settings = lib.mkOption { + description = lib.mdDoc '' + Configuration options to set in `configuration.py`. + See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options. + ''; + + default = { }; + + type = lib.types.submodule { + freeformType = pythonFmt.type; + + options = { + ALLOWED_HOSTS = lib.mkOption { + type = with lib.types; listOf str; + default = ["*"]; + description = lib.mdDoc '' + A list of valid fully-qualified domain names (FQDNs) and/or IP + addresses that can be used to reach the peering manager service. + ''; + }; + }; + }; + }; + extraConfig = mkOption { type = types.lines; default = ""; @@ -135,7 +142,39 @@ in { }; config = lib.mkIf cfg.enable { - services.peering-manager.plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); + services.peering-manager = { + settings = { + DATABASE = { + NAME = "peering-manager"; + USER = "peering-manager"; + HOST = "/run/postgresql"; + }; + + # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate + # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended + # to use two separate database IDs. + REDIS = { + tasks = { + UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket; + DATABASE = 0; + }; + caching = { + UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket; + DATABASE = 1; + }; + }; + }; + + extraConfig = '' + with open("${cfg.secretKeyFile}", "r") as file: + SECRET_KEY = file.readline() + '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) '' + with open("${cfg.peeringdbApiKeyFile}", "r") as file: + PEERINGDB_API_KEY = file.readline() + ''; + + plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]); + }; system.build.peeringManagerPkg = pkg; @@ -163,32 +202,30 @@ in { }; systemd.services = let - defaultServiceConfig = { - WorkingDirectory = "/var/lib/peering-manager"; - User = "peering-manager"; - Group = "peering-manager"; - StateDirectory = "peering-manager"; - StateDirectoryMode = "0750"; - Restart = "on-failure"; + defaults = { + environment = { + PYTHONPATH = pkg.pythonPath; + }; + serviceConfig = { + WorkingDirectory = "/var/lib/peering-manager"; + User = "peering-manager"; + Group = "peering-manager"; + StateDirectory = "peering-manager"; + StateDirectoryMode = "0750"; + Restart = "on-failure"; + }; }; in { - peering-manager-migration = { + peering-manager-migration = lib.recursiveUpdate defaults { description = "Peering Manager migrations"; wantedBy = [ "peering-manager.target" ]; - - environment = { - PYTHONPATH = pkg.pythonPath; - }; - - serviceConfig = defaultServiceConfig // { + serviceConfig = { Type = "oneshot"; - ExecStart = '' - ${pkg}/bin/peering-manager migrate - ''; + ExecStart = "${pkg}/bin/peering-manager migrate"; }; }; - peering-manager = { + peering-manager = lib.recursiveUpdate defaults { description = "Peering Manager WSGI Service"; wantedBy = [ "peering-manager.target" ]; after = [ "peering-manager-migration.service" ]; @@ -197,11 +234,7 @@ in { ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input ''; - environment = { - PYTHONPATH = pkg.pythonPath; - }; - - serviceConfig = defaultServiceConfig // { + serviceConfig = { ExecStart = '' ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \ --bind ${cfg.listenAddress}:${toString cfg.port} \ @@ -210,45 +243,92 @@ in { }; }; - peering-manager-rq = { + peering-manager-rq = lib.recursiveUpdate defaults { description = "Peering Manager Request Queue Worker"; wantedBy = [ "peering-manager.target" ]; after = [ "peering-manager.service" ]; + serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low"; + }; - environment = { - PYTHONPATH = pkg.pythonPath; + peering-manager-housekeeping = lib.recursiveUpdate defaults { + description = "Peering Manager housekeeping job"; + after = [ "peering-manager.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkg}/bin/peering-manager housekeeping"; }; + }; - serviceConfig = defaultServiceConfig // { - ExecStart = '' - ${pkg}/bin/peering-manager rqworker high default low - ''; + peering-manager-peeringdb-sync = lib.recursiveUpdate defaults { + description = "PeeringDB sync"; + after = [ "peering-manager.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkg}/bin/peering-manager peeringdb_sync"; }; }; - peering-manager-housekeeping = { - description = "Peering Manager housekeeping job"; + peering-manager-prefix-fetch = lib.recursiveUpdate defaults { + description = "Fetch IRR AS-SET prefixes"; after = [ "peering-manager.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkg}/bin/peering-manager grab_prefixes"; + }; + }; - environment = { - PYTHONPATH = pkg.pythonPath; + peering-manager-configuration-deployment = lib.recursiveUpdate defaults { + description = "Push configuration to routers"; + after = [ "peering-manager.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkg}/bin/peering-manager configure_routers"; }; + }; - serviceConfig = defaultServiceConfig // { + peering-manager-session-poll = lib.recursiveUpdate defaults { + description = "Poll peering sessions from routers"; + after = [ "peering-manager.service" ]; + serviceConfig = { Type = "oneshot"; - ExecStart = '' - ${pkg}/bin/peering-manager housekeeping - ''; + ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all"; }; }; }; - systemd.timers.peering-manager-housekeeping = { - description = "Run Peering Manager housekeeping job"; - wantedBy = [ "timers.target" ]; + systemd.timers = { + peering-manager-housekeeping = { + description = "Run Peering Manager housekeeping job"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "daily"; + }; + + peering-manager-peeringdb-sync = { + enable = lib.mkDefault cfg.enableScheduledTasks; + description = "Sync PeeringDB at 2:30"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "02:30:00"; + }; + + peering-manager-prefix-fetch = { + enable = lib.mkDefault cfg.enableScheduledTasks; + description = "Fetch IRR AS-SET prefixes at 4:30"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "04:30:00"; + }; + + peering-manager-configuration-deployment = { + enable = lib.mkDefault cfg.enableScheduledTasks; + description = "Push router configuration every hour 5 minutes before full hour"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*:55:00"; + }; - timerConfig = { - OnCalendar = "daily"; + peering-manager-session-poll = { + enable = lib.mkDefault cfg.enableScheduledTasks; + description = "Poll peering sessions from routers every hour"; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = "*:00:00"; }; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/phylactery.nix b/nixpkgs/nixos/modules/services/web-apps/phylactery.nix index 4801bd203b48..723b38ee75d9 100644 --- a/nixpkgs/nixos/modules/services/web-apps/phylactery.nix +++ b/nixpkgs/nixos/modules/services/web-apps/phylactery.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.phylactery; in { options.services.phylactery = { - enable = mkEnableOption (lib.mdDoc "Whether to enable Phylactery server"); + enable = mkEnableOption (lib.mdDoc "Phylactery server"); host = mkOption { type = types.str; diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.nix b/nixpkgs/nixos/modules/services/web-apps/plausible.nix index e2d5cdc4f7c7..e5deb6cf511f 100644 --- a/nixpkgs/nixos/modules/services/web-apps/plausible.nix +++ b/nixpkgs/nixos/modules/services/web-apps/plausible.nix @@ -296,6 +296,6 @@ in { ]; }; - meta.maintainers = with maintainers; [ ma27 ]; + meta.maintainers = with maintainers; [ ]; meta.doc = ./plausible.md; } diff --git a/nixpkgs/nixos/modules/services/web-apps/rimgo.nix b/nixpkgs/nixos/modules/services/web-apps/rimgo.nix new file mode 100644 index 000000000000..4d35473fda31 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/rimgo.nix @@ -0,0 +1,107 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.rimgo; + inherit (lib) + mkOption + mkEnableOption + mkPackageOption + mkDefault + mkIf + types + literalExpression + optionalString + getExe + mapAttrs + ; +in +{ + options.services.rimgo = { + enable = mkEnableOption "rimgo"; + package = mkPackageOption pkgs "rimgo" { }; + settings = mkOption { + type = types.submodule { + freeformType = with types; attrsOf str; + options = { + PORT = mkOption { + type = types.port; + default = 3000; + example = 69420; + description = "The port to use."; + }; + ADDRESS = mkOption { + type = types.str; + default = "127.0.0.1"; + example = "1.1.1.1"; + description = "The address to listen on."; + }; + }; + }; + example = literalExpression '' + { + PORT = 69420; + FORCE_WEBP = "1"; + } + ''; + description = '' + Settings for rimgo, see [the official documentation](https://rimgo.codeberg.page/docs/usage/configuration/) for supported options. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.rimgo = { + description = "Rimgo"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = mapAttrs (_: toString) cfg.settings; + serviceConfig = { + ExecStart = getExe cfg.package; + AmbientCapabilities = mkIf (cfg.settings.PORT < 1024) [ + "CAP_NET_BIND_SERVICE" + ]; + DynamicUser = true; + Restart = "on-failure"; + RestartSec = "5s"; + CapabilityBoundingSet = [ + (optionalString (cfg.settings.PORT < 1024) "CAP_NET_BIND_SERVICE") + ]; + DeviceAllow = [ "" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = cfg.settings.PORT >= 1024; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ quantenzitrone ]; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix index e861a4185194..9cba5cb4fa9e 100644 --- a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix +++ b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix @@ -30,7 +30,7 @@ let in { options.services.snipe-it = { - enable = mkEnableOption (lib.mdDoc "A free open source IT asset/license management system"); + enable = mkEnableOption (lib.mdDoc "snipe-it, a free open source IT asset/license management system"); user = mkOption { default = "snipeit"; diff --git a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix index d4c987da1144..5d2e775d4521 100644 --- a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix +++ b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix @@ -34,7 +34,7 @@ let # copy additional plugin(s), theme(s) and language(s) ${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)} ${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)} - ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages} + ${concatMapStringsSep "\n" (language: "cp -r ${language}/* $out/share/wordpress/wp-content/languages/") cfg.languages} ''; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/writefreely.nix b/nixpkgs/nixos/modules/services/web-apps/writefreely.nix index a7671aa717f4..f92afa9276e3 100644 --- a/nixpkgs/nixos/modules/services/web-apps/writefreely.nix +++ b/nixpkgs/nixos/modules/services/web-apps/writefreely.nix @@ -120,7 +120,7 @@ let withConfigFile '' query () { local result=$(${sqlite}/bin/sqlite3 \ - '${cfg.stateDir}/${settings.database.filename}' + '${cfg.stateDir}/${settings.database.filename}' \ "$1" \ ) diff --git a/nixpkgs/nixos/modules/services/web-apps/zitadel.nix b/nixpkgs/nixos/modules/services/web-apps/zitadel.nix new file mode 100644 index 000000000000..99b0a0bc56f6 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/zitadel.nix @@ -0,0 +1,223 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.zitadel; + + settingsFormat = pkgs.formats.yaml { }; +in +{ + options.services.zitadel = + let inherit (lib) mkEnableOption mkOption mkPackageOption types; + in { + enable = mkEnableOption "ZITADEL, a user and identity access management platform"; + + package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; }; + + user = mkOption { + type = types.str; + default = "zitadel"; + description = "The user to run ZITADEL under."; + }; + + group = mkOption { + type = types.str; + default = "zitadel"; + description = "The group to run ZITADEL under."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open the port specified in `listenPort` in the firewall. + ''; + }; + + masterKeyFile = mkOption { + type = types.path; + description = '' + Path to a file containing a master encryption key for ZITADEL. The + key must be 32 bytes. + ''; + }; + + tlsMode = mkOption { + type = types.enum [ "external" "enabled" "disabled" ]; + default = "external"; + example = "enabled"; + description = '' + The TLS mode to use. Options are: + + - enabled: ZITADEL accepts HTTPS connections directly. You must + configure TLS if this option is selected. + - external: ZITADEL forces HTTPS connections, with TLS terminated at a + reverse proxy. + - disabled: ZITADEL accepts HTTP connections only. Should only be used + for testing. + ''; + }; + + settings = mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + + options = { + Port = mkOption { + type = types.port; + default = 8080; + description = "The port that ZITADEL listens on."; + }; + + TLS = { + KeyPath = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the TLS certificate private key."; + }; + Key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The TLS certificate private key, as a base64-encoded string. + + Note that the contents of this option will be added to the Nix + store as world-readable plain text. Set + [KeyPath](#opt-services.zitadel.settings.TLS.KeyPath) instead + if this is undesired. + ''; + }; + CertPath = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the TLS certificate."; + }; + Cert = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The TLS certificate, as a base64-encoded string. + + Note that the contents of this option will be added to the Nix + store as world-readable plain text. Set + [CertPath](#opt-services.zitadel.settings.TLS.CertPath) instead + if this is undesired. + ''; + }; + }; + }; + }; + default = { }; + example = lib.literalExpression '' + { + Port = 8123; + ExternalDomain = "example.com"; + TLS = { + CertPath = "/path/to/cert.pem"; + KeyPath = "/path/to/cert.key"; + }; + Database.cockroach.Host = "db.example.com"; + }; + ''; + description = '' + Contents of the runtime configuration file. See + https://zitadel.com/docs/self-hosting/manage/configure for more + details. + ''; + }; + + extraSettingsPaths = mkOption { + type = types.listOf types.path; + default = [ ]; + description = '' + A list of paths to extra settings files. These will override the + values set in [settings](#opt-services.zitadel.settings). Useful if + you want to keep sensitive secrets out of the Nix store. + ''; + }; + + steps = mkOption { + type = settingsFormat.type; + default = { }; + example = lib.literalExpression '' + { + FirstInstance = { + InstanceName = "Example"; + Org.Human = { + UserName = "foobar"; + FirstName = "Foo"; + LastName = "Bar"; + }; + }; + } + ''; + description = '' + Contents of the database initialization config file. See + https://zitadel.com/docs/self-hosting/manage/configure for more + details. + ''; + }; + + extraStepsPaths = mkOption { + type = types.listOf types.path; + default = [ ]; + description = '' + A list of paths to extra steps files. These will override the values + set in [steps](#opt-services.zitadel.steps). Useful if you want to + keep sensitive secrets out of the Nix store. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [{ + assertion = cfg.tlsMode == "enabled" + -> ((cfg.settings.TLS.Key != null || cfg.settings.TLS.KeyPath != null) + && (cfg.settings.TLS.Cert != null || cfg.settings.TLS.CertPath + != null)); + message = '' + A TLS certificate and key must be configured in + services.zitadel.settings.TLS if services.zitadel.tlsMode is enabled. + ''; + }]; + + networking.firewall.allowedTCPPorts = + lib.mkIf cfg.openFirewall [ cfg.settings.Port ]; + + systemd.services.zitadel = + let + configFile = settingsFormat.generate "config.yaml" cfg.settings; + stepsFile = settingsFormat.generate "steps.yaml" cfg.steps; + + args = lib.cli.toGNUCommandLineShell { } { + config = cfg.extraSettingsPaths ++ [ configFile ]; + steps = cfg.extraStepsPaths ++ [ stepsFile ]; + masterkeyFile = cfg.masterKeyFile; + inherit (cfg) tlsMode; + }; + in + { + description = "ZITADEL identity access management"; + path = [ cfg.package ]; + wantedBy = [ "multi-user.target" ]; + + script = '' + zitadel start-from-init ${args} + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + }; + }; + + users.users.zitadel = lib.mkIf (cfg.user == "zitadel") { + isSystemUser = true; + group = cfg.group; + }; + users.groups.zitadel = lib.mkIf (cfg.group == "zitadel") { }; + }; + + meta.maintainers = with lib.maintainers; [ Sorixelle ]; +} |