arizuko › howto › WebDAV mount
mount the workspace over WebDAV
Every arizuko group folder is exposed as WebDAV at https://<host>/dav/<group>/. Any WebDAV client (Finder, Windows Explorer, rclone, davfs2, curl) sees the same files the agent reads and writes.
quick start
One-time setup of an rclone remote against your instance, plus a mount:
# 1. point rclone at the WebDAV endpoint (no user/pass yet)
rclone config create arizuko webdav \
url=https://fab.krons.cx/dav/ \
vendor=other
# 2. issue a Bearer token from the dashboard:
# open https://fab.krons.cx/dash/profile in a browser and copy it.
# 3. tell rclone to use the token (it stores it in ~/.config/rclone/rclone.conf)
rclone config update arizuko bearer_token=<paste-token-here>
# 4. mount a group as a local folder
mkdir -p ~/arizuko-atlas
rclone mount arizuko:atlas ~/arizuko-atlas --vfs-cache-mode writes
You should now see the group’s files under ~/arizuko-atlas/ — CLAUDE.md, diary/, facts/, media/, and so on. Edit them with your editor, drop files in, and the agent picks them up on its next turn.
/auth/login, then open https://<host>/dav/<group>/ in your browser. dufs (the underlying server) ships a directory UI, so you get a clickable file tree for free.path structure
The URL /dav/<group>/<rest> maps one-to-one to /<group>/<rest> under the data directory on the host. davd serves <DATA_DIR>/groups/ read-write over WebDAV; proxyd strips the leading /dav before forwarding and applies the davAllow write-block guard on top.
| URL | on-disk path (host) |
|---|---|
/dav/ | redirect to /dav/<first-group>/ |
/dav/atlas/ | /srv/data/arizuko_<inst>/groups/atlas/ |
/dav/atlas/strengths/ | /srv/data/arizuko_<inst>/groups/atlas/strengths/ |
/dav/atlas/CLAUDE.md | /srv/data/arizuko_<inst>/groups/atlas/CLAUDE.md |
/dav/atlas/users/alice.md | /srv/data/arizuko_<inst>/groups/atlas/users/alice.md |
<group> is the group’s folder name (the on-disk slug), not the human-friendly group name. If you don’t know it, the operator dashboard at /dash/groups lists them. The default group on a fresh instance is called main.
authentication
The same auth layer that gates /dash/ gates /dav/. There is no separate WebDAV password file and no Basic Auth.
| client style | credential | how |
|---|---|---|
| browser | JWT cookie | sign in at /auth/login with GitHub / Google / Discord / Telegram; the refresh_token cookie carries through to /dav/ |
| rclone, davfs2, curl, Finder, Windows Explorer | Bearer token | issue at /dash/profile, send as Authorization: Bearer <token> |
An unauthenticated request to /dav/... returns 303 See Other with Location: /auth/login — that’s by design. The browser follows it; curl does not unless you pass -L. Verify directly:
curl -s -o /dev/null -w '%{http_code}\n' https://fab.krons.cx/dav/
# 303
curl -s -o /dev/null -w '%{http_code}\n' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/
# 200
/dash/profile if you suspect a leak.per-group access
Authentication tells proxyd who you are; grants tell it which group folders you may touch. Grants are stored as acl rows (with role:operator membership in acl_membership for the operator) and resolved at request time:
- Operator — any user in
acl_membership(sub, role:operator)sees every group on the instance./dav/redirects them to the first non-operator group in sorted order (deterministic across requests); they can reach any other group by typing its path directly. - Regular user — sees only the groups for which a matching
aclrow authorizes access. Requesting a group you have no grant on returns403 Forbidden(and proxyd logsdav forbiddenwith your sub and the path).
Grant scope decides what your token can read; auth alone is not enough. A user signed in to /dav/ with no grants gets 403 on every group path.
write-block guard
WebDAV is writable end-to-end — davd mounts <DATA_DIR>/groups/ read-write, and proxyd’s davAllow middleware is the single point of write enforcement. The full ruleset, lifted from proxyd/main.go:
- Reads always pass.
GET,HEAD,OPTIONS,PROPFINDare never blocked by the guard (grants and auth still apply). - Logs are read-only. Any write method on
<group>/logs/...(PUT,POST,MKCOL,DELETE,MOVE,COPY,PROPPATCH) returns403. - Sensitive paths are write-blocked anywhere in the tree. Any path segment equal to
.envor.git, or ending in.pem, rejects writes. Reads still work so the agent can self-inspect. - Everything else is writable. A
PUTto/dav/atlas/notes/todo.mdlands on disk; the agent reads it on its next turn.
The spec at specs/5/M-webdav.md is the source of truth.
common workflows
operator edits a channel’s CLAUDE.md from a laptop
rclone mount arizuko:atlas ~/atlas --vfs-cache-mode writes
$EDITOR ~/atlas/CLAUDE.md
# save, unmount. The agent picks up the new persona on its next turn.
user drops a file the agent should read
# put a CSV in the channel's media folder
curl -u :$TOKEN \
-T ./quarterly.csv \
https://fab.krons.cx/dav/atlas/media/quarterly.csv
# then in chat, ask the agent: "read media/quarterly.csv and summarise"
Or just drag-drop into a Finder window mounted at /dav/atlas/. -u :$TOKEN sends the token as the password with an empty username — curl maps that to a Basic header which proxyd ignores; the real auth still flows through the Bearer header below if you set it explicitly.
curl -H "Authorization: Bearer $TOKEN" \
-T ./quarterly.csv \
https://fab.krons.cx/dav/atlas/media/quarterly.csv
agent writes a file; user pulls it down
The agent runs inside a container with its group folder mounted read-write; it can drop generated artifacts (PDFs, charts, transcripts) anywhere except logs/. The user then pulls them down:
curl -H "Authorization: Bearer $TOKEN" \
-o report.pdf \
https://fab.krons.cx/dav/atlas/out/report.pdf
multi-user: peek at a per-user memory file
Per-user memory lives at <group>/users/<user>.md. With operator grants you can list them all:
curl -X PROPFIND \
-H 'Depth: 1' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/users/
per-client setup notes
rclone (recommended for scripts and headless boxes)
One-liner config (interactive prompts skipped):
rclone config create arizuko webdav \
url=https://fab.krons.cx/dav/ \
vendor=other \
bearer_token=$TOKEN
Mount with the VFS cache so writes survive editor save-rename dances:
rclone mount arizuko:atlas ~/atlas \
--vfs-cache-mode writes \
--dir-cache-time 30s
macOS Finder
- In Finder, Go → Connect to Server (Cmd-K).
- Enter
https://fab.krons.cx/dav/atlas/(include the trailing slash and the group name). - When prompted, choose Registered User; leave the name blank and paste your Bearer token in the password field. Some macOS versions reject empty usernames — in that case sign in to
/auth/loginin Safari first; the JWT cookie is shared.
Linux davfs2
Install davfs2, then add to /etc/davfs2/secrets (root-owned, mode 0600):
https://fab.krons.cx/dav/atlas/ "" <your-bearer-token>
Mount:
sudo mkdir -p /mnt/atlas
sudo mount -t davfs https://fab.krons.cx/dav/atlas/ /mnt/atlas
davfs2 sends credentials over HTTP Basic; proxyd only accepts Authorization: Bearer <JWT> or the refresh_token cookie. davfs2 will not authenticate. Use rclone on Linux.
Windows Explorer
- Open This PC → Map network drive.
- Folder:
https://fab.krons.cx/dav/atlas/ - Tick Connect using different credentials.
- Username blank, password = your Bearer token.
Windows’ built-in client is conservative about HTTPS WebDAV — if it refuses to connect, install rclone and use that instead.
curl
# read a file
curl -H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/CLAUDE.md
# list a directory (Depth: 1 = immediate children only)
curl -X PROPFIND -H 'Depth: 1' \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/
# upload a file
curl -X PUT --data-binary @./notes.md \
-H "Authorization: Bearer $TOKEN" \
https://fab.krons.cx/dav/atlas/notes.md
troubleshooting
- 303 redirect to
/auth/login - Not signed in. Browser: follow the redirect and log in.
curl/rclone: passAuthorization: Bearer <token>. Token comes from/dash/profile. - 403 Forbidden on a path you can read elsewhere
- Either (a) the write-block guard fired — you tried to
PUTunderlogs/or against a sensitive segment (.env,*.pem,.git) — or (b) you don’t have a grant on that group.journalctl -u arizuko_<inst> | grep "dav (forbidden|blocked)"tells you which. - 404 Not Found at
/dav/<group>/ - Either the group folder doesn’t exist, or proxyd has no
davProxyconfigured (response body:WebDAV not configured). Check thatWEBDAV_ENABLED=truein the instance.envand that compose has been regenerated (arizuko run <inst>). - 423 Locked
- Another WebDAV client (Finder is the usual culprit) holds a lock. Unmount that client, or wait for the lock TTL.
dufsimplements per-file locks; they expire on their own but can persist for minutes after a hard disconnect. - “WebDAV not configured” from proxyd
DAV_ADDRis unset on the proxyd container.compose/compose.gosets it tohttp://davd:8080whenWEBDAV_ENABLED=true; if you toggled it off, davd isn’t in the compose graph. SetWEBDAV_ENABLED=truein.envand re-runarizuko run.- Edits don’t reach the agent
- The write landed (no
403in proxyd logs) but the agent hasn’t seen it. Either it has nothing pending to react to — new files don’t trigger a turn on their own; ping the agent in chat — or you wrote to a path the agent doesn’t consult (e.g. an ad-hoc folder outsidemedia/,facts/,users/). Reference itsCLAUDE.mdfor what it reads.
security model
TLS terminates at the public edge (nginx in front of proxyd). From there inward — proxyd to davd, davd to disk — traffic is plain HTTP on the Docker bridge network; the network namespace is the trust boundary. Identity is signed at proxyd with HMAC over X-User-Sub/X-User-Name/X-User-Groups headers, but davd doesn’t verify those today — it trusts that anything reaching it has already cleared requireAuth + davAllow + auth.Authorize upstream. Full threat model in SECURITY.md.
going deeper
- specs/5/M-webdav.md — routing, auth, write-block rules, config flags.
- davd/README.md — the daemon (a thin wrap of
sigoden/dufs). - concepts / webdav — one-page overview of the same surface.
- concepts / auth — where the Bearer token comes from.
- concepts / grants — per-group scoping.