at 25.11-pre 11 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 inherit (lib) mkOption types literalExpression; 10 11 cfg = config.services.hedgedoc; 12 13 # 21.03 will not be an official release - it was instead 21.05. This 14 # versionAtLeast statement remains set to 21.03 for backwards compatibility. 15 # See https://github.com/NixOS/nixpkgs/pull/108899 and 16 # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md. 17 name = if lib.versionAtLeast config.system.stateVersion "21.03" then "hedgedoc" else "codimd"; 18 19 settingsFormat = pkgs.formats.json { }; 20in 21{ 22 meta.maintainers = with lib.maintainers; [ 23 SuperSandro2000 24 h7x4 25 ]; 26 27 imports = [ 28 (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ]) 29 (lib.mkRenamedOptionModule 30 [ "services" "hedgedoc" "configuration" ] 31 [ "services" "hedgedoc" "settings" ] 32 ) 33 (lib.mkRenamedOptionModule 34 [ "services" "hedgedoc" "groups" ] 35 [ "users" "users" "hedgedoc" "extraGroups" ] 36 ) 37 (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] '' 38 This option has been removed in favor of systemd managing the state directory. 39 40 If you have set this option without specifying `services.hedgedoc.settings.uploadsPath`, 41 please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point 42 at the correct location. 43 '') 44 ]; 45 46 options.services.hedgedoc = { 47 package = lib.mkPackageOption pkgs "hedgedoc" { }; 48 enable = lib.mkEnableOption "the HedgeDoc Markdown Editor"; 49 50 settings = mkOption { 51 type = types.submodule { 52 freeformType = settingsFormat.type; 53 options = { 54 domain = mkOption { 55 type = with types; nullOr str; 56 default = null; 57 example = "hedgedoc.org"; 58 description = '' 59 Domain to use for website. 60 61 This is useful if you are trying to run hedgedoc behind 62 a reverse proxy. 63 ''; 64 }; 65 urlPath = mkOption { 66 type = with types; nullOr str; 67 default = null; 68 example = "hedgedoc"; 69 description = '' 70 URL path for the website. 71 72 This is useful if you are hosting hedgedoc on a path like 73 `www.example.com/hedgedoc` 74 ''; 75 }; 76 host = mkOption { 77 type = with types; nullOr str; 78 default = "localhost"; 79 description = '' 80 Address to listen on. 81 ''; 82 }; 83 port = mkOption { 84 type = types.port; 85 default = 3000; 86 example = 80; 87 description = '' 88 Port to listen on. 89 ''; 90 }; 91 path = mkOption { 92 type = with types; nullOr path; 93 default = null; 94 example = "/run/hedgedoc/hedgedoc.sock"; 95 description = '' 96 Path to UNIX domain socket to listen on 97 98 ::: {.note} 99 If specified, {option}`host` and {option}`port` will be ignored. 100 ::: 101 ''; 102 }; 103 protocolUseSSL = mkOption { 104 type = types.bool; 105 default = false; 106 example = true; 107 description = '' 108 Use `https://` for all links. 109 110 This is useful if you are trying to run hedgedoc behind 111 a reverse proxy. 112 113 ::: {.note} 114 Only applied if {option}`domain` is set. 115 ::: 116 ''; 117 }; 118 allowOrigin = mkOption { 119 type = with types; listOf str; 120 default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]; 121 defaultText = literalExpression '' 122 with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ] 123 ''; 124 example = [ 125 "localhost" 126 "hedgedoc.org" 127 ]; 128 description = '' 129 List of domains to whitelist. 130 ''; 131 }; 132 db = mkOption { 133 type = types.attrs; 134 default = { 135 dialect = "sqlite"; 136 storage = "/var/lib/${name}/db.sqlite"; 137 }; 138 defaultText = literalExpression '' 139 { 140 dialect = "sqlite"; 141 storage = "/var/lib/hedgedoc/db.sqlite"; 142 } 143 ''; 144 example = literalExpression '' 145 db = { 146 username = "hedgedoc"; 147 database = "hedgedoc"; 148 host = "localhost:5432"; 149 # or via socket 150 # host = "/run/postgresql"; 151 dialect = "postgresql"; 152 }; 153 ''; 154 description = '' 155 Specify the configuration for sequelize. 156 HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`. 157 See <https://sequelize.readthedocs.io/en/v3/> 158 for more information. 159 160 ::: {.note} 161 The relevant parts will be overriden if you set {option}`dbURL`. 162 ::: 163 ''; 164 }; 165 useSSL = mkOption { 166 type = types.bool; 167 default = false; 168 description = '' 169 Enable to use SSL server. 170 171 ::: {.note} 172 This will also enable {option}`protocolUseSSL`. 173 174 It will also require you to set the following: 175 176 - {option}`sslKeyPath` 177 - {option}`sslCertPath` 178 - {option}`sslCAPath` 179 - {option}`dhParamPath` 180 ::: 181 ''; 182 }; 183 uploadsPath = mkOption { 184 type = types.path; 185 default = "/var/lib/${name}/uploads"; 186 defaultText = "/var/lib/hedgedoc/uploads"; 187 description = '' 188 Directory for storing uploaded images. 189 ''; 190 }; 191 192 # Declared because we change the default to false. 193 allowGravatar = mkOption { 194 type = types.bool; 195 default = false; 196 example = true; 197 description = '' 198 Whether to enable [Libravatar](https://wiki.libravatar.org/) as 199 profile picture source on your instance. 200 201 Despite the naming of the setting, Hedgedoc replaced Gravatar 202 with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/) 203 ''; 204 }; 205 }; 206 }; 207 208 description = '' 209 HedgeDoc configuration, see 210 <https://docs.hedgedoc.org/configuration/> 211 for documentation. 212 ''; 213 }; 214 215 environmentFile = mkOption { 216 type = with types; nullOr path; 217 default = null; 218 example = "/var/lib/hedgedoc/hedgedoc.env"; 219 description = '' 220 Environment file as defined in {manpage}`systemd.exec(5)`. 221 222 Secrets may be passed to the service without adding them to the world-readable 223 Nix store, by specifying placeholder variables as the option value in Nix and 224 setting these variables accordingly in the environment file. 225 226 ``` 227 # snippet of HedgeDoc-related config 228 services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb"; 229 services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY"; 230 ``` 231 232 ``` 233 # content of the environment file 234 DB_PASSWORD=verysecretdbpassword 235 MINIO_SECRET_KEY=verysecretminiokey 236 ``` 237 238 Note that this file needs to be available on the host on which 239 `HedgeDoc` is running. 240 ''; 241 }; 242 }; 243 244 config = lib.mkIf cfg.enable { 245 users.groups.${name} = { }; 246 users.users.${name} = { 247 description = "HedgeDoc service user"; 248 group = name; 249 isSystemUser = true; 250 }; 251 252 services.hedgedoc.settings = { 253 defaultNotePath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/default.md"; 254 docsPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/docs"; 255 viewPath = lib.mkDefault "${cfg.package}/share/hedgedoc/public/views"; 256 }; 257 258 systemd.services.hedgedoc = { 259 description = "HedgeDoc Service"; 260 documentation = [ "https://docs.hedgedoc.org/" ]; 261 wantedBy = [ "multi-user.target" ]; 262 after = [ "networking.target" ]; 263 preStart = 264 let 265 configFile = settingsFormat.generate "hedgedoc-config.json" { 266 production = cfg.settings; 267 }; 268 in 269 '' 270 ${pkgs.envsubst}/bin/envsubst \ 271 -o /run/${name}/config.json \ 272 -i ${configFile} 273 ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath} 274 ''; 275 serviceConfig = { 276 User = name; 277 Group = name; 278 279 Restart = "always"; 280 ExecStart = lib.getExe cfg.package; 281 RuntimeDirectory = [ name ]; 282 StateDirectory = [ name ]; 283 WorkingDirectory = "/run/${name}"; 284 ReadWritePaths = [ 285 "-${cfg.settings.uploadsPath}" 286 ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ]; 287 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; 288 Environment = [ 289 "CMD_CONFIG_FILE=/run/${name}/config.json" 290 "NODE_ENV=production" 291 ]; 292 293 # Hardening 294 AmbientCapabilities = ""; 295 CapabilityBoundingSet = ""; 296 LockPersonality = true; 297 NoNewPrivileges = true; 298 PrivateDevices = true; 299 PrivateMounts = true; 300 PrivateTmp = true; 301 PrivateUsers = true; 302 ProcSubset = "pid"; 303 ProtectClock = true; 304 ProtectControlGroups = true; 305 ProtectHome = true; 306 ProtectHostname = true; 307 ProtectKernelLogs = true; 308 ProtectKernelModules = true; 309 ProtectKernelTunables = true; 310 ProtectProc = "invisible"; 311 ProtectSystem = "strict"; 312 RemoveIPC = true; 313 RestrictAddressFamilies = [ 314 "AF_INET" 315 "AF_INET6" 316 # Required for connecting to database sockets, 317 # and listening to unix socket at `cfg.settings.path` 318 "AF_UNIX" 319 ]; 320 RestrictNamespaces = true; 321 RestrictRealtime = true; 322 RestrictSUIDSGID = true; 323 SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port; 324 SocketBindDeny = "any"; 325 SystemCallArchitectures = "native"; 326 SystemCallFilter = [ 327 "@system-service" 328 "~@privileged @obsolete" 329 "@pkey" 330 "fchown" # needed for filesystem image backend 331 ]; 332 UMask = "0007"; 333 }; 334 }; 335 }; 336}