Commit 897954b8 authored by Kerstin Humm's avatar Kerstin Humm Committed by Valentin Gagarin
Browse files

nixos/open-web-calendar: init module

parent ad869c8f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1494,6 +1494,7 @@
  ./services/web-apps/onlyoffice.nix
  ./services/web-apps/openvscode-server.nix
  ./services/web-apps/mediagoblin.nix
  ./services/web-apps/open-web-calendar.nix
  ./services/web-apps/mobilizon.nix
  ./services/web-apps/openwebrx.nix
  ./services/web-apps/outline.nix
+162 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  inherit (lib)
    mkIf
    mkOption
    mkEnableOption
    mkPackageOption
    mkDefault
    types
    concatMapStringsSep
    generators
    ;
  cfg = config.services.open-web-calendar;

  nixosSpec = calendarSettingsFormat.generate "nixos_specification.json" cfg.calendarSettings;
  finalPackage = cfg.package.override {
    # The calendarSettings need to be merged with the default_specification.yml
    # in the source. This way we use upstreams default values but keep everything overridable.
    defaultSpecificationFile = pkgs.runCommand "custom-default_specification.yml" { } ''
      ${pkgs.yq}/bin/yq -s '.[0] * .[1]' ${cfg.package}/${cfg.package.defaultSpecificationPath} ${nixosSpec} > $out
    '';
  };

  inherit (finalPackage) python;
  pythonEnv = python.buildEnv.override {
    extraLibs = [
      (python.pkgs.toPythonModule finalPackage)
      # Allows Gunicorn to set a meaningful process name
      python.pkgs.gunicorn.optional-dependencies.setproctitle
    ];
  };

  settingsFormat = pkgs.formats.keyValue { };
  calendarSettingsFormat = pkgs.formats.json { };
in
{
  options.services.open-web-calendar = {

    enable = mkEnableOption "OpenWebCalendar service";

    package = mkPackageOption pkgs "open-web-calendar" { };

    domain = mkOption {
      type = types.str;
      description = "The domain under which open-web-calendar is made available";
      example = "open-web-calendar.example.org";
    };

    settings = mkOption {
      type = types.submodule {
        freeformType = settingsFormat.type;
        options = {
          ALLOWED_HOSTS = mkOption {
            type = types.str;
            readOnly = true;
            description = ''
              The hosts that the Open Web Calendar permits. This is required to
              mitigate the Host Header Injection vulnerability.

              We always set this to the empty list, as Nginx already checks the Host header.
            '';
            default = "";
          };
        };
      };
      default = { };
      description = ''
        Configuration for the server. These are set as environment variables to the gunicorn/flask service.

        See the documentation options in <https://open-web-calendar.quelltext.eu/host/configure/#configuring-the-server>.
      '';
    };

    calendarSettings = mkOption {
      type = types.submodule {
        freeformType = calendarSettingsFormat.type;
        options = { };
      };
      default = { };
      description = ''
        Configure the default calendar.

        See the documentation options in <https://open-web-calendar.quelltext.eu/host/configure/#configuring-the-default-calendar> and <https://github.com/niccokunzmann/open-web-calendar/blob/master/open_web_calendar/default_specification.yml>.

        Individual calendar instances can be further configured outside this module, by specifying the `specification_url` parameter.
      '';
    };

  };

  config = mkIf cfg.enable {

    assertions = [
      {
        assertion = !cfg.settings ? "PORT";
        message = ''
          services.open-web-calendar.settings.PORT can't be set, as the service uses a unix socket.
        '';
      }
    ];

    systemd.sockets.open-web-calendar = {
      before = [ "nginx.service" ];
      wantedBy = [ "sockets.target" ];
      socketConfig = {
        ListenStream = "/run/open-web-calendar/socket";
        SocketUser = "open-web-calendar";
        SocketGroup = "open-web-calendar";
        SocketMode = "770";
      };
    };

    systemd.services.open-web-calendar = {
      description = "Open Web Calendar";
      after = [ "network.target" ];
      environment.PYTHONPATH = "${pythonEnv}/${python.sitePackages}/";
      serviceConfig = {
        Type = "notify";
        NotifyAccess = "all";
        ExecStart = ''
          ${pythonEnv.pkgs.gunicorn}/bin/gunicorn \
            --name=open-web-calendar \
            --bind='unix:///run/open-web-calendar/socket' \
            open_web_calendar.app:app
        '';
        EnvironmentFile = settingsFormat.generate "open-web-calendar.env" cfg.settings;
        ExecReload = "kill -s HUP $MAINPID";
        KillMode = "mixed";
        PrivateTmp = true;
        RuntimeDirectory = "open-web-calendar";
        User = "open-web-calendar";
        Group = "open-web-calendar";
      };
    };

    users.users.open-web-calendar = {
      isSystemUser = true;
      group = "open-web-calendar";
    };

    services.nginx = {
      enable = true;
      virtualHosts."${cfg.domain}" = {
        forceSSL = mkDefault true;
        enableACME = mkDefault true;
        locations."/".proxyPass = "http://unix:///run/open-web-calendar/socket";
      };
    };

    users.groups.open-web-calendar.members = [ config.services.nginx.user ];

  };

  meta.maintainers = with lib.maintainers; [ erictapen ];

}
+1 −0
Original line number Diff line number Diff line
@@ -749,6 +749,7 @@ in {
  openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
  opentabletdriver = handleTest ./opentabletdriver.nix {};
  opentelemetry-collector = handleTest ./opentelemetry-collector.nix {};
  open-web-calendar = handleTest ./web-apps/open-web-calendar.nix {};
  ocsinventory-agent = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./ocsinventory-agent.nix {};
  owncast = handleTest ./owncast.nix {};
  outline = handleTest ./outline.nix {};
+51 −0
Original line number Diff line number Diff line
import ../make-test-python.nix (
  { pkgs, ... }:

  let
    certs = import ../common/acme/server/snakeoil-certs.nix;

    serverDomain = certs.domain;
  in
  {
    name = "open-web-calendar";
    meta.maintainers = with pkgs.lib.maintainers; [ erictapen ];

    nodes.server =
      { pkgs, lib, ... }:
      {
        services.open-web-calendar = {
          enable = true;
          domain = serverDomain;
          calendarSettings.title = "My custom title";
        };

        services.nginx.virtualHosts."${serverDomain}" = {
          enableACME = lib.mkForce false;
          sslCertificate = certs."${serverDomain}".cert;
          sslCertificateKey = certs."${serverDomain}".key;
        };

        security.pki.certificateFiles = [ certs.ca.cert ];

        networking.hosts."::1" = [ "${serverDomain}" ];
        networking.firewall.allowedTCPPorts = [
          80
          443
        ];
      };

    nodes.client =
      { pkgs, nodes, ... }:
      {
        networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ "${serverDomain}" ];

        security.pki.certificateFiles = [ certs.ca.cert ];
      };

    testScript = ''
      start_all()
      server.wait_for_unit("open-web-calendar.socket")
      server.wait_until_succeeds("curl -f https://${serverDomain}/ | grep 'My custom title'")
    '';
  }
)