Documentation
Versioned storage
Versioned storage is Everlock's internal persistence primitive. Every backend that needs to store state — site content, user records, mail, system configuration — uses it. It is a key-value store where every write is a Git commit, which means all state has full history and every change is auditable by default.
What it is
At the lowest level, versioned storage is a Store trait backed by a bare Git repository. A store holds files at relative paths. Every write produces one commit; batch writes group multiple file changes into a single atomic commit. Reads come from HEAD. History is the Git commit log.
Store (trait)
read(path) → bytes or None
write(path, bytes) → CommitId
delete(path) → CommitId
commit(ops) → CommitId ← batch: multiple writes/deletes, one commit
list(prefix) → [paths]
history(path) → [HistoryEntry]
read_at(path, id) → bytes or None at a specific past commit
StoreManager is the factory. Each module calls manager.open("name") to get its own isolated store, which maps to a bare Git repository at data/<name>/.
On disk
Every store is a standard bare Git repository. No custom binary format, no proprietary layout:
data/
├── everlock-system/ ← system configuration store
│ ├── HEAD
│ ├── config
│ ├── objects/
│ │ └── pack/
│ └── refs/
│ └── heads/
│ └── main
├── everlock-users/ ← user and group records
│ └── …
├── my-docs/ ← site content store, created by /site create my-docs
│ └── …
└── my-project/ ← standalone repo, created by /git create my-project
└── …
Internal system stores use the everlock- prefix by convention. That prefix distinguishes them from user-owned repositories in the list of available stores.
Commits
Every write is authored with a fixed system identity:
Author: Everlock System <system@everlock>
The commit message carries the domain context supplied by the caller. This makes the log readable as a domain audit trail:
git log --oneline data/everlock-users/
a3f1b2c grant alice write access to site my-docs
7e4c9d1 create group backend-team
2b8f301 add user alice
f09c411 initial
Because commit messages are caller-supplied, each subsystem can record meaningful domain events rather than generic storage noise.
Batch writes
Multiple file changes can be grouped into one atomic commit using commit(ops, message). Either all changes land, or none do:
store.commit(vec![
StoreOp::Write { path: user_path, content: user_bytes },
StoreOp::Write { path: cred_path, content: cred_bytes },
], "add user alice with initial credential")
This matters for correctness: a user record and its credential must never be half-written. A single commit guarantees they arrive together.
History and point-in-time reads
Every file carries a full history of changes:
let entries = store.history(&path)?;
// → [HistoryEntry { commit_id, message, timestamp }, …]
Any past state can be read by commit ID:
let old_bytes = store.read_at(&path, &commit_id)?;
The site backend uses this to show git_date on pages — it reads the most recent commit in the file's history to get the last-modified timestamp.
Accessing a store through Git
Because every store is a standard bare Git repository, it is directly accessible through backend-git-ssh. You can clone any store you have access to, inspect its history with git log, and see exactly what Everlock has written.
Cloning the user store
The everlock-users store holds all user and group records. To inspect it:
git clone ssh://admin@localhost:2222/everlock-users
cd everlock-users
git log --oneline
a3f1b2c grant alice write access to site my-docs
7e4c9d1 create group backend-team
2b8f301 add user alice
f09c411 initial
The repository contains one TOML file per user:
ls
alice.toml admin.toml
cat alice.toml
id = "user-4a3b2c1d..."
primary_name = "alice"
display_name = "Alice"
created_at = "2026-05-21T10:00:00Z"
groups = []
Cloning the system configuration store
git clone ssh://admin@localhost:2222/everlock-system
cd everlock-system
git log --oneline
c2d1e3f create site my-docs with vhost localhost
b1a2f4e enable backend site-http
9f3c812 initial
Cloning a site content store
Site stores are ordinary repositories. The site backend reads from HEAD; what you see when you clone is exactly what is being served:
git clone ssh://admin@localhost:2222/my-docs
cd my-docs
ls
index.md docs/ layouts/ assets/ site.toml
This is the same repository you push content to. There is no intermediate build artefact.
Access control on internal stores
Internal stores (everlock-*) are accessible to users with the appropriate grant. The admin bootstrap user holds owner on */*/*, which includes all stores. Other users need an explicit grant to access them:
/users grant ops-user */git/everlock-system reader
Use this sparingly — system stores contain credentials and configuration. Read access to everlock-users exposes password hashes.
Why Git
Choosing Git as the storage format rather than a database or custom binary format carries specific trade-offs:
Inspectable without Everlock. Anyone with a clone can read the state of the system using standard tools. There is no proprietary query layer or format to reverse-engineer.
Auditable by default. Every change has a timestamp, a message, and a parent commit. There is no "who changed this?" question without an answer.
Pushable from outside. A site store is updated by git push, not by an API call. Content stays in a format that can be edited, reviewed, and version-controlled outside of Everlock itself.
No compaction needed for typical state sizes. User records, site content, and configuration fit comfortably in Git's object model. The stores Everlock maintains internally are not expected to grow into millions of objects.