The NUC (ssh pink-sudo, host thebeachlab) and Mac use Taskwarrior 3.5.0.
The NUC hosts TaskChampion Sync Server 0.7.1 and the syncall 1.8.8
CalDAV adapter, using the
Taskwarrior 3.5.0
and TaskChampion 0.7.1
releases.
Mac Taskwarrior <-> TaskChampion (via SSH) <-> NUC Taskwarrior <-> syncall <-> Nextcloud Tasks <-> Apple Reminders
TaskChampion syncs encrypted Taskwarrior replicas; it does not replace the CalDAV bridge. Its sync configuration uses a server URL, client ID and encryption secret. Nextcloud and Apple Reminders use CalDAV, so syncall remains necessary for this workflow.
Apple Reminders must use the Nextcloud account's lists. This does not bridge separate iCloud lists. See Apple's CalDAV account guide and Nextcloud's compatible clients. Apple-device UI behavior and notifications have not been tested in this setup.
On the Mac:
task next
task projects
task add "My task" # defaults to NC.Quick_Reminder
task 123 done
task syncOn the NUC use task-nc instead of task; it selects the bridge replica and
runs as taskmaster. Each list has a mapped NC.* project. The mapping records
exact collection URLs, so duplicate display names remain distinct.
The Mac sync LaunchAgent runs every 120 seconds while logged in. The NUC timer runs two minutes after the previous bridge invocation finishes. The bridge pulls from TaskChampion, reconciles CalDAV, then pushes to TaskChampion, all under an exclusive lock. Propagation across the whole chain can take several minutes. Local Taskwarrior commands work offline against the last synced data. For an immediate round trip:
task sync
ssh pink-sudo 'sudo systemctl start nextcloud-taskwarrior.service'
task syncIf both Nextcloud and the NUC replica changed since bridge synchronization, Nextcloud wins. Missing collections and failed reads fail the run rather than being interpreted as empty lists. Collection URLs are fixed; new lists must be deliberately added to the mapping.
The Mac binary is managed by Homebrew (brew install task), available at
/opt/homebrew/bin/task. ~/.local/bin/task points to it too, avoiding ambiguity
with the earlier private 2.6.2 build. The Mac configuration is ~/.taskrc.
LaunchAgents live under ~/Library/LaunchAgents:
com.beachlab.taskwarrior-tunnel: SSH forwarding127.0.0.1:53590to NUC127.0.0.1:53590throughpink-sudo.com.beachlab.taskwarrior-sync: Homebrew Taskwarrior sync every 120 seconds.
Logs live under ~/Library/Logs/Taskwarrior. The versioned
agent installer requires
--replace-existing when upgrading an existing definition and saves the old
plist before replacement. Binary and credentials must already be provisioned.
Ubuntu's repository only provided Taskwarrior 2.6.1, so the NUC's 3.5.0 binary
was built from official tag v3.5.0, commit
3419d5ba1e6a780fbbb0a8d68d8fd31e675c1bf8, with its pinned submodules, into
/opt/taskwarrior-3.5.0. Build commands use two jobs and nice priority to limit
impact. The actual taskchampion-lib submodule requires Rust 1.91.1, despite the
root INSTALL file mentioning 1.88.0. /usr/local/bin/task selects this build. CMake 3.24+ is required; 3.31.10 was used.
Rust's rust-src component must be installed before starting parallel builds.
# As taskmaster, after installing Rust 1.91.1 + rust-src and CMake 3.31.10:
git clone --branch v3.5.0 --depth 1 --recurse-submodules --shallow-submodules \
https://github.com/GothenburgBitFactory/taskwarrior.git ~/taskwarrior-3.5.0
cmake -S ~/taskwarrior-3.5.0 -B ~/taskwarrior-3.5.0/build-stable \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/taskwarrior-3.5.0 \
-DRust_TOOLCHAIN=1.91.1-x86_64-unknown-linux-gnu \
-DCORROSION_TOOLS_RUST_TOOLCHAIN=1.91.1-x86_64-unknown-linux-gnu
CARGO_BUILD_JOBS=2 nice -n 10 cmake --build ~/taskwarrior-3.5.0/build-stable -j2
sudo cmake --install /home/taskmaster/taskwarrior-3.5.0/build-stableTaskChampion runs as container taskchampion-nextcloud using the official image
pinned to 0.7.1 and digest
sha256:7903477ff4857cc7c702377d1c93a3f6703eed2f89e1733400b8599a4d0bb1bc.
The Compose definition
is installed at /home/taskmaster/taskchampion/compose.yaml. It runs directly
as UID/GID 1001 (taskmaster) against a pre-owned directory; the image's default
root chown entrypoint is unnecessary. The listener is published only on
127.0.0.1:53590. SSH supplies transport protection; Taskwarrior also encrypts
payloads. One allowed client ID is configured in the private server.env.
No public firewall port was opened.
ssh pink-sudo 'sudo docker compose -f /home/taskmaster/taskchampion/compose.yaml ps'
ssh pink-sudo 'sudo systemctl status nextcloud-taskwarrior.timer'
ssh pink-sudo 'sudo journalctl -u nextcloud-taskwarrior.service -n 30 --no-pager'
launchctl print gui/$(id -u)/com.beachlab.taskwarrior-tunnel
launchctl print gui/$(id -u)/com.beachlab.taskwarrior-syncThe adapter explicitly scopes bubop's
preferences factory because bubop 0.1.12 ignores XDG_CONFIG_HOME. Existing
~/.config/syncall mappings were moved into the replica backup boundary. A
mapped UUID missing from the local replica stops the run before reconciliation;
an empty or incorrectly selected database must not become mass deletion.
The adapter uses syncall's identity
mapping/change-detection engine, with typed VTODO serialization and URI-based
collection selection. Unmodified syncall 1.8.8 wrote an invalid empty PRIORITY
when editing an unprioritized task, causing the next read to fail. Its taskw-ng
library also retained removed tags. The adapter corrects those tested problems;
do not invoke plain tw_caldav_sync against the managed replica.
Supported fields include title, completion, notes/annotations, priority, tags, and deadlines. Date-only deadlines remain dates. Remote alarms, Apple properties and other unsupported metadata are preserved on edits. Priority maps to H/M/L: CalDAV 1-4 -> H, 5 -> M, 6-9 -> L; Taskwarrior writes those as 1/5/9.
Recurrence, waiting/scheduling semantics, dependencies and subtask hierarchy are
not bridged. New recurring items stop that collection's sync for review. The
one pre-existing completed task in NC.Lista_Con_Fran had its expired recurrence
removed at the user's request on 2026-09-13; no tasks remain excluded. Renaming
mapped projects, moving tasks between projects, and deleting whole lists are
not validated workflows.
| Path | Purpose |
|---|---|
/home/taskmaster/nextcloud-sync/data |
NUC Taskwarrior 3 replica |
/home/taskmaster/nextcloud-sync/config |
Syncall identity mappings and previous-state cache |
/home/taskmaster/nextcloud-sync/settings.json |
Collection mapping and TaskChampion sync switch |
/home/taskmaster/nextcloud-sync/taskrc |
NUC config, including private sync credentials |
/home/taskmaster/nextcloud-sync/app-password |
Nextcloud credential, mode 0600 |
/home/taskmaster/taskchampion/data |
TaskChampion server SQLite data |
/home/taskmaster/taskchampion/client.json |
Private client ID/encryption secret provisioning record |
/home/taskmaster/nextcloud-sync/initial-caldav-backup |
Initial 1,462 VTODOs before installation |
/home/taskmaster/nextcloud-sync/before-recurrence-removal.ics |
Original completed recurring task |
/var/backups/taskwarrior-nextcloud/before-sync-20260913.sql |
Original calendar table dump, root-only |
/home/taskmaster/nextcloud-sync/backups |
Daily pre-sync replica/config snapshots, latest 14 days with runs |
Keep Taskwarrior data and syncall state together. Removing mappings can duplicate or mis-reconcile tasks. The daily snapshot uses SQLite's online backup API for the v3 database. Local snapshots are not off-host backups. Stop sync schedules and wait for active runs before restoring. Taskwarrior 3 SQLite files must not be synchronized through file-copy tools; use TaskChampion. For a manual snapshot, stop writers first or use SQLite's backup facilities. Keep the TaskChampion client ID and encryption secret in a secure backup too.
The original legacy taskd.service and its historical data remain untouched.
The Ubuntu 2.6 package was removed after preserving its binary in
migration-v3/task-2.6.1. The short-lived taskd-nextcloud.service is disabled.
The 2.6 setup was superseded by
TaskChampion; old data/configuration are retained only as migration backups.
The server migration imports v2 data once; the Mac starts as an empty v3 replica
and downloads via TaskChampion to avoid creating competing imported histories.
The existing task UUIDs and syncall mappings are retained.
The one-time migration script
retains migration-v3/ and data-v2-20260913/ on the NUC. The Mac retains
~/.task-migration-3.5.0/ and ~/.task-v2-20260913/. Source fields and UUIDs
were compared before/after import. During cutover, a shell command incorrectly
continued after the migration script failed its state-path check. The brief
run against an empty v3 replica soft-deleted 115 tasks. All 115 were matched
by original Taskwarrior and CalDAV UUIDs, restored using Nextcloud's own
CalDAV restore backend, and restored in Taskwarrior with their original
contents/statuses and mappings. Content hashes alone do not detect soft
deletions: final checks also cover live/deleted state and all 1,462 tasks.
The adapter now explicitly scopes its state, blocks a missing mapped replica,
and has an integration test proving that this case cannot delete remote tasks.
Recovery evidence is retained under migration-v3/recovery-plan.json. Run migration/deployment commands with fail-fast error handling; never activate
a new adapter after a failed migration. Back up and coordinate both replicas
before any future migration.
The following is the original setup, retained for the already-running service. It is not the Nextcloud bridge installation procedure. Taskwarrior 3 uses a different sync implementation and cannot use taskd; see the official upgrade guide.
It is a bit tedious but worth it
sudo apt install g++
sudo apt install libgnutls28-dev
sudo apt install uuid-dev
sudo apt install cmake
sudo apt install gnutls-binsudo adduser taskmaster
usermod -aG sudo taskmaster
su taskmaster
git clone --recurse-submodules=yes https://github.com/GothenburgBitFactory/taskserver.git taskserver.git
cmake -DCMAKE_BUILD_TYPE=release .
make
cd test
make
./run_all
cd ..
sudo make installexport TASKDDATA=~/taskd
mkdir -p $TASKDDATA
cd taskserver.git
cp -R pki ~/taskd
taskd init
nano ~/taskd/pki/varsIn vars we have information about the server
BITS=4096
EXPIRATION_DAYS=3650
ORGANIZATION="The Beach Lab"
CN=beachlab.org
COUNTRY=ES
STATE="Barcelona"
LOCALITY="Sitges"Generate and install the server certificates
./generate
cp client* $TASKDDATA
cp server* $TASKDDATA
cp ca.cert.pem $TASKDDATA
taskd config --force client.cert $TASKDDATA/client.cert.pem
taskd config --force client.key $TASKDDATA/client.key.pem
taskd config --force server.cert $TASKDDATA/server.cert.pem
taskd config --force server.key $TASKDDATA/server.key.pem
taskd config --force server.crl $TASKDDATA/server.crl.pem
taskd config --force ca.cert $TASKDDATA/ca.cert.pemConfigure the server settings
taskd config --force log $PWD/taskd.log
taskd config --force pid.file $PWD/taskd.pid
taskd config --force server localhost:53589
cd /home/taskmaster/taskserver.git/scripts/systemd/
cp /home/taskmaster/taskserver.git/scripts/systemd/taskd.service ~Edit the service daemon nano taskd.service
[Unit]
Description=Secure server providing multi-user, multi-client access to task data
Requires=network.target
After=network.target
Documentation=http://taskwarrior.org/docs/
[Service]
ExecStart=/usr/local/bin/taskd server --data /home/taskmaster/taskd
Restart=on-abort
Type=simple
User=taskmaster
Group=taskmaster
WorkingDirectory=/home/taskmaster/taskd
PrivateTmp=true
[Install]
WantedBy=multi-user.target
cp taskd.service /etc/systemd/system
systemctl daemon-reload
systemctl start taskd.service
systemctl status taskd.service
systemctl enable taskd.service
systemctl enable taskd.serviceAdd org and users
taskd add org TBL --data ~/taskd
taskd add user 'TBL' 'Fran Sanchez' --data ~/taskd
cd taskd/pki
./generate.client fran_sanchezThis will generate a certificate and a private key for the user fran_sanchez
Remember taskmaster user must have keys in .ssh folder and 2FA enabled
scp -P 622 taskmaster@beachlab.org:/home/taskmaster/taskd/pki/fran_sanchez.cert.pem ~/.task
scp -P 622 taskmaster@beachlab.org:/home/taskmaster/taskd/pki/fran_sanchez.key.pem ~/.task
scp -P 622 taskmaster@beachlab.org:/home/taskmaster/taskd/pki/ca.cert.pem ~/.taskAdd the certificates to taskwarrior
task config taskd.certificate -- ~/.task/fran_sanchez.cert.pem
task config taskd.key -- ~/.task/fran_sanchez.key.pem
task config taskd.ca -- ~/.task/ca.cert.pemSet the server parameters
task config taskd.server -- beachlab.org:53589
task config taskd.credentials -- TBL/Fran Sanchez/YOUR-CREDENTIALS-HERECredentials where shown when creating the user.
task sync