Files
roam/azosnextcloud.org
2026-10-14 09:41:44 +03:00

6.8 KiB

azos/nextcloud

Backlink: azos

Architecture

features/nextcloud/default.nix (home-manager module, azos.nextcloud-client / azos.nextcloud-mount) mounts a Nextcloud webdav folder at ~/synced-docs via rclone mount as a systemd user service (nextcloud-mount.service, WantedBy = default.target, not graphical-session.target — the latter never activates under EXWM since nothing explicitly starts it).

Credentials come from pass at service start (not baked into the Nix store): password from pass zakobar.com, Nextcloud user ID (distinct from login name on this server) from pass nextcloud-username/aner. See azos/pass.

Gated on YubiKey smartcard presence via the shared azos-card-present overlay (overlays/default.nix, wraps gpg --card-status) — same silent presence check used by the mbsync mail-sync timer (features/mail/default.nix). No card -> loop quietly until plugged in, no pinentry prompt either way. Restart = always means unplugging/replugging the key remounts naturally.

Added in commit 04427dd "Nextcloud sync, also zapzap" (2026-10-11), same commit that bumped azos-core to add the zapzap WhatsApp client (azos-core/features/station/whatsapp.nix) — unrelated features landed together.

Gotchas

PATH-override crash loop (fixed 2026-10-11)

nextcloud-mount.service crash-looped (exit code 127, every 10s) because Service.Environment = "PATH=${pkgs.fuse3}/bin:${pkgs.fuse}/bin"; in features/nextcloud/default.nix replaces the service's PATH rather than appending. Every command in the mount script used absolute ${pkgs.X}/bin/... paths except the YubiKey-poll loop's bare sleep 10 — which couldn't be found under the restricted PATH, so bash exited 127. Ran fine manually (inherits full interactive PATH), which is why it looked like it should work.

Fix: ${pkgs.coreutils}/bin/sleep 10 instead of bare sleep 10. General lesson logged on the azos root — any systemd unit that restricts PATH this way needs every bare command in its script converted to an absolute nix-store reference, not just the ones the override was added for.

Same crash loop recurred with a different command (2026-10-12)

nextcloud-mount.service was 127-looping again, but this time the culprit was head, not sleep: password=$(pass zakobar.com | head -1) and the matching uid=$(pass nextcloud-username/aner | head -1) line both piped into bare head -1, which isn't on the restricted PATH either. Under set -eu, the pipeline's exit status is head's (127), which aborts the script. Fixed by changing both to ${pkgs.coreutils}/bin/head -1.

Confirms the general lesson above needs to be applied exhaustively: fixing the one bare command that happened to be caught isn't enough — every token in the script that isn't an absolute /nix/store/... path is a latent 127. Next time this crash-loops, visually scan the whole script rather than assuming the previously-fixed command is the only one.

Still 127'd after deploying the head fix — turned out mkdir -p "$mountpoint" (the very first line of the script, before the YubiKey loop) was also bare and had been bare the whole time, just masked because it never got reached/tested in isolation before. Fixed to ${pkgs.coreutils}/bin/mkdir -p. Verified by extracting the deployed writeShellScript output, manually running it under the exact restricted PATH=/<fuse paths> from systemctl --user show nextcloud-mount.service -p Environment, with bash -x to see exactly which line 127'd — this is the reliable way to debug this script going forward rather than guessing from journalctl alone (which only reports the exit code, not which line).

A third, different bug after that: non-setuid fusermount3 (2026-10-12)

After the mkdir fix the script got all the way to rclone mount, which then failed with "fusermount3: mount failed: Operation not permitted" (exit 1, not 127 — a new failure mode). Root cause: NixOS's real, usable fusermount3 is the setuid-root one at /run/wrappers/bin/fusermount3 (installed via security.wrappers); the plain ${pkgs.fuse3}/bin/fusermount3 in the nix store has no setuid bit and can't perform the mount syscall (lacks CAP_SYS_ADMIN). rclone mount looks up fusermount3 via PATH internally rather than an absolute path, and the service's Service.Environment = "PATH…";= override pointed only at the raw pkgs.fuse3=/=pkgs.fuse bin dirs, never /run/wrappers/bin — so it always resolved to the broken non-setuid binary. ExecStop had the identical bug (hardcoded to the non-setuid ${pkgs.fuse3}/bin/fusermount3 -u ...).

Fix: prepend /run/wrappers/bin to the PATH override, and point ExecStop at /run/wrappers/bin/fusermount3 directly. Verified by manually exporting the rclone env vars and PATH and running rclone mount by hand — failed identically without /run/wrappers/bin, succeeded (real files visible) with it.

General lesson for any systemd unit that overrides PATH and touches FUSE: /run/wrappers/bin must be included, not just the raw package's /bin — several NixOS tools (fusermount, sudo, etc.) only work from their setuid wrapper, which lives there, not in the nix store path itself. I initially misdiagnosed this same "Operation not permitted" error as an artifact of my own sandboxed test shell lacking mount capabilities — it was not; always re-test with the real setuid wrapper on PATH before concluding "not reproducible" for FUSE-related permission errors.

Notably, the symptom this time didn't look like a crash-looping systemd service at first glance — it looked like the Nextcloud desktop GUI client (the separate services.nextcloud-client, webflow login) showing only the Activity/notifications view with no file browser. That GUI client authenticates fine on its own (confirmed via ~/.config/Nextcloud/logs: AccountState CredentialsNotReady -> Connected) — it's unrelated to file access. Actual file access goes through the rclone webdav mount at ~/synced-docs, which was empty because this crash loop kept it unmounted. When the GUI client looks "connected but empty", check systemctl --user status nextcloud-mount.service rather than assuming an auth/account problem.

Related: zapzap notification delivery (confirmed working, no bug)

Same session also investigated zapzap (WhatsApp client, added alongside this feature) not showing desktop notifications. Confirmed the whole pipeline is correctly wired — see azos/exwm for the EDNC/D-Bus notification architecture note. No bug found; likely explanation if it recurs is WhatsApp Web's standard behavior of suppressing notifications for a chat that's already focused/open in the window.