13 defaultUser = "healthchecks";
14 cfg = config.services.healthchecks;
15 opt = options.services.healthchecks;
17 boolToPython = b: if b then "True" else "False";
19 PYTHONPATH = pkg.pythonPath;
20 STATIC_ROOT = cfg.dataDir + "/static";
21 } // lib.filterAttrs (_: v: !builtins.isNull v) cfg.settings;
23 environmentFile = pkgs.writeText "healthchecks-environment" (
24 lib.generators.toKeyValue { } environment
27 healthchecksManageScript = pkgs.writeShellScriptBin "healthchecks-manage" ''
29 if [[ "$USER" != "${cfg.user}" ]]; then
30 sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env --preserve-env=PYTHONPATH'
32 export $(cat ${environmentFile} | xargs)
33 ${lib.optionalString (cfg.settingsFile != null) "export $(cat ${cfg.settingsFile} | xargs)"}
34 $sudo ${pkg}/opt/healthchecks/manage.py "$@"
38 options.services.healthchecks = {
39 enable = mkEnableOption "healthchecks" // {
42 It is expected to be run behind a HTTP reverse proxy.
46 package = mkPackageOption pkgs "healthchecks" { };
49 default = defaultUser;
52 User account under which healthchecks runs.
55 If left as the default value this user will automatically be created
56 on system activation, otherwise you are responsible for
57 ensuring the user exists before the healthchecks service starts.
63 default = defaultUser;
66 Group account under which healthchecks runs.
69 If left as the default value this group will automatically be created
70 on system activation, otherwise you are responsible for
71 ensuring the group exists before the healthchecks service starts.
76 listenAddress = mkOption {
78 default = "localhost";
79 description = "Address the server will listen on.";
85 description = "Port the server will listen on.";
90 default = "/var/lib/healthchecks";
92 The directory used to store all data for healthchecks.
95 If left as the default value this directory will automatically be created before
96 the healthchecks server starts, otherwise you are responsible for ensuring the
97 directory exists with appropriate ownership and permissions.
102 settingsFile = lib.mkOption {
103 type = lib.types.nullOr lib.types.path;
105 description = opt.settings.description;
108 settings = lib.mkOption {
110 Environment variables which are read by healthchecks `(local)_settings.py`.
112 Settings which are explicitly covered in options below, are type-checked and/or transformed
113 before added to the environment, everything else is passed as a string.
115 See <https://healthchecks.io/docs/self_hosted_configuration/>
116 for a full documentation of settings.
118 We add additional variables to this list inside the packages `local_settings.py.`
119 - `STATIC_ROOT` to set a state directory for dynamically generated static files.
120 - `SECRET_KEY_FILE` to read `SECRET_KEY` from a file at runtime and keep it out of
122 - `_FILE` variants for several values that hold sensitive information in
123 [Healthchecks configuration](https://healthchecks.io/docs/self_hosted_configuration/) so
124 that they also can be read from a file and kept out of /nix/store. To see which values
125 have support for a `_FILE` variant, run:
126 - `nix-instantiate --eval --expr '(import <nixpkgs> {}).healthchecks.secrets'`
127 - or `nix eval 'nixpkgs#healthchecks.secrets'` if the flake support has been enabled.
129 If the same variable is set in both `settings` and `settingsFile` the value from `settingsFile` has priority.
131 type = types.submodule (settings: {
132 freeformType = types.attrsOf types.str;
134 ALLOWED_HOSTS = lib.mkOption {
135 type = types.listOf types.str;
137 description = "The host/domain names that this site can serve.";
138 apply = lib.concatStringsSep ",";
141 SECRET_KEY_FILE = mkOption {
142 type = types.nullOr types.path;
143 description = "Path to a file containing the secret key.";
150 description = "Enable debug mode.";
151 apply = boolToPython;
154 REGISTRATION_OPEN = mkOption {
158 A boolean that controls whether site visitors can create new accounts.
159 Set it to false if you are setting up a private Healthchecks instance,
160 but it needs to be publicly accessible (so, for example, your cloud
161 services can send pings to it).
162 If you close new user registration, you can still selectively invite
163 users to your team account.
165 apply = boolToPython;
175 description = "Database engine to use.";
180 default = if settings.config.DB == "sqlite" then "${cfg.dataDir}/healthchecks.sqlite" else "hc";
181 defaultText = lib.literalExpression ''
182 if config.${settings.options.DB} == "sqlite"
183 then "''${config.${opt.dataDir}}/healthchecks.sqlite"
186 description = "Database name.";
193 config = mkIf cfg.enable {
194 environment.systemPackages = [ healthchecksManageScript ];
196 systemd.targets.healthchecks = {
197 description = "Target for all Healthchecks services";
198 wantedBy = [ "multi-user.target" ];
199 wants = [ "network-online.target" ];
202 "network-online.target"
209 WorkingDirectory = cfg.dataDir;
214 ] ++ lib.optional (cfg.settingsFile != null) cfg.settingsFile;
215 StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks";
216 StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
220 healthchecks-migration = {
221 description = "Healthchecks migrations";
222 wantedBy = [ "healthchecks.target" ];
224 serviceConfig = commonConfig // {
225 Restart = "on-failure";
228 ${pkg}/opt/healthchecks/manage.py migrate
234 description = "Healthchecks WSGI Service";
235 wantedBy = [ "healthchecks.target" ];
236 after = [ "healthchecks-migration.service" ];
240 ${pkg}/opt/healthchecks/manage.py collectstatic --no-input
241 ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input
243 + lib.optionalString (cfg.settings.DEBUG != "True") "${pkg}/opt/healthchecks/manage.py compress";
245 serviceConfig = commonConfig // {
248 ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \
249 --bind ${cfg.listenAddress}:${toString cfg.port} \
250 --pythonpath ${pkg}/opt/healthchecks
255 healthchecks-sendalerts = {
256 description = "Healthchecks Alert Service";
257 wantedBy = [ "healthchecks.target" ];
258 after = [ "healthchecks.service" ];
260 serviceConfig = commonConfig // {
263 ${pkg}/opt/healthchecks/manage.py sendalerts
268 healthchecks-sendreports = {
269 description = "Healthchecks Reporting Service";
270 wantedBy = [ "healthchecks.target" ];
271 after = [ "healthchecks.service" ];
273 serviceConfig = commonConfig // {
276 ${pkg}/opt/healthchecks/manage.py sendreports --loop
282 users.users = optionalAttrs (cfg.user == defaultUser) {
284 description = "healthchecks service owner";
290 users.groups = optionalAttrs (cfg.user == defaultUser) {
292 members = [ defaultUser ];