evolution: integrate with Radicale for vcard contacts storage

This commit is contained in:
2024-12-14 04:27:55 +00:00
parent 4788170e8a
commit b5d7f3d861
4 changed files with 164 additions and 4 deletions

View File

@@ -87,6 +87,8 @@
sane.ids.resolvconf.gid = 2009; sane.ids.resolvconf.gid = 2009;
sane.ids.smartd.uid = 2010; sane.ids.smartd.uid = 2010;
sane.ids.smartd.gid = 2010; sane.ids.smartd.gid = 2010;
sane.ids.radicale.uid = 2011;
sane.ids.radicale.gid = 2011;
# found on graphical hosts # found on graphical hosts
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy

View File

@@ -148,6 +148,7 @@
./portfolio-filemanager.nix ./portfolio-filemanager.nix
./playerctl.nix ./playerctl.nix
./qmk-udev-rules.nix ./qmk-udev-rules.nix
./radicale.nix
./rhythmbox.nix ./rhythmbox.nix
./ripgrep.nix ./ripgrep.nix
./rofi ./rofi

View File

@@ -1,7 +1,5 @@
# evolution-data-server (e-d-s) exposes DBus services for managing contacts and calendars. # evolution-data-server (e-d-s) exposes DBus services for managing contacts and calendars.
# #
# TODO: setup plaintext backend (e.g. vcard import/export; or CardDAV with a plaintext backend like radicale)
#
# common users include: # common users include:
# - `folks` (for contacts only, used in turn by `gnome-calls`, `gnome-contacts`, et al.) # - `folks` (for contacts only, used in turn by `gnome-calls`, `gnome-contacts`, et al.)
# - `gnome-calendars`, along with several other calendar or todo/tasking apps # - `gnome-calendars`, along with several other calendar or todo/tasking apps
@@ -69,6 +67,15 @@
# - WEBCAL_DEBUG # - WEBCAL_DEBUG
# - WEBDAV_DEBUG # - WEBDAV_DEBUG
# - WEBDAV_NOTES_DEBUG # - WEBDAV_NOTES_DEBUG
#
#
# should you need to reconfigure the radicale stuff (e.g. add another collection):
# - do so using `evolution`
# - it will ask to auth against radicale: enter <user>, keep password field empty
# - new `.source` files will appear under ~/.cache/evolution:
# - these represent the collections: move them into ~/.config and give them stable names
# - edit them to not have `parent=...` (this will allow them to connect w/o auth)
# - then remove the `evolution` passwords from gnome-keyring to ensure reproducibility
{ config, pkgs, ... }: { config, pkgs, ... }:
let let
cfg = config.sane.programs."evolution-data-server"; cfg = config.sane.programs."evolution-data-server";
@@ -81,7 +88,16 @@ in
withGtk4 = false; withGtk4 = false;
}; };
suggestedPrograms = [
# "gnome-keyring" # to save the empty password for my calendar
# radicale acts as a CardDAV / CalDAV server:
# evolution speaks to it over http port 5232
# and it reads/writes vcard .vcf files to its own storage
"radicale"
];
sandbox.whitelistDbus = [ "user" ]; sandbox.whitelistDbus = [ "user" ];
sandbox.net = "localhost"; #< to reach radicale (TODO: restrict further)
persist.byStore.ephemeral = [ persist.byStore.ephemeral = [
".cache/evolution" ".cache/evolution"
@@ -89,13 +105,93 @@ in
# - birthdays.source # - birthdays.source
# - system-calendar.source # - system-calendar.source
# - system-proxy.source # - system-proxy.source
".config/evolution/sources" # ".config/evolution/sources"
# ".local/share/evolution"
]; ];
persist.byStore.private = [ persist.byStore.private = [
# ".cache/evolution/sources"
# local data stores (e.g. addressbook, calendar) live in ~/.local/share/evolution # local data stores (e.g. addressbook, calendar) live in ~/.local/share/evolution
".local/share/evolution" # ".local/share/evolution"
# ".local/share/evolution/addressbook/system"
# ".local/share/evolution/calendar/system"
]; ];
# radicale source: configured by building `evolution` and adding the account there.
# the password (empty) was presumably saved to gnome-keyring.
# fs.".config/evolution/sources/ec1438cee0ce00521a96cd266b980c20891c41cf.source".symlink.text = ''
# fs.".config/evolution/sources/pkm.source".symlink.text = ''
# [Data Source]
# DisplayName=colin
# Enabled=true
# Parent=
# [Authentication]
# Host=
# Method=plain/password
# Port=0
# ProxyUid=system-proxy
# RememberPassword=true
# User=colin
# CredentialName=
# IsExternal=false
# [Collection]
# BackendName=webdav
# CalendarEnabled=true
# ContactsEnabled=true
# Identity=colin
# MailEnabled=true
# AllowSourcesRename=false
# CalendarUrl=
# ContactsUrl=http://localhost:5232
# '';
# file created by:
# - after configuring `pkm.source` above, evolution should auto-discover collection items
# and make them available as random UUIDs in e.g. `~/.cache/evolution/sources/pkm/<UUID>.source`.
# - copy that file here, and rename it as desired.
fs.".config/evolution/sources/pkm-contacts.source".symlink.text = ''
[Data Source]
DisplayName=git-synchronized PKM
Enabled=true
# Parent=pkm
[Authentication]
Host=localhost
Method=plain/password
Port=5232
ProxyUid=system-proxy
RememberPassword=true
User=colin
CredentialName=
IsExternal=false
[Security]
Method=none
[Resource]
Identity=contacts::http://localhost:5232/colin/pkm/
[WebDAV Backend]
AvoidIfmatch=false
CalendarAutoSchedule=false
Color=
DisplayName=git-synchronized PKM
EmailAddress=
ResourcePath=/colin/pkm/
ResourceQuery=
SslTrust=
Order=4294967295
Timeout=90
[Address Book]
BackendName=carddav
Order=0
'';
gsettings."org/freedesktop/folks" = {
primary-store = "eds:pkm-contacts";
};
# e-d-s provides the following services: # e-d-s provides the following services:
# - evolution-addressbook-factory (org.gnome.evolution.dataserver.AddressBook10) # - evolution-addressbook-factory (org.gnome.evolution.dataserver.AddressBook10)
# - evolution-calendar-factory (org.gnome.evolution.dataserver.Calendar8) # - evolution-calendar-factory (org.gnome.evolution.dataserver.Calendar8)
@@ -105,6 +201,7 @@ in
# evolution-addressbook-factory is required for gnome-contacts to add/view contacts # evolution-addressbook-factory is required for gnome-contacts to add/view contacts
description = "evolution-addressbook-factory provides contacts storage/retrieval to dbus users"; description = "evolution-addressbook-factory provides contacts storage/retrieval to dbus users";
dependencyOf = [ "graphical-session" ]; dependencyOf = [ "graphical-session" ];
depends = [ "radicale" ];
command = "${cfg.package}/libexec/evolution-addressbook-factory --keep-running"; command = "${cfg.package}/libexec/evolution-addressbook-factory --keep-running";
readiness.waitDbus = "org.gnome.evolution.dataserver.AddressBook10"; readiness.waitDbus = "org.gnome.evolution.dataserver.AddressBook10";
}; };
@@ -112,6 +209,7 @@ in
# evolution-addressbook-factory is required by gnome-calendar to add/view events # evolution-addressbook-factory is required by gnome-calendar to add/view events
description = "evolution-calendar-factory provides calendar storage/retrieval to dbus users"; description = "evolution-calendar-factory provides calendar storage/retrieval to dbus users";
dependencyOf = [ "graphical-session" ]; dependencyOf = [ "graphical-session" ];
depends = [ "radicale" ];
command = "${cfg.package}/libexec/evolution-calendar-factory --keep-running"; command = "${cfg.package}/libexec/evolution-calendar-factory --keep-running";
readiness.waitDbus = "org.gnome.evolution.dataserver.Calendar8"; readiness.waitDbus = "org.gnome.evolution.dataserver.Calendar8";
}; };

