125 lines
6.8 KiB
Org Mode
125 lines
6.8 KiB
Org Mode
:PROPERTIES:
|
|
:ID: f4196b35-5438-4da3-9d34-511291f482b5
|
|
:END:
|
|
|
|
#+title: azos/nextcloud
|
|
#+filetags: :project: :knowledge: :nextcloud:
|
|
|
|
Backlink: [[id:bf4dde56-0967-4374-94ed-771301e47c66][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 [[id:09ae970c-2995-43d9-a597-47bb904c98de][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 [[id:bf4dde56-0967-4374-94ed-771301e47c66][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 [[id:c52036a9-25ec-4b8b-8b72-90fcd9ea65dd][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.
|