A self-hosted, multi-tenant Zotero storage platform powered by WebDAV and S3-compatible object storage.
Libris provisions this production data path:
Zotero WebDAV sync -> Caddy -> SFTPGo WebDAV -> AWS S3
The Rust provisioner is control-plane only. It reads config/users.csv, creates
or updates SFTPGo users, and assigns each user a private S3 key prefix:
S3_PREFIX_BASE/<username>/
It does not proxy WebDAV traffic and does not upload files to S3 itself.
- Docker with Compose v2
- A reachable AWS S3 bucket
- AWS access credentials allowed to read/write the configured bucket and prefix
- A DNS name for WebDAV, for example
dav.example.com - Caddy, either already running on the host or started by this Compose project
- Rust only if you want to run local development checks outside Docker
- Create the local config files:
cp .env.example .env
cp config/users.csv.example config/users.csv- Edit
.envfor your deployment:
PUBLIC_WEBDAV_HOST=dav.example.com
SFTPGO_ADMIN_USER=admin
SFTPGO_ADMIN_PASSWORD=<long-random-admin-password>
SFTPGO_ADMIN_PORT=5757
SFTPGO_WEBDAV_PORT=5227
S3_BUCKET=<your-aws-bucket>
S3_REGION=<your-aws-region>
S3_ENDPOINT=
S3_ACCESS_KEY=<your-aws-access-key>
S3_SECRET_KEY=<your-aws-secret-key>
S3_PREFIX_BASE=usersFor native AWS S3, keep S3_ENDPOINT empty. MinIO is only a local test double.
- Edit
config/users.csv:
username,password
alice,long-random-webdav-password
bob,another-long-random-webdav-passwordUsernames may contain ASCII letters, digits, ., _, and -. Passwords may
contain punctuation such as #, /, ", [, ], _, and @. The restricted
CSV format does not support commas, newlines, or leading/trailing spaces inside
passwords.
Each user gets a separate SFTPGo WebDAV account and a separate S3 prefix.
This is the default mode when Caddy already runs on the machine.
- Start SFTPGo and run the provisioner:
make upIf Docker build containers cannot resolve index.crates.io but the host can,
use:
make DOCKER_BUILD_NETWORK=host up- Add
deploy/caddy/Caddyfile.external.exampleto your host Caddy config and replace the example hostnames. The default local WebDAV upstream is:
127.0.0.1:5227
-
Reload Caddy.
-
Configure Zotero:
File Syncing: WebDAV
URL: https://dav.example.com
Username: alice
Password: the password from config/users.csv
Use this mode if this project should run Caddy too:
make up-edgeThis exposes ports 80 and 443 from the Caddy container and reverse-proxies
only the dedicated WebDAV host to SFTPGo inside the Compose network.
Run the WebDAV smoke test against the public URL:
SMOKE_WEBDAV_URL=https://dav.example.com \
SMOKE_WEBDAV_USER=alice \
SMOKE_WEBDAV_PASSWORD=<alice-webdav-password> \
make smokeThe smoke test exercises:
MKCOL -> PUT -> PROPFIND -> GET -> DELETE
Then use Zotero's Verify Server button and perform a real attachment sync. Objects should appear under:
users/<username>/zotero/
- Edit
config/users.csv. - Re-run provisioning:
make provisionProvisioning is idempotent. Existing users are updated; missing users are
created. Users removed from config/users.csv are not deleted automatically.
For local testing without touching AWS, run:
make up-e2e
SMOKE_WEBDAV_URL=http://127.0.0.1:5227 \
SMOKE_WEBDAV_USER=alice \
SMOKE_WEBDAV_PASSWORD=change-this-alice-password \
make smokeThis starts MinIO as an S3-compatible test double and creates the configured bucket before provisioning users. Do not put MinIO in the production path.
make downTo remove local build artifacts:
make cleanBefore committing code changes, run:
make validateThat runs:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
The Rust provisioner intentionally uses only serde and serde_json as direct
runtime crates.
- Do not commit
.env,config/users.csv, real AWS keys, WebDAV passwords, or generated tokens. - Only WebDAV is published to the host/public edge. SFTP, FTP, the SFTPGo WebAdmin, and the SFTPGo WebClient are disabled. The SFTPGo REST API remains reachable only on the Compose network for the provisioner.
- Do not share one WebDAV account across multiple Zotero users.
- Do not mount Zotero's local data directory into WebDAV or S3.