Skip to content

Commit

Permalink
nixos/open-web-calendar: init module
Browse files Browse the repository at this point in the history
  • Loading branch information
erictapen committed Oct 29, 2024
1 parent faf5b60 commit 264a3be
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,7 @@
./services/web-apps/ocis.nix
./services/web-apps/onlyoffice.nix
./services/web-apps/openvscode-server.nix
./services/web-apps/open-web-calendar.nix
./services/web-apps/mobilizon.nix
./services/web-apps/openwebrx.nix
./services/web-apps/outline.nix
Expand Down
162 changes: 162 additions & 0 deletions nixos/modules/services/web-apps/open-web-calendar.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
{
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 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,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 {};
Expand Down
51 changes: 51 additions & 0 deletions nixos/tests/web-apps/open-web-calendar.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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'")
'';
}
)

0 comments on commit 264a3be

Please sign in to comment.