View File

@@ -0,0 +1,59 @@
# radicale is a CalDAV/CardDAV server.
# it receives http queries from calendar or contacts managers
# and translates that into reads/writes of vcard .vcf files in some persisted folder
#
# admin interface: <http://localhost:5232>
# enter username: "colin", password: (empty)
#
# settings: <https://radicale.org/v3.html#configuration>
#
# TODO: this setup allows access to *anything* on the machine with net access;
# but i don't really want e.g. my web browser to know all my personal contacts:
# maybe run this in a net namespace? `JoinsNamespaceOf=evolution` (or vice versa)?
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.radicale;
in
{
sane.programs.radicale = {
# sane.programs.radicale is a sentinel for the nixpkgs' radicale service,
# which is well-sandboxed, and there's no benefit to having it on PATH
packageUnwrapped = null;
};
services.radicale = lib.mkIf cfg.enabled {
enable = true;
package = pkgs.radicale.overrideAttrs (upstream: {
version = lib.warnIf (lib.versionOlder "3.3.1" upstream.version) "radicale outdated: remove src override" "3.3.1-unstable-2024-12-14";
src = pkgs.fetchFromGitHub {
owner = "Kozea";
repo = "Radicale";
rev = "778f56cc4d7b828af6e2e472f2e7898db72dca22";
hash = "sha256-Oy6LDI+gvAqwR5XRz7JmRWI7KrAUYTOzHfvJsBRyVmU=";
};
});
settings.storage.type = "multifilesystem_nolock";
settings.storage.use_cache_subfolder_for_history = true; #< requires radicale > 3.3.1
settings.storage.use_cache_subfolder_for_item = true;
settings.storage.use_cache_subfolder_for_synctoken = true;
# settings.storage.filesystem_cache_folder = "/var/lib/radicale/cache";
# settings.storage.filesystem_folder = "/path/to/storage"
# settings.auth.type = "none"; # default: none
};
# TODO: service is considered 'up' too early: we should wait, and notify once the http port is bound/listening
systemd.services.radicale = lib.mkIf cfg.enabled {
serviceConfig.User = lib.mkForce "colin";
# serviceConfig.Group = "users";
serviceConfig.ReadWritePaths = [
"/mnt/persist/private/home/colin/knowledge/social/contacts/db"
];
unitConfig.RequiresMountsFor = [
"/mnt/persist/private/home/colin/knowledge/social/contacts/db"
];
};
sane.fs = lib.optionals cfg.enabled {
"/var/lib/radicale/collections/collection-root/colin/pkm".symlink.target = "/mnt/persist/private/home/colin/knowledge/social/contacts/db";
};
}