nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
2023-01-21 18:08:38 +01:00

1075 lines
34 KiB
Nix

{ 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 <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.
'';
};
};
});
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 = types.str;
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
<https://docs.hedgedoc.org/configuration/>
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;
};
};
};
}