at 23.11-pre 8.0 kB view raw
1{ config, lib, pkgs, buildEnv, ... }: 2 3with lib; 4 5let 6 defaultUser = "healthchecks"; 7 cfg = config.services.healthchecks; 8 pkg = cfg.package; 9 boolToPython = b: if b then "True" else "False"; 10 environment = { 11 PYTHONPATH = pkg.pythonPath; 12 STATIC_ROOT = cfg.dataDir + "/static"; 13 DB_NAME = "${cfg.dataDir}/healthchecks.sqlite"; 14 } // cfg.settings; 15 16 environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment); 17 18 healthchecksManageScript = pkgs.writeShellScriptBin "healthchecks-manage" '' 19 sudo=exec 20 if [[ "$USER" != "${cfg.user}" ]]; then 21 sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env --preserve-env=PYTHONPATH' 22 fi 23 export $(cat ${environmentFile} | xargs) 24 $sudo ${pkg}/opt/healthchecks/manage.py "$@" 25 ''; 26in 27{ 28 options.services.healthchecks = { 29 enable = mkEnableOption (lib.mdDoc "healthchecks") // { 30 description = lib.mdDoc '' 31 Enable healthchecks. 32 It is expected to be run behind a HTTP reverse proxy. 33 ''; 34 }; 35 36 package = mkOption { 37 default = pkgs.healthchecks; 38 defaultText = literalExpression "pkgs.healthchecks"; 39 type = types.package; 40 description = lib.mdDoc "healthchecks package to use."; 41 }; 42 43 user = mkOption { 44 default = defaultUser; 45 type = types.str; 46 description = lib.mdDoc '' 47 User account under which healthchecks runs. 48 49 ::: {.note} 50 If left as the default value this user will automatically be created 51 on system activation, otherwise you are responsible for 52 ensuring the user exists before the healthchecks service starts. 53 ::: 54 ''; 55 }; 56 57 group = mkOption { 58 default = defaultUser; 59 type = types.str; 60 description = lib.mdDoc '' 61 Group account under which healthchecks runs. 62 63 ::: {.note} 64 If left as the default value this group will automatically be created 65 on system activation, otherwise you are responsible for 66 ensuring the group exists before the healthchecks service starts. 67 ::: 68 ''; 69 }; 70 71 listenAddress = mkOption { 72 type = types.str; 73 default = "localhost"; 74 description = lib.mdDoc "Address the server will listen on."; 75 }; 76 77 port = mkOption { 78 type = types.port; 79 default = 8000; 80 description = lib.mdDoc "Port the server will listen on."; 81 }; 82 83 dataDir = mkOption { 84 type = types.str; 85 default = "/var/lib/healthchecks"; 86 description = lib.mdDoc '' 87 The directory used to store all data for healthchecks. 88 89 ::: {.note} 90 If left as the default value this directory will automatically be created before 91 the healthchecks server starts, otherwise you are responsible for ensuring the 92 directory exists with appropriate ownership and permissions. 93 ::: 94 ''; 95 }; 96 97 settings = lib.mkOption { 98 description = lib.mdDoc '' 99 Environment variables which are read by healthchecks `(local)_settings.py`. 100 101 Settings which are explicitly covered in options bewlow, are type-checked and/or transformed 102 before added to the environment, everything else is passed as a string. 103 104 See <https://healthchecks.io/docs/self_hosted_configuration/> 105 for a full documentation of settings. 106 107 We add two variables to this list inside the packages `local_settings.py.` 108 - STATIC_ROOT to set a state directory for dynamically generated static files. 109 - SECRET_KEY_FILE to read SECRET_KEY from a file at runtime and keep it out of /nix/store. 110 ''; 111 type = types.submodule { 112 freeformType = types.attrsOf types.str; 113 options = { 114 ALLOWED_HOSTS = lib.mkOption { 115 type = types.listOf types.str; 116 default = [ "*" ]; 117 description = lib.mdDoc "The host/domain names that this site can serve."; 118 apply = lib.concatStringsSep ","; 119 }; 120 121 SECRET_KEY_FILE = mkOption { 122 type = types.path; 123 description = lib.mdDoc "Path to a file containing the secret key."; 124 }; 125 126 DEBUG = mkOption { 127 type = types.bool; 128 default = false; 129 description = lib.mdDoc "Enable debug mode."; 130 apply = boolToPython; 131 }; 132 133 REGISTRATION_OPEN = mkOption { 134 type = types.bool; 135 default = false; 136 description = lib.mdDoc '' 137 A boolean that controls whether site visitors can create new accounts. 138 Set it to false if you are setting up a private Healthchecks instance, 139 but it needs to be publicly accessible (so, for example, your cloud 140 services can send pings to it). 141 If you close new user registration, you can still selectively invite 142 users to your team account. 143 ''; 144 apply = boolToPython; 145 }; 146 }; 147 }; 148 }; 149 }; 150 151 config = mkIf cfg.enable { 152 environment.systemPackages = [ healthchecksManageScript ]; 153 154 systemd.targets.healthchecks = { 155 description = "Target for all Healthchecks services"; 156 wantedBy = [ "multi-user.target" ]; 157 after = [ "network.target" "network-online.target" ]; 158 }; 159 160 systemd.services = 161 let 162 commonConfig = { 163 WorkingDirectory = cfg.dataDir; 164 User = cfg.user; 165 Group = cfg.group; 166 EnvironmentFile = [ environmentFile ]; 167 StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks"; 168 StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750"; 169 }; 170 in 171 { 172 healthchecks-migration = { 173 description = "Healthchecks migrations"; 174 wantedBy = [ "healthchecks.target" ]; 175 176 serviceConfig = commonConfig // { 177 Restart = "on-failure"; 178 Type = "oneshot"; 179 ExecStart = '' 180 ${pkg}/opt/healthchecks/manage.py migrate 181 ''; 182 }; 183 }; 184 185 healthchecks = { 186 description = "Healthchecks WSGI Service"; 187 wantedBy = [ "healthchecks.target" ]; 188 after = [ "healthchecks-migration.service" ]; 189 190 preStart = '' 191 ${pkg}/opt/healthchecks/manage.py collectstatic --no-input 192 ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input 193 ${pkg}/opt/healthchecks/manage.py compress 194 ''; 195 196 serviceConfig = commonConfig // { 197 Restart = "always"; 198 ExecStart = '' 199 ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \ 200 --bind ${cfg.listenAddress}:${toString cfg.port} \ 201 --pythonpath ${pkg}/opt/healthchecks 202 ''; 203 }; 204 }; 205 206 healthchecks-sendalerts = { 207 description = "Healthchecks Alert Service"; 208 wantedBy = [ "healthchecks.target" ]; 209 after = [ "healthchecks.service" ]; 210 211 serviceConfig = commonConfig // { 212 Restart = "always"; 213 ExecStart = '' 214 ${pkg}/opt/healthchecks/manage.py sendalerts 215 ''; 216 }; 217 }; 218 219 healthchecks-sendreports = { 220 description = "Healthchecks Reporting Service"; 221 wantedBy = [ "healthchecks.target" ]; 222 after = [ "healthchecks.service" ]; 223 224 serviceConfig = commonConfig // { 225 Restart = "always"; 226 ExecStart = '' 227 ${pkg}/opt/healthchecks/manage.py sendreports --loop 228 ''; 229 }; 230 }; 231 }; 232 233 users.users = optionalAttrs (cfg.user == defaultUser) { 234 ${defaultUser} = 235 { 236 description = "healthchecks service owner"; 237 isSystemUser = true; 238 group = defaultUser; 239 }; 240 }; 241 242 users.groups = optionalAttrs (cfg.user == defaultUser) { 243 ${defaultUser} = 244 { 245 members = [ defaultUser ]; 246 }; 247 }; 248 }; 249}