{ pkgs, config, lib, ... }: let domain = "radicale.leaf.ninja"; in { users.users.radicale-sync = { isSystemUser = true; group = "radicale-sync"; }; users.groups.radicale-sync = { }; age.secrets = { radicale-htpasswd = { file = ./secrets/radicale-htpasswd; mode = "400"; owner = "radicale"; }; radicale-sync-secrets = { file = ./secrets/radicale-sync-secrets.fish; mode = "400"; owner = "radicale-sync"; }; }; services.radicale = { enable = true; settings = { server.hosts = [ "localhost:5232" ]; auth = { type = "htpasswd"; htpasswd_filename = config.age.secrets.radicale-htpasswd.path; htpasswd_encryption = "plain"; }; }; rights = { root = { user = ".+"; collection = ""; permissions = "R"; }; principal = { user = ".+"; collection = "{user}"; permissions = "RW"; }; calendars = { user = ".+"; collection = "{user}/[^/]+"; permissions = "rw"; }; remote = { user = ".+"; collection = "remote/.+"; permissions = "r"; }; }; }; services.caddy.virtualHosts.${domain}.extraConfig = '' reverse_proxy localhost:5232 ''; systemd.timers.radicale-sync = { wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = "5min"; OnCalendar = "*-*-* *:0/4:00"; }; }; systemd.services.radicale-sync = let radicaleUrl = "http://localhost:5232"; remoteCollections = [{ collection = "devhack"; url = "https://devhack.net/calendar.ics"; }]; remoteCollectionsFile = pkgs.writers.writeText "remote-collections" (lib.concatMapStringsSep "\n" ({ collection, url }: "${collection} ${url}") remoteCollections); syncScript = pkgs.writers.writeFish "sync.fish" '' alias curl ${lib.getExe pkgs.curl} source ${config.age.secrets.radicale-sync-secrets.path} while read -l name url set tempfile (mktemp) curl -sf $url -o $tempfile curl -sf -u "remote:$password" \ -X PUT "${radicaleUrl}/remote/$name" \ -H 'Content-Type: text/calendar; charset=utf-8' \ --data-binary @$tempfile echo "Uploaded $name" end < ${remoteCollectionsFile} ''; in { serviceConfig = { Type = "oneshot"; User = "radicale-sync"; Group = "radicale-sync"; ExecStart = syncScript; }; }; }