diff options
Diffstat (limited to 'nixos/modules/services/web-apps')
22 files changed, 815 insertions, 1163 deletions
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix index 8d1775258612..eaee70c712bb 100644 --- a/nixos/modules/services/web-apps/akkoma.nix +++ b/nixos/modules/services/web-apps/akkoma.nix @@ -282,11 +282,11 @@ let AKKOMA_CONFIG_PATH="$RUNTIME_DIRECTORY/config.exs" \ ERL_EPMD_ADDRESS="${cfg.dist.address}" \ ERL_EPMD_PORT="${toString cfg.dist.epmdPort}" \ - ERL_FLAGS="${concatStringsSep " " [ - "-kernel inet_dist_use_interface '${erlAddr cfg.dist.address}'" - "-kernel inet_dist_listen_min ${toString cfg.dist.portMin}" - "-kernel inet_dist_listen_max ${toString cfg.dist.portMax}" - ]}" \ + ERL_FLAGS=${lib.escapeShellArg (lib.escapeShellArgs ([ + "-kernel" "inet_dist_use_interface" (erlAddr cfg.dist.address) + "-kernel" "inet_dist_listen_min" (toString cfg.dist.portMin) + "-kernel" "inet_dist_listen_max" (toString cfg.dist.portMax) + ] ++ cfg.dist.extraFlags))} \ RELEASE_COOKIE="$(<"$RUNTIME_DIRECTORY/cookie")" \ RELEASE_NAME="akkoma" \ exec "${cfg.package}/bin/$(basename "$0")" "$@" @@ -553,6 +553,13 @@ in { description = mdDoc "TCP port to bind Erlang Port Mapper Daemon to."; }; + extraFlags = mkOption { + type = with types; listOf str; + default = [ ]; + description = mdDoc "Extra flags to pass to Erlang"; + example = [ "+sbwt" "none" "+sbwtdcpu" "none" "+sbwtdio" "none" ]; + }; + portMin = mkOption { type = types.port; default = 49152; diff --git a/nixos/modules/services/web-apps/c2fmzq-server.md b/nixos/modules/services/web-apps/c2fmzq-server.md new file mode 100644 index 000000000000..236953bd4ff7 --- /dev/null +++ b/nixos/modules/services/web-apps/c2fmzq-server.md @@ -0,0 +1,42 @@ +# c2FmZQ {#module-services-c2fmzq} + +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 +``` +{ + services.c2fmzq-server.enable = true; +} +``` +This will spin up an instance of the server which is API-compatible with +[Stingle Photos](https://stingle.org) and an experimental Progressive Web App +(PWA) to interact with the storage via the browser. + +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. + +``` +{ + services.c2fmzq-server = { + enable = true; + bindIP = "127.0.0.1"; # default + port = 8080; # default + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts."example.com" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8080"; + }; + }; + }; +} +``` + +For more information, see <https://github.com/c2FmZQ/c2FmZQ/>. diff --git a/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixos/modules/services/web-apps/c2fmzq-server.nix new file mode 100644 index 000000000000..2749c2a5a87a --- /dev/null +++ b/nixos/modules/services/web-apps/c2fmzq-server.nix @@ -0,0 +1,125 @@ +{ lib, pkgs, config, ... }: + +let + inherit (lib) mkEnableOption mkPackageOption mkOption types; + + cfg = config.services.c2fmzq-server; + + argsFormat = { + type = with lib.types; nullOr (oneOf [ bool int str ]); + generate = lib.cli.toGNUCommandLineShell { }; + }; +in { + options.services.c2fmzq-server = { + enable = mkEnableOption "c2fmzq-server"; + + bindIP = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The local address to use."; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "The local port to use."; + }; + + passphraseFile = mkOption { + type = types.str; + example = "/run/secrets/c2fmzq/pwfile"; + description = "Path to file containing the database passphrase"; + }; + + package = mkPackageOption pkgs "c2fmzq" { }; + + settings = mkOption { + type = types.submodule { + freeformType = argsFormat.type; + + options = { + address = mkOption { + internal = true; + type = types.str; + default = "${cfg.bindIP}:${toString cfg.port}"; + }; + + database = mkOption { + type = types.str; + default = "%S/c2fmzq-server/data"; + description = "Path of the database"; + }; + + verbose = mkOption { + type = types.ints.between 1 3; + default = 2; + description = "The level of logging verbosity: 1:Error 2:Info 3:Debug"; + }; + }; + }; + description = '' + Configuration for c2FmZQ-server passed as CLI arguments. + Run {command}`c2FmZQ-server help` for supported values. + ''; + example = { + verbose = 3; + allow-new-accounts = true; + auto-approve-new-accounts = true; + encrypt-metadata = true; + enable-webapp = true; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.c2fmzq-server = { + description = "c2FmZQ-server"; + documentation = [ "https://github.com/c2FmZQ/c2FmZQ/blob/main/README.md" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "network-online.target" ]; + + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} ${argsFormat.generate cfg.settings}"; + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DynamicUser = true; + Environment = "C2FMZQ_PASSPHRASE_FILE=%d/passphrase-file"; + IPAccounting = true; + IPAddressAllow = cfg.bindIP; + IPAddressDeny = "any"; + LoadCredential = "passphrase-file:${cfg.passphraseFile}"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + PrivateUsers = true; + 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" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SocketBindAllow = cfg.port; + SocketBindDeny = "any"; + StateDirectory = "c2fmzq-server"; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ]; + }; + }; + }; + + meta = { + doc = ./c2fmzq-server.md; + maintainers = with lib.maintainers; [ hmenke ]; + }; +} diff --git a/nixos/modules/services/web-apps/cloudlog.nix b/nixos/modules/services/web-apps/cloudlog.nix index da2cf93d7f1c..5519d6967a12 100644 --- a/nixos/modules/services/web-apps/cloudlog.nix +++ b/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/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix index bd041db007a1..0c4a71c6dfe4 100644 --- a/nixos/modules/services/web-apps/dex.nix +++ b/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/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix index bfa5fd5aff25..1a66f077b09d 100644 --- a/nixos/modules/services/web-apps/hedgedoc.nix +++ b/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/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix index 0fc283ff5219..be8ecc645e59 100644 --- a/nixos/modules/services/web-apps/hledger-web.nix +++ b/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/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix index 1a852ec352f2..6cb2d9ec785e 100644 --- a/nixos/modules/services/web-apps/isso.nix +++ b/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/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 3825b03c2449..21416be35877 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/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/nixos/modules/services/web-apps/lanraragi.nix b/nixos/modules/services/web-apps/lanraragi.nix new file mode 100644 index 000000000000..f1ab8b8b4eb4 --- /dev/null +++ b/nixos/modules/services/web-apps/lanraragi.nix @@ -0,0 +1,100 @@ +{ pkgs, lib, config, ... }: + +let + cfg = config.services.lanraragi; +in +{ + meta.maintainers = with lib.maintainers; [ tomasajt ]; + + options.services = { + lanraragi = { + enable = lib.mkEnableOption (lib.mdDoc "LANraragi"); + package = lib.mkPackageOptionMD pkgs "lanraragi" { }; + + port = lib.mkOption { + type = lib.types.port; + default = 3000; + description = lib.mdDoc "Port for LANraragi's web interface."; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/keys/lanraragi-password"; + description = lib.mdDoc '' + A file containing the password for LANraragi's admin interface. + ''; + }; + + redis = { + port = lib.mkOption { + type = lib.types.port; + default = 6379; + description = lib.mdDoc "Port for LANraragi's Redis server."; + }; + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/keys/redis-lanraragi-password"; + description = lib.mdDoc '' + A file containing the password for LANraragi's Redis server. + ''; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.redis.servers.lanraragi = { + enable = true; + port = cfg.redis.port; + requirePassFile = cfg.redis.passwordFile; + }; + + systemd.services.lanraragi = { + description = "LANraragi main service"; + after = [ "network.target" "redis-lanraragi.service" ]; + requires = [ "redis-lanraragi.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = lib.getExe cfg.package; + DynamicUser = true; + StateDirectory = "lanraragi"; + RuntimeDirectory = "lanraragi"; + LogsDirectory = "lanraragi"; + Restart = "on-failure"; + WorkingDirectory = "/var/lib/lanraragi"; + }; + environment = { + "LRR_TEMP_DIRECTORY" = "/run/lanraragi"; + "LRR_LOG_DIRECTORY" = "/var/log/lanraragi"; + "LRR_NETWORK" = "http://*:${toString cfg.port}"; + "HOME" = "/var/lib/lanraragi"; + }; + preStart = '' + REDIS_PASS=${lib.optionalString (cfg.redis.passwordFile != null) "$(head -n1 ${cfg.redis.passwordFile})"} + cat > lrr.conf <<EOF + { + redis_address => "127.0.0.1:${toString cfg.redis.port}", + redis_password => "$REDIS_PASS", + redis_database => "0", + redis_database_minion => "1", + redis_database_config => "2", + redis_database_search => "3", + } + EOF + '' + lib.optionalString (cfg.passwordFile != null) '' + PASS_HASH=$( + PASS=$(head -n1 ${cfg.passwordFile}) ${cfg.package.perlEnv}/bin/perl -I${cfg.package}/share/lanraragi/lib -e \ + 'use LANraragi::Controller::Config; print LANraragi::Controller::Config::make_password_hash($ENV{PASS})' \ + 2>/dev/null + ) + + ${lib.getExe pkgs.redis} -h 127.0.0.1 -p ${toString cfg.redis.port} -a "$REDIS_PASS" <<EOF + SELECT 2 + HSET LRR_CONFIG password $PASS_HASH + EOF + ''; + }; + }; +} diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix index cb864321ef27..652dc8840252 100644 --- a/nixos/modules/services/web-apps/meme-bingo-web.nix +++ b/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/nixos/modules/services/web-apps/microbin.nix b/nixos/modules/services/web-apps/microbin.nix new file mode 100644 index 000000000000..233bfac6e699 --- /dev/null +++ b/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/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md index cbd7b5b3d066..a25bed30e47f 100644 --- a/nixos/modules/services/web-apps/nextcloud.md +++ b/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/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index e0a7e7d4859c..f9713cac47e9 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/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/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix index 1d8298963e6d..0e3bd07c1fc1 100644 --- a/nixos/modules/services/web-apps/outline.nix +++ b/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/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix index 55bf0da7b7b9..7012df6dffbf 100644 --- a/nixos/modules/services/web-apps/peering-manager.nix +++ b/nixos/modules/services/web-apps/peering-manager.nix @@ -35,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/) ''; }; @@ -194,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" ]; @@ -228,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} \ @@ -241,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/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix index 4801bd203b48..723b38ee75d9 100644 --- a/nixos/modules/services/web-apps/phylactery.nix +++ b/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/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix index 84914b7ad11c..576b54a7edf2 100644 --- a/nixos/modules/services/web-apps/plausible.nix +++ b/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/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix index f0505e052e1c..71b5ad4d4c06 100644 --- a/nixos/modules/services/web-apps/shiori.nix +++ b/nixos/modules/services/web-apps/shiori.nix @@ -29,6 +29,13 @@ in { default = 8080; description = lib.mdDoc "The port of the Shiori web application"; }; + + webRoot = mkOption { + type = types.str; + default = "/"; + example = "/shiori"; + description = lib.mdDoc "The root of the Shiori web application"; + }; }; }; @@ -40,7 +47,7 @@ in { environment.SHIORI_DIR = "/var/lib/shiori"; serviceConfig = { - ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}'"; + ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'"; DynamicUser = true; StateDirectory = "shiori"; diff --git a/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix index e861a4185194..4fbf2bad750b 100644 --- a/nixos/modules/services/web-apps/snipe-it.nix +++ b/nixos/modules/services/web-apps/snipe-it.nix @@ -18,19 +18,23 @@ let inherit (snipe-it.passthru) phpPackage; # shell script for local administration - artisan = pkgs.writeScriptBin "snipe-it" '' + artisan = (pkgs.writeScriptBin "snipe-it" '' #! ${pkgs.runtimeShell} - cd ${snipe-it} + cd "${snipe-it}/share/php/snipe-it" sudo=exec if [[ "$USER" != ${user} ]]; then sudo='exec /run/wrappers/bin/sudo -u ${user}' fi $sudo ${phpPackage}/bin/php artisan $* - ''; + '').overrideAttrs (old: { + meta = old.meta // { + mainProgram = "snipe-it"; + }; + }); 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"; @@ -357,7 +361,7 @@ in { services.nginx = { enable = mkDefault true; virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx { - root = mkForce "${snipe-it}/public"; + root = mkForce "${snipe-it}/share/php/snipe-it/public"; extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"; locations = { "/" = { @@ -394,7 +398,7 @@ in { RuntimeDirectory = "snipe-it/cache"; RuntimeDirectoryMode = "0700"; }; - path = [ pkgs.replace-secret ]; + path = [ pkgs.replace-secret artisan ]; script = let isSecret = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret); @@ -451,7 +455,7 @@ in { rm "${cfg.dataDir}"/bootstrap/cache/*.php || true # migrate db - ${phpPackage}/bin/php artisan migrate --force + ${lib.getExe artisan} migrate --force # A placeholder file for invalid barcodes invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif" diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix index a7671aa717f4..f92afa9276e3 100644 --- a/nixos/modules/services/web-apps/writefreely.nix +++ b/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/nixos/modules/services/web-apps/zitadel.nix b/nixos/modules/services/web-apps/zitadel.nix index f225d138cc43..99b0a0bc56f6 100644 --- a/nixos/modules/services/web-apps/zitadel.nix +++ b/nixos/modules/services/web-apps/zitadel.nix @@ -9,7 +9,7 @@ in options.services.zitadel = let inherit (lib) mkEnableOption mkOption mkPackageOption types; in { - enable = mkEnableOption "ZITADEL, a user and identity access management platform."; + enable = mkEnableOption "ZITADEL, a user and identity access management platform"; package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; }; |