Unverified Commit f3c2be2a authored by isabel's avatar isabel
Browse files

nixos/wakapi: init module

parent c5ea55f5
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -90,6 +90,8 @@
- [chromadb](https://www.trychroma.com/), an open-source AI application
  database. Batteries included. Available as [services.chromadb](options.html#opt-services.chromadb.enable).

- [Wakapi](https://wakapi.dev/), a time tracking software for programmers. Available as [services.wakapi](#opt-services.wakapi.enable).

## Backward Incompatibilities {#sec-release-24.11-incompatibilities}

- `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage:
+1 −0
Original line number Diff line number Diff line
@@ -1490,6 +1490,7 @@
  ./services/web-apps/trilium.nix
  ./services/web-apps/tt-rss.nix
  ./services/web-apps/vikunja.nix
  ./services/web-apps/wakapi.nix
  ./services/web-apps/weblate.nix
  ./services/web-apps/whitebophir.nix
  ./services/web-apps/wiki-js.nix
+164 −0
Original line number Diff line number Diff line
{
  lib,
  pkgs,
  config,
  ...
}:
let
  cfg = config.services.wakapi;

  settingsFormat = pkgs.formats.yaml { };
  settingsFile = settingsFormat.generate "wakapi-settings" cfg.settings;

  inherit (lib)
    getExe
    mkOption
    mkEnableOption
    mkPackageOption
    types
    mkIf
    optional
    mkMerge
    singleton
    ;
in
{
  options.services.wakapi = {
    enable = mkEnableOption "Wakapi";
    package = mkPackageOption pkgs "wakapi" { };

    settings = mkOption {
      inherit (settingsFormat) type;
      default = { };
      description = ''
        Settings for Wakapi.

        See [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for a list of all possible options.
      '';
    };

    passwordSalt = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = ''
        The password salt to use for Wakapi.
      '';
    };
    passwordSaltFile = mkOption {
      type = types.nullOr types.path;
      default = null;
      description = ''
        The path to a file containing the password salt to use for Wakapi.
      '';
    };

    smtpPassword = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = ''
        The password used for the smtp mailed to used by Wakapi.
      '';
    };
    smtpPasswordFile = mkOption {
      type = types.nullOr types.path;
      default = null;
      description = ''
        The path to a file containing the password for the smtp mailer used by Wakapi.
      '';
    };
  };

  config = mkIf cfg.enable {
    systemd.services.wakapi = {
      description = "Wakapi (self-hosted WakaTime-compatible backend)";
      wants = [
        "network-online.target"
      ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service";
      after = [
        "network-online.target"
      ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service";
      wantedBy = [ "multi-user.target" ];

      script = ''
        exec ${getExe cfg.package} -config ${settingsFile}
      '';

      serviceConfig = {
        Environment = mkMerge [
          (mkIf (cfg.passwordSalt != null) "WAKAPI_PASSWORD_SALT=${cfg.passwordSalt}")
          (mkIf (cfg.smtpPassword != null) "WAKAPI_MAIL_SMTP_PASS=${cfg.smtpPassword}")
        ];
        EnvironmentFile = [
          (optional (cfg.passwordSaltFile != null) cfg.passwordSaltFile)
          (optional (cfg.smtpPasswordFile != null) cfg.smtpPasswordFile)
        ];

        User = config.users.users.wakapi.name;
        Group = config.users.users.wakapi.group;

        DynamicUser = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_UNIX"
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        StateDirectoryMode = "0700";
        Restart = "always";
      };
    };

    services.wakapi.settings = {
      env = lib.mkDefault "production";
    };

    assertions = [
      {
        assertion = cfg.passwordSalt != null || cfg.passwordSaltFile != null;
        message = "Either `services.wakapi.passwordSalt` or `services.wakapi.passwordSaltFile` must be set.";
      }
      {
        assertion = cfg.passwordSalt != null -> cfg.passwordSaltFile != null;
        message = "Both `services.wakapi.passwordSalt` `services.wakapi.passwordSaltFile` should not be set at the same time.";
      }
      {
        assertion = cfg.smtpPassword != null -> cfg.smtpPasswordFile != null;
        message = "Both `services.wakapi.smtpPassword` `services.wakapi.smtpPasswordFile` should not be set at the same time.";
      }
    ];

    users = {
      users.wakapi = {
        group = "wakapi";
        createHome = false;
        isSystemUser = true;
      };
      groups.wakapi = { };
    };

    services.postgresql = mkIf (cfg.settings.db.dialect == "postgres") {
      enable = true;

      ensureDatabases = singleton cfg.settings.db.name;
      ensureUsers = singleton {
        name = cfg.settings.db.user;
        ensureDBOwnership = true;
      };

      authentication = ''
        host ${cfg.settings.db.name} ${cfg.settings.db.user} 127.0.0.1/32 trust
      '';
    };
  };

  meta.maintainers = with lib.maintainers; [ isabelroses ];
}