diff --git a/hosts/dav/default.nix b/hosts/dav/default.nix new file mode 100644 index 0000000..d76a35c --- /dev/null +++ b/hosts/dav/default.nix @@ -0,0 +1,37 @@ +{ ... }: + +{ + imports = [ + ../common.nix + ../../services/openssh.nix + ../../services/prometheus-node.nix + ./radicale.nix + ]; + + networking = { + hostName = "dav"; + firewall = { + allowedTCPPorts = [ + 80 # HTTP/1 + 443 # HTTP/2 + ]; + allowedUDPPorts = [ + 443 # HTTP/3 + ]; + }; + }; + + services = { + openssh.banner = '' + __ + /\ \ + \_\ \ __ __ __ + /'_` \ /'__`\ /\ \/\ \ + /\ \L\ \/\ \L\.\_\ \ \_/ | + \ \___,_\ \__/.\_\\ \___/ + \/__,_ /\/__/\/_/ \/__/ + ''; + }; + + system.stateVersion = "26.05"; +} diff --git a/hosts/dav/radicale.nix b/hosts/dav/radicale.nix new file mode 100644 index 0000000..c5ec70b --- /dev/null +++ b/hosts/dav/radicale.nix @@ -0,0 +1,105 @@ +{ config, pkgs, ... }: + +let + calendarAggregate = pkgs.writers.writePython3 "calendar-aggregate.py" { libraries = [ pkgs.python3Packages.icalendar ]; } '' + from pathlib import Path + from icalendar import Calendar + + combined = Calendar() + + for path in Path("/var/lib/radicale/collections").rglob("*.ics"): + with open(path, "rb") as f: + cal = Calendar.from_ical(f.read()) + + for component in cal.walk("VEVENT"): + combined.add_component(component) + + with open("/srv/www/calendar/all.ics", "wb") as f: + f.write(combined.to_ical()) + ''; +in +{ + imports = [ + ../../services/nginx.nix + ../../services/prometheus-nginx.nix + ]; + + systemd = { + services.calendar-aggregate = { + script = "${calendarAggregate}"; + serviceConfig.Type = "oneshot"; + }; + timers.calendar-aggregate = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5m"; + OnUnitActiveSec = "5m"; + }; + }; + }; + + services = { + radicale = { + enable = true; + rights = { + readonly = { + user = ".*"; + collection = ".*"; + permissions = "r"; + }; + cccb = { + user = "cccb"; + collection = "cccb"; + permissions = "rw"; + }; + openwrt = { + user = "openwrt"; + collection = "openwrt"; + permissions = "rw"; + }; + }; + settings = { + server = { + hosts = [ "[::1]:5232" ]; + validate_user_value = "strict"; + validate_path_value = "strict"; + }; + auth = { + type = "htpasswd"; + htpasswd_filename = config.age.secrets.radicale_htpasswd.path; + htpasswd_encryption = "bcrypt"; + }; + storage.filesystem_folder = "/var/lib/radicale/collections"; + headers."Access-Control-Allow-Origin" = "*"; + }; + }; + + nginx.virtualHosts."dav.${config.networking.domain}" = { + default = true; + quic = true; + kTLS = true; + forceSSL = true; + enableACME = true; + locations = { + "/" = { + proxyPass = "http://[::1]:5232"; + recommendedProxySettings = true; + extraConfig = '' + proxy_pass_header Authorization; + ''; + }; + "/all.ics".root = "/srv/www/calendar"; + "/status" = { + proxyPass = "http://${cfg.host}:${toString cfg.port}"; + recommendedProxySettings = true; + extraConfig = '' + allow 195.160.173.14; + allow 2001:678:760:cccb::14; + deny all; + ''; + }; + }; + }; + }; +} + diff --git a/secrets/radicale_htpasswd.age b/secrets/radicale_htpasswd.age new file mode 100644 index 0000000..f967a4f Binary files /dev/null and b/secrets/radicale_htpasswd.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index a237a73..655aa73 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -22,6 +22,7 @@ let _sql = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPcSXjDSyVVVdJbpheOhT0fIuOGFk+jsHhjrAVnBNLQV root@sql"; _www = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4TJCMuJZn03soKuxxv6ywFKiXfhLf9Ab03fbMqNaBJ root@www"; _monitoring = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINmRWdAUur0lb08NiB6ZWLrGmCeELRV30ElxRLfVJGPB root@monitoring"; + _dav = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICArHbX8OlNOv8HCWyyFvyi60d6MRFYe+apK0iGJ7yIM root@dav"; in { "matrix_admin_password.age".publicKeys = xengi; @@ -59,4 +60,5 @@ in ++ [ _sql ]; "www-staging-htpasswd.age".publicKeys = xengi ++ [ _www ]; + "radicale_htpasswd.age".publicKeys = xengi ++ [ _dav ]; }