diff --git a/hosts/tsuki/configuration.nix b/hosts/tsuki/configuration.nix
index 9fbd90f..ddbecf9 100644
--- a/hosts/tsuki/configuration.nix
+++ b/hosts/tsuki/configuration.nix
@@ -9,7 +9,7 @@
./services/gitea
./services/grafana
./services/headscale.nix
- ./services/hedgedoc.nix
+ ./services/hedgedoc
./services/hydra.nix
./services/invidious.nix
# ./services/jitsi.nix
diff --git a/hosts/tsuki/services/hedgedoc/default.nix b/hosts/tsuki/services/hedgedoc/default.nix
new file mode 100644
index 0000000..777356b
--- /dev/null
+++ b/hosts/tsuki/services/hedgedoc/default.nix
@@ -0,0 +1,93 @@
+{ pkgs, lib, config, options, ... }: let
+ cfg = config.services.hedgedoc;
+in {
+ imports = [ ./hedgedoc.nix ];
+ disabledModules = [ "services/web-apps/hedgedoc.nix" ];
+
+ config = {
+ # Contains CMD_SESSION_SECRET and CMD_OAUTH2_CLIENT_SECRET
+ sops.secrets."hedgedoc/env" = {
+ restartUnits = [ "hedgedoc.service" ];
+ };
+
+ services.hedgedoc = {
+ enable = true;
+ workDir = "${config.machineVars.dataDrives.default}/var/hedgedoc";
+ environmentFile = config.sops.secrets."hedgedoc/env".path;
+ settings = {
+ domain = "docs.nani.wtf";
+ email = false;
+ allowAnonymous = false;
+ allowAnonymousEdits = true;
+ protocolUseSSL = true;
+
+ db = {
+ username = "hedgedoc";
+ # TODO: set a password
+ database = "hedgedoc";
+ host = "/var/run/postgresql";
+ dialect = "postgresql";
+ };
+
+ oauth2 = let
+ authServerUrl = config.services.kanidm.serverSettings.origin;
+ in rec {
+ baseURL = "${authServerUrl}/oauth2";
+ tokenURL = "${authServerUrl}/oauth2/token";
+ authorizationURL = "${authServerUrl}/ui/oauth2";
+ userProfileURL = "${authServerUrl}/oauth2/openid/${clientID}/userinfo";
+
+ clientID = "hedgedoc";
+
+ scope = "openid email profile";
+ userProfileUsernameAttr = "name";
+ userProfileEmailAttr = "email";
+ userProfileDisplayNameAttr = "displayname";
+
+ providerName = "KaniDM";
+ };
+ };
+ };
+
+ services.postgresql = {
+ ensureDatabases = [ "hedgedoc" ];
+ ensureUsers = [{
+ name = "hedgedoc";
+ ensurePermissions = {
+ "DATABASE \"hedgedoc\"" = "ALL PRIVILEGES";
+ };
+ }];
+ };
+
+ systemd.services.hedgedoc = {
+ requires = [
+ "postgresql.service"
+ "kanidm.service"
+ ];
+ serviceConfig = {
+ CapabilityBoundingSet = "";
+ LockPersonality = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateTmp = true;
+ PrivateUsers = true;
+ ProtectClock = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectProc = "invisible";
+ ProtectSystem = "strict";
+ ReadWritePaths = [ cfg.workDir ];
+ RemoveIPC = true;
+ RestrictSUIDSGID = true;
+ UMask = "0007";
+ RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+ SystemCallArchitectures = "native";
+ SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+ };
+ };
+ };
+}
diff --git a/hosts/tsuki/services/hedgedoc/hedgedoc.nix b/hosts/tsuki/services/hedgedoc/hedgedoc.nix
new file mode 100644
index 0000000..e2014a9
--- /dev/null
+++ b/hosts/tsuki/services/hedgedoc/hedgedoc.nix
@@ -0,0 +1,1075 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.hedgedoc;
+
+ # 21.03 will not be an official release - it was instead 21.05. This
+ # 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";
+
+ 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
+ '';
+in
+{
+ imports = [
+ (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
+ (mkRenamedOptionModule
+ [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
+ ];
+
+ options.services.hedgedoc = {
+ enable = mkEnableOption (lib.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 = 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`.
+ '';
+ };
+ 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 .
+ '';
+ };
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ });
+ 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.
+ '';
+ };
+ };
+ };
+ });
+ default = null;
+ description = lib.mdDoc "Configure the SAML integration.";
+ };
+ }; in lib.mkOption {
+ type = lib.types.submodule {
+ freeformType = settingsFormat.type;
+ inherit options;
+ };
+ description = lib.mdDoc ''
+ HedgeDoc configuration, see
+
+ for documentation.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = "/var/lib/hedgedoc/hedgedoc.env";
+ description = lib.mdDoc ''
+ Environment file as defined in {manpage}`systemd.exec(5)`.
+
+ Secrets may be passed to the service without adding them to the world-readable
+ Nix store, by specifying placeholder variables as the option value in Nix and
+ setting these variables accordingly in the environment file.
+
+ ```
+ # snippet of HedgeDoc-related config
+ services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
+ services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
+ ```
+
+ ```
+ # content of the environment file
+ DB_PASSWORD=verysecretdbpassword
+ MINIO_SECRET_KEY=verysecretminiokey
+ ```
+
+ Note that this file needs to be available on the host on which
+ `HedgeDoc` is running.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.hedgedoc;
+ defaultText = literalExpression "pkgs.hedgedoc";
+ description = lib.mdDoc ''
+ Package that provides HedgeDoc.
+ '';
+ };
+
+ };
+
+ 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} = {};
+ users.users.${name} = {
+ description = "HedgeDoc service user";
+ group = name;
+ extraGroups = cfg.groups;
+ home = cfg.workDir;
+ createHome = true;
+ isSystemUser = true;
+ };
+
+ systemd.services.hedgedoc = {
+ description = "HedgeDoc Service";
+ 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}
+ '';
+ serviceConfig = {
+ WorkingDirectory = cfg.workDir;
+ StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
+ ExecStart = "${cfg.package}/bin/hedgedoc";
+ EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+ Environment = [
+ "CMD_CONFIG_FILE=${cfg.workDir}/config.json"
+ "NODE_ENV=production"
+ ];
+ Restart = "always";
+ User = name;
+ PrivateTmp = true;
+ };
+ };
+ };
+}