Skip to content

Commit 897954b

Browse files
erictapenfricklerhandwerk
authored andcommitted
nixos/open-web-calendar: init module
1 parent ad869c8 commit 897954b

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,7 @@
14941494
./services/web-apps/onlyoffice.nix
14951495
./services/web-apps/openvscode-server.nix
14961496
./services/web-apps/mediagoblin.nix
1497+
./services/web-apps/open-web-calendar.nix
14971498
./services/web-apps/mobilizon.nix
14981499
./services/web-apps/openwebrx.nix
14991500
./services/web-apps/outline.nix
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
{
2+
config,
3+
lib,
4+
pkgs,
5+
...
6+
}:
7+
8+
let
9+
inherit (lib)
10+
mkIf
11+
mkOption
12+
mkEnableOption
13+
mkPackageOption
14+
mkDefault
15+
types
16+
concatMapStringsSep
17+
generators
18+
;
19+
cfg = config.services.open-web-calendar;
20+
21+
nixosSpec = calendarSettingsFormat.generate "nixos_specification.json" cfg.calendarSettings;
22+
finalPackage = cfg.package.override {
23+
# The calendarSettings need to be merged with the default_specification.yml
24+
# in the source. This way we use upstreams default values but keep everything overridable.
25+
defaultSpecificationFile = pkgs.runCommand "custom-default_specification.yml" { } ''
26+
${pkgs.yq}/bin/yq -s '.[0] * .[1]' ${cfg.package}/${cfg.package.defaultSpecificationPath} ${nixosSpec} > $out
27+
'';
28+
};
29+
30+
inherit (finalPackage) python;
31+
pythonEnv = python.buildEnv.override {
32+
extraLibs = [
33+
(python.pkgs.toPythonModule finalPackage)
34+
# Allows Gunicorn to set a meaningful process name
35+
python.pkgs.gunicorn.optional-dependencies.setproctitle
36+
];
37+
};
38+
39+
settingsFormat = pkgs.formats.keyValue { };
40+
calendarSettingsFormat = pkgs.formats.json { };
41+
in
42+
{
43+
options.services.open-web-calendar = {
44+
45+
enable = mkEnableOption "OpenWebCalendar service";
46+
47+
package = mkPackageOption pkgs "open-web-calendar" { };
48+
49+
domain = mkOption {
50+
type = types.str;
51+
description = "The domain under which open-web-calendar is made available";
52+
example = "open-web-calendar.example.org";
53+
};
54+
55+
settings = mkOption {
56+
type = types.submodule {
57+
freeformType = settingsFormat.type;
58+
options = {
59+
ALLOWED_HOSTS = mkOption {
60+
type = types.str;
61+
readOnly = true;
62+
description = ''
63+
The hosts that the Open Web Calendar permits. This is required to
64+
mitigate the Host Header Injection vulnerability.
65+
66+
We always set this to the empty list, as Nginx already checks the Host header.
67+
'';
68+
default = "";
69+
};
70+
};
71+
};
72+
default = { };
73+
description = ''
74+
Configuration for the server. These are set as environment variables to the gunicorn/flask service.
75+
76+
See the documentation options in <https://open-web-calendar.quelltext.eu/host/configure/#configuring-the-server>.
77+
'';
78+
};
79+
80+
calendarSettings = mkOption {
81+
type = types.submodule {
82+
freeformType = calendarSettingsFormat.type;
83+
options = { };
84+
};
85+
default = { };
86+
description = ''
87+
Configure the default calendar.
88+
89+
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>.
90+
91+
Individual calendar instances can be further configured outside this module, by specifying the `specification_url` parameter.
92+
'';
93+
};
94+
95+
};
96+
97+
config = mkIf cfg.enable {
98+
99+
assertions = [
100+
{
101+
assertion = !cfg.settings ? "PORT";
102+
message = ''
103+
services.open-web-calendar.settings.PORT can't be set, as the service uses a unix socket.
104+
'';
105+
}
106+
];
107+
108+
systemd.sockets.open-web-calendar = {
109+
before = [ "nginx.service" ];
110+
wantedBy = [ "sockets.target" ];
111+
socketConfig = {
112+
ListenStream = "/run/open-web-calendar/socket";
113+
SocketUser = "open-web-calendar";
114+
SocketGroup = "open-web-calendar";
115+
SocketMode = "770";
116+
};
117+
};
118+
119+
systemd.services.open-web-calendar = {
120+
description = "Open Web Calendar";
121+
after = [ "network.target" ];
122+
environment.PYTHONPATH = "${pythonEnv}/${python.sitePackages}/";
123+
serviceConfig = {
124+
Type = "notify";
125+
NotifyAccess = "all";
126+
ExecStart = ''
127+
${pythonEnv.pkgs.gunicorn}/bin/gunicorn \
128+
--name=open-web-calendar \
129+
--bind='unix:///run/open-web-calendar/socket' \
130+
open_web_calendar.app:app
131+
'';
132+
EnvironmentFile = settingsFormat.generate "open-web-calendar.env" cfg.settings;
133+
ExecReload = "kill -s HUP $MAINPID";
134+
KillMode = "mixed";
135+
PrivateTmp = true;
136+
RuntimeDirectory = "open-web-calendar";
137+
User = "open-web-calendar";
138+
Group = "open-web-calendar";
139+
};
140+
};
141+
142+
users.users.open-web-calendar = {
143+
isSystemUser = true;
144+
group = "open-web-calendar";
145+
};
146+
147+
services.nginx = {
148+
enable = true;
149+
virtualHosts."${cfg.domain}" = {
150+
forceSSL = mkDefault true;
151+
enableACME = mkDefault true;
152+
locations."/".proxyPass = "http://unix:///run/open-web-calendar/socket";
153+
};
154+
};
155+
156+
users.groups.open-web-calendar.members = [ config.services.nginx.user ];
157+
158+
};
159+
160+
meta.maintainers = with lib.maintainers; [ erictapen ];
161+
162+
}

