backup: 2026-10-14 09:41
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
: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.
|
||||
Reference in New Issue
Block a user