diff --git a/README.md b/README.md index c1f7a24..54da4fa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Overview -This utility is a fancy wrapper around `syncevolution` and `cron` and it creates or removes connections to remote carddav/caldav servers. This utility was created specifically to work on Ubuntu Touch but it may work in different environments. +This utility is a fancy wrapper around `syncevolution` and user-based `systemd timer` templates and it creates or removes connections to remote carddav/caldav servers. This utility was created specifically to work on Ubuntu Touch but it may work in different environments. # Get the Code. @@ -14,7 +14,7 @@ Configure server URLs, credentials, and naming preferences. cp config-nextcloud-template.txt config-personal.txt vim config-personal.txt -Executing `setup-dav-sync.sh --contacts config-personal.txt` or `setup-dav-sync.sh --calendar config-personal.txt` will read your configurations, connect to the specified carddav/caldav server, synchronize data and setup a cron job to keep this device in sync with the server. +Executing `setup-dav-sync.sh --contacts config-personal.txt` or `setup-dav-sync.sh --calendar config-personal.txt` will read your configurations, connect to the specified carddav/caldav server, synchronize data and setup a systemd timer to keep this device in sync with the server. Executing `setup-dav-sync.sh --delete-contacts config-personal.txt` or `setup-dav-sync.sh --delete-calendar config-personal.txt` will read your configurations, remove them from `syncevolution` configurations and remove all data from this device only (the data on the server will not be affected). @@ -24,10 +24,17 @@ Simple specify multiple configurations files and a single command will process t setup-dav-sync.sh --contacts --calendar config-personal.txt holidays.txt team-awesome-schedule.txt +# Backwards compatibility + +Users of `v0.1.1` or older of this tool (typically used with Ubuntu-Touch before the 20.04 release) might want to consider using the old script to remove the old configuration before reapplying it with the new script, as there has been some breaking changes. The old script relied on `cron` to do the regular sync-job. In Ubuntu-Touch Focal cron is no longer installed by default. Therefore this script shifts to using systemd timer units. + +This new version of the script does not remove existing cron-jobs that may remain from previous installations. These must be cleared out manually, otherwise synchronization might start in parallel. The effects of this are unknown but potentially not desirable. + # Known Bugs 1. When a contact is delete from Ubuntu Touch using the native Contacts app `syncevolution` will relay this change to the carddav server as a 'modification' of the contact and it will not be deleted from the server and other clients synchronized with the server will still have this _old_ contact. -2. Even after a calendar has been removed it will still show up as an available calendar in the Ubuntu Touch native calendar app. +2. Even after a calendar has been removed it will still show up as an available calendar in the Ubuntu Touch native calendar app. This can sometimes be mitigated by rebooting the device and running the delete command again. +3. There currently is only one synchronization interval possible for all synchronizations. This could be mitigated by systemd unit overlays, but needs to be implemented. # Recommendations for Nextcloud Users. diff --git a/configs-template-nextcloud.txt b/configs-template-nextcloud.txt index 069fefa..615463c 100644 --- a/configs-template-nextcloud.txt +++ b/configs-template-nextcloud.txt @@ -31,7 +31,6 @@ CONTACTS_VISUAL_NAMES[0]="$USERNAME - nextcloud" # can NOT includ ################################################################################################################################### -declare -g CRON_FREQUENCY='0,15,30,45 * * * *' # every 15 minutes -#declare -g CRON_FREQUENCY='0,30 * * * *' # every 30 minutes -#declare -g CRON_FREQUENCY='0 * * * *' # every hour -#declare -g CRON_FREQUENCY='@hourly' # non standard +declare -g SYNC_INTERVAL='15min' # every 15 minutes +#declare -g SYNC_INTERVAL='60min' # every hour +#declare -g SYNC_INTERVAL='2hr 30min' # every 2,5 hours diff --git a/setup-dav-sync.sh b/setup-dav-sync.sh index 8e7315d..705c32a 100755 --- a/setup-dav-sync.sh +++ b/setup-dav-sync.sh @@ -32,67 +32,6 @@ function generate_padding { done } -function cron_handler { - local action="$1" # either 'add' or 'delete' - local server_config_name="$2" - local cron_entry="$3" - - local cron_tab="/var/spool/cron/crontabs/$USER" - local regex_search="^.*$server_config_name.*$" - - sudo mount / -o remount,rw - - case "$action" in - 'add') - if sudo [ -f "$cron_tab" ]; then - local grep_result=$(sudo grep --only-matching "$regex_search" "$cron_tab") - - # if a matching cron entry exists replace it - if [ ! -z "$grep_result" ]; then - echo "cron - updating entry for '$server_config_name'" - sudo sed --in-place --expression="s|$regex_search|$cron_entry|" "$cron_tab" - else - echo "cron - creating new entry for '$server_config_name'" - echo "$cron_entry" | sudo tee --append "$cron_tab" &>/dev/null - fi - else - echo "User's cron tab not found, creating it." - sudo touch "$cron_tab" - sudo chown $USER:clickpkg "$cron_tab" # non portable operation - sudo chmod 600 "$cron_tab" - - echo "$cron_entry" | sudo tee --append "$cron_tab" &>/dev/null - fi - ;; - 'delete') - if sudo [ -f "$cron_tab" ]; then - local grep_result=$(sudo grep --only-matching "$regex_search" "$cron_tab") - - # if a matching cron entry exists delete it - if [ ! -z "$grep_result" ]; then - echo "cron - deleting entry for '$server_config_name'" - sudo sed --in-place --expression="/$regex_search/d" "$cron_tab" - else - echo "cron - no entry found for '$server_config_name'" - fi - else - echo "User's cron tab not found, no action taken on cron tab." - fi - ;; - *) - sudo mount / -o remount,ro - echo 'Internal error!' >&2 - echo "in the '${FUNCNAME[0]}' function" >&2 - exit 1 - ;; - esac - - sudo service cron restart &>/dev/null - - sudo mount / -o remount,ro - -} - function manual_sync { local action="$1" # either 'add' or 'delete' local name="$2" @@ -126,7 +65,6 @@ function manual_sync { echo "$padding" ;; *) - sudo mount / -o remount,ro echo 'Internal error!' >&2 echo "in the '${FUNCNAME[0]}' function" >&2 exit 1 @@ -138,11 +76,11 @@ function setup_sync { local server_config_name="$1" local name="$2" - local sync="export DISPLAY=:0.0 && export DBUS_SESSION_BUS_ADDRESS=\$(ps -u $USER e | grep -Eo 'dbus-daemon.*address=unix:abstract=/tmp/dbus-[A-Za-z0-9]{10}' | tail -c35) && /usr/bin/syncevolution $server_config_name" - local cron_entry="$CRON_FREQUENCY $sync" + local sync="systemctl start utdavsync@${server_config_name}.service" local action='add' - cron_handler "$action" "$server_config_name" "$cron_entry" + # Enable systemd user timer + systemctl --user enable --now "utdavsync@${server_config_name}.timer" if [ ! -d $HOME/bin ]; then mkdir $HOME/bin @@ -151,6 +89,48 @@ function setup_sync { manual_sync "$action" "$name" "$sync" } +function setup_systemd_units { + # Default interval to 60min if not configured + # TODO: allow for individual intervals + local interval="${1:-60min}" + + # On a fresh install, there is no systemd user unit folder + mkdir --parents "${HOME}/.config/systemd/user/" + + # Create systemd user service template + # Indentation and bash here documents are not known to work well together, + # so this will look a little ugly. +cat << EOF > "${HOME}/.config/systemd/user/utdavsync@.service" +[Unit] +Description=CardDAV sync for %i +After=network.target + +[Service] +Type=oneshot +WorkingDirectory=%h +ExecStart=/usr/bin/syncevolution %i + +[Install] +WantedBy=default.target +EOF + + # Create systemd user timer template +cat << EOF > "${HOME}/.config/systemd/user/utdavsync@.timer" +[Unit] +Description=CardDAV sync for %i + +[Timer] +OnBootSec=3min +OnUnitInactiveSec=${interval} + +[Install] +WantedBy=timers.target +EOF + + # Make the new user units known to systemd + systemctl --user daemon-reload +} + function delete { local type_action="$1" local server_config_name="$2" @@ -158,7 +138,7 @@ function delete { local visual_name="$4" local action='delete' - cron_handler "$action" "$server_config_name" + systemctl --user disable --now "utdavsync@${server_config_name}.timer" manual_sync "$action" "$name" case "$type_action" in @@ -207,11 +187,6 @@ function contacts { local url="${CARD_URL%%/}/${CARD_NAMES[$i]}" - - # add cron entry and create manual sync script - setup_sync "$contacts_server_config_names" \ - "$contacts_names" - #Create contact list syncevolution --create-database backend=evolution-contacts \ database="$contacts_visual_names" @@ -249,6 +224,10 @@ function contacts { #Start first sync syncevolution --sync refresh-from-remote \ "$contacts_server_config_names" "$contacts_names" + + # add systemd user timer and create manual sync script + setup_sync "$contacts_server_config_names" \ + "$contacts_names" done } @@ -262,9 +241,6 @@ function calendar { local url="${CAL_URL%%/}/${CAL_NAMES[$i]}" - # add cron entry and create manual sync script - setup_sync "$calendar_server_config_names" "$calendar_names" - #Create Calendar syncevolution --create-database backend=evolution-calendar \ database="$calendar_visual_names" @@ -301,6 +277,9 @@ function calendar { #Start first sync syncevolution --sync refresh-from-remote \ "$calendar_server_config_names" "$calendar_names" + + # add systemd user timer and create manual sync script + setup_sync "$calendar_server_config_names" "$calendar_names" done } @@ -400,6 +379,12 @@ for config_file in "$@"; do continue fi + # systemd units only need to be set up once, but we don't know the sync + # interval before we source a config file. This way the configured interval + # from the last config file wins. + # TODO: allow for individual intervals + setup_systemd_units "${SYNC_INTERVAL}" + if $calendar; then calendar fi