nixos/tests/all-tests.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ in {
749749
openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
750750
opentabletdriver = handleTest ./opentabletdriver.nix {};
751751
opentelemetry-collector = handleTest ./opentelemetry-collector.nix {};
752+
open-web-calendar = handleTest ./web-apps/open-web-calendar.nix {};
752753
ocsinventory-agent = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./ocsinventory-agent.nix {};
753754
owncast = handleTest ./owncast.nix {};
754755
outline = handleTest ./outline.nix {};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import ../make-test-python.nix (
2+
{ pkgs, ... }:
3+
4+
let
5+
certs = import ../common/acme/server/snakeoil-certs.nix;
6+
7+
serverDomain = certs.domain;
8+
in
9+
{
10+
name = "open-web-calendar";
11+
meta.maintainers = with pkgs.lib.maintainers; [ erictapen ];
12+
13+
nodes.server =
14+
{ pkgs, lib, ... }:
15+
{
16+
services.open-web-calendar = {
17+
enable = true;
18+
domain = serverDomain;
19+
calendarSettings.title = "My custom title";
20+
};
21+
22+
services.nginx.virtualHosts."${serverDomain}" = {
23+
enableACME = lib.mkForce false;
24+
sslCertificate = certs."${serverDomain}".cert;
25+
sslCertificateKey = certs."${serverDomain}".key;
26+
};
27+
28+
security.pki.certificateFiles = [ certs.ca.cert ];
29+
30+
networking.hosts."::1" = [ "${serverDomain}" ];
31+
networking.firewall.allowedTCPPorts = [
32+
80
33+
443
34+
];
35+
};
36+
37+
nodes.client =
38+
{ pkgs, nodes, ... }:
39+
{
40+
networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ "${serverDomain}" ];
41+
42+
security.pki.certificateFiles = [ certs.ca.cert ];
43+
};
44+
45+
testScript = ''
46+
start_all()
47+
server.wait_for_unit("open-web-calendar.socket")
48+
server.wait_until_succeeds("curl -f https://${serverDomain}/ | grep 'My custom title'")
49+
'';
50+
}
51+
)

0 commit comments

Comments
 (0)