Kepr — the library for your Koh repositories.

Complete reference for every operator command, concept, and configuration option.

v1.19.0 Zig 0.16.0 SQLite 3.49.1

01 What is Kepr

Kepr is a rolling snapshot library for Koh repositories.

Offer your faces. Let others steal what they need.

Private instance — a rolling snapshot buffer for your own projects. Runs on a machine you own. Keeps the last N saves of every project you offer to it. If something goes wrong, you reach back in. koh offer pushes there automatically.

Public instance — a read-only portal for sharing open source work. You offer explicitly when something is worth sharing. Anyone can browse, steal, and download bundles.

The two instances are completely independent deployments — separate machines, separate databases, separate object stores. They share only the Kepr binary.

02 Quick start

$ curl -fsSL kepr.uk/install.sh | sh

Downloads the Kepr binary, installs to ~/.local/bin, prints next steps.

First run:

$ kepr init

Guided setup. Prompts for domain, data directory, HTTP and SSH ports, and your first operator SSH key. Writes keeper.toml. Generates a recovery phrase — displayed once, store it safely. Generates supervisor integration files.

$ kepr start

Starts the Kepr daemon. Visit your configured domain to confirm.

Connect Koh to your instance:

$ koh steal kepr.local/myproject

Initializes a local Koh repository and sets kepr.local as the default remote. Subsequent koh offer commands push there automatically.

Offer your first save:

$ koh offer

Save in Koh, offer to Kepr. Your snapshot is stored. If you lose your machine, steal it back from anywhere on your network.

03 Core concepts

Faces

A face is a Koh project tracked by Kepr. When you run koh steal kepr.local/peach, peach becomes a face on that Kepr instance. Each face has its own save history, rolling window, and web page.

Offers

An offer is a snapshot pushed from Koh to Kepr. koh offer performs a two-phase protocol — first negotiating which objects Kepr is missing, then sending only those objects. Kepr never receives data it already has.

Steals

A steal is pulling a snapshot from Kepr to a local Koh repository. The result is a fully working Koh repository, objects and all.

The rolling window

Each face maintains a rolling window of max_snapshots saves per lane. When the window fills, the oldest save is pruned. Objects shared between saves are deleted only when the last save referencing them is pruned. max_snapshots = 0 keeps everything.

Objects

Every file is stored as a zlib-compressed blob keyed by its Blake3 hash. Unchanged files between saves are stored once — deduplication is automatic and exact. Files under 512 bytes are inlined in the manifest and never stored as separate objects.

Manifests

Every save is a JSON file in data_dir/manifests/{repo}/{id}.json. The SQLite database is a cache — if lost, kepr recover rebuilds it entirely from manifests. No save is ever lost unless its manifest file is also gone.

Lanes

Koh tracks two lanes: main and dev. Kepr maintains the rolling window separately per lane. A face with max_snapshots = 10 keeps up to 10 main saves and 10 dev saves independently.

04 kepr init

kepr init [--init <system>]

Guided setup. Prompts for domain, data directory, ports, and first operator SSH key. Writes keeper.toml. Generates a recovery phrase shown once. Creates data directory structure. Generates supervisor integration files.

--init <system>: systemd, runit, s6, openrc, launchd, generic, all. Omit to auto-detect.

No kepr init on NixOS — the module handles initialization.

05 kepr start · kepr stop · kepr restart

kepr start
kepr stop [--timeout <seconds>]
kepr restart

kepr start starts the daemon. Delegates to systemd when available, otherwise starts in the foreground.

kepr stop drains in-flight requests before stopping. Default timeout 30 seconds.

kepr restart drains, stops, starts.

06 kepr status · kepr ping · kepr doctor

kepr status
kepr ping
kepr doctor

kepr status

kepr · kepr.local · v3.1.0

  domain     kepr.local
  uptime     4 days 3 hours
  repos      14
  window     10 saves per repo per lane
  storage    2.1 GB / 50 GB (4%)
  memory     180 MB / 8 GB (2%)

  kepr       ● running

  last offer 2 hours ago
  last steal 3 days ago

kepr ping

Quick liveness check. Exits 0 if healthy, 4 if unreachable.

$ kepr ping
kepr · kepr.local · ok (12ms)

kepr doctor

Full self-diagnostic. SQLite integrity, disk, memory, ports, keys, config, DNS, TLS. Exits 0 all pass, 1 warnings, 2 errors. Safe for monitoring scripts.

07 kepr config

kepr config show
kepr config get <key>
kepr config set <key> <value>
kepr config validate

kepr config set validates before writing and prints whether the change takes effect immediately or requires a restart:

$ kepr config set retention.max_snapshots 20
  retention.max_snapshots = 20
  takes effect immediately — no restart required

$ kepr config set server.ssh_port 2223
  server.ssh_port = 2223
  restart required: kepr restart

Full configuration reference in §18.

08 kepr keys

kepr keys
kepr keys add
kepr keys remove <fingerprint>

Operator SSH key management. kepr keys remove refuses to remove the last key — lockout prevention.

Operator keys authenticate koh offer via Ed25519 signed HTTP headers. Key resolution order:

  • .koh/keeper-key — per-remote key path
  • $KEPR_SSH_KEY environment variable
  • ~/.ssh/id_ed25519
  • ~/.ssh/id_rsa

To use a specific key for a remote:

$ koh steal kepr.example.com/peach --key ~/.ssh/kepr_key

Writes the key path to .koh/keeper-key for subsequent offers.

09 kepr repos

kepr repos
kepr repo show <name>
kepr repo rename <name> <new>
kepr repo remove <name>

kepr repo show full metadata:

$ kepr repo show peach

peach
  main   10 saves   window full     last offered 2 hours ago
  dev     3 saves   3 remaining     last offered 4 days ago
  storage  4.2 MB · 847 objects
  last stolen  yesterday
  created      2026-01-15

kepr repo rename is atomic — all SQLite records and the manifests directory in a single transaction.

kepr repo remove is irreversible. Prompts for confirmation by typing the repo name.

10 kepr prune

kepr prune <name>
kepr prune --all
kepr prune --dry-run

Prune a repo to its current window immediately. With retention.prune_on_offer = true (default), this runs automatically after every successful offer — manual pruning is rarely needed.

Objects shared between saves are deleted only when the last save referencing them is pruned.

11 kepr offers

Contribution management. Only relevant when contributions.accept_offers = true.

kepr offers pending
kepr offer show <id>
kepr offer accept <id>
kepr offer decline <id> [--reason "<text>"]
kepr offer decline --all [--yes]
$ kepr offers pending

3 pending contributions

  a3f9c2   peach   SHA256:AbCd...   2 hours ago    REVIEW CAREFULLY
  b4e891   nina    SHA256:EfGh...   1 day ago      ACCEPT
  c5f902   toph    SHA256:IjKl...   3 days ago     STRONG DECLINE

kepr offer accept moves objects from quarantine to the main store, writes the manifest, runs pruning.

kepr offer decline discards — quarantine objects deleted synchronously. No trace remains.

Full contribution system in §21.

12 kepr backup · kepr restore

kepr backup [--output <path>]
kepr restore <backup.tar.gz>

kepr backup creates a tar.gz of SQLite and all objects. Prints output path and SHA-256 on completion.

$ kepr backup
  wrote kepr-backup-2026-05-09.tar.gz (4.2 GB)
  sha256: a3f9c2d1...

kepr restore validates integrity before writing. Refuses to run while serving unless --force. Prompts for confirmation by typing the domain name. Creates a pre-restore backup automatically — if restore fails, auto-restores from it.

13 kepr storage

kepr storage status
kepr storage gc
kepr storage verify [--face <name>]
kepr storage trend

kepr storage gc removes orphaned objects — safe to run anytime.

kepr storage verify decompresses and rehashes every object. --face <name> limits to one repo for speed.

kepr storage trend shows 30-day usage chart and disk-full projection.

14 kepr image

kepr image [--output <path>]
kepr image --verify <file>
kepr restore --from-image <dir>

A Kepr instance image is a complete portable snapshot — data, configuration, and the exact binary that produced it.

$ kepr image
  wrote kepr-instance-myserver-2026-05-09.tar.gz (4.1 GB)
  checksum: blake3:a3f9c2...
  14 repos · 247 saves · 12,847 objects

Restore on a new machine:

$ tar -xzf kepr-instance-myserver-2026-05-09.tar.gz
$ cd kepr-instance-myserver-2026-05-09/
$ ./kepr restore --from-image . --init systemd

The binary that produced the image is included. The README.txt has plain-text restoration instructions — readable without any tools, on any machine, in any future.

Schedule automatically:

[images]
schedule = "monthly"
keep     = 3

Full detail in §23.

15 kepr recover

kepr recover [--verify] [--dry-run]

Rebuilds SQLite entirely from the manifest JSON files in data_dir/manifests/. Every repo, save, tag, and object reference is reconstructed. No save is lost unless its manifest is also gone.

--verify checks all objects against their hashes after rebuilding.

Handles dangling parent_id references gracefully — stolen saves may reference parents that were never transferred.

16 kepr logs

kepr logs [--since <duration>] [--service <name>]

Tails the structured JSON log as human-readable lines.

14:23:01  offer    peach       a3f9c2   wanshitong@library
14:18:44  steal    peach       a3f9c2   anonymous
13:55:12  offer    nina        b4e891   wanshitong@library

--since: 1h, 30m, 24h. --service caddy on journald systems.

17 Koh integration

koh steal <kepr-url>[@<tag|id>]
koh offer [<kepr-url>] [--local]
koh offers
koh apply <id>

koh steal

Initialize a local Koh repository from a Kepr snapshot. Stores the Kepr URL in .koh/keeper as the default remote.

$ koh steal kepr.local/peach
$ koh steal kepr.local/peach@v1.0.0   # steal tagged save
$ koh steal kepr.local/peach@a3f9c2   # steal specific save

If the pinned tag is flagged, a warning is shown before any files are written.

koh offer

Push the current save to the default Kepr remote.

$ koh offer
$ koh offer kepr.example.com/peach   # specific remote
$ koh offer --local                  # write .face to disk

--local writes a self-contained .face file — a portable bundle of the manifest and all objects, applicable on any machine.

koh offers

List recent offers in the interactive pager.

koh apply

Apply a specific save by ID or from a local .face file.

$ koh apply a3f9c2
$ koh apply ./peach-a3f9c2.face

18 Configuration reference

Key Type Default Restart
server.domainstringyes
server.http_portint8080yes
server.http_bindstring0.0.0.0yes
server.ssh_portint2222yes
server.ssh_bindstring0.0.0.0yes
storage.data_dirpath/var/lib/kepryes
instance.publicboolfalseyes
retention.max_snapshotsint10no
retention.prune_on_offerbooltrueno
retention.max_bundle_age_daysint0no
contributions.accept_offersboolfalseno
contributions.require_reviewbooltrueno
contributions.require_authbooltrueno
contributions.auto_activatebooltrueno
contributions.welcome_messagestringno
review.agent_enabledboolfalseno
review.inference_urlstringno
review.inference_modelstringno
review.agent_timeout_sint180no
images.dirpath{data_dir}/imagesno
images.keepint3no
images.schedulestring"never"no
ops.tailscale_onlyboolfalseno

Private instance

[server]
domain    = "kepr.local"
http_port = 8080
ssh_port  = 2222

[storage]
data_dir = "/var/lib/kepr"

[instance]
public = false

[retention]
max_snapshots  = 10
prune_on_offer = true

[images]
schedule = "monthly"
keep     = 3

Public instance

[server]
domain    = "kepr.example.com"
http_port = 8080
ssh_port  = 2222

[storage]
data_dir = "/var/lib/kepr"

[instance]
public = true

[retention]
max_snapshots  = 0
prune_on_offer = false

[contributions]
accept_offers   = true
require_review  = true
welcome_message = """
Your welcome message here.
"""

[review]
agent_enabled   = true
inference_url   = "http://llm.local:8080/v1/chat/completions"
inference_model = "your-model-name"

19 Data directory

data_dir/
  kepr.db                 SQLite — cache, always rebuildable
  kepr.db-wal
  kepr.db-shm
  kepr.sock               Unix control socket
  kepr.pid                PID file
  kepr.prev               Previous binary (kept by upgrade)
  manifests/              SOURCE OF TRUTH
    {repo}/
      {save_id}.json      Verbatim ManifestJson
  objects/                Content-addressed store
    {hh}/
      {remaining62hex}    zlib-compressed, Blake3-keyed
  images/                 Instance images
  quarantine/             Pending contribution objects
  logs/
    kepr.log              Structured JSON log

manifests/ is the source of truth. If everything else is lost, kepr recover rebuilds from manifests alone.

20 Init system adapters

Kepr is init-system-agnostic. It handles its own signals and writes its own PID file.

kepr init --init <system> generates:

SystemFiles
systemdkepr.service
runitrun, finish
s6run, finish, notification-fd
openrckepr
launchdsoftware.asha.kepr.plist
genericstart.sh, README.txt

Generic adapter — works with any POSIX supervisor:

#!/bin/sh
exec /usr/local/bin/kepr daemon \
  --config /etc/kepr/keeper.toml

Signal contract:

SignalBehavior
SIGTERMGraceful drain, exit 0
SIGINTSame as SIGTERM
SIGHUPReload non-restart-required config
SIGUSR1Reopen log file
SIGUSR2Trigger storage GC

21 Contributions

Allow others to offer to your public instance. Their offers go to a review queue before joining the library.

Enable

[contributions]
accept_offers = true
require_review = true

Contributor signup

Contributors visit /contribute, paste their SSH public key, and are registered immediately. No username, no email, no password. Their fingerprint is their identity.

After registering:

$ koh offer kepr.uk/koh

The offer goes to your review queue.

Review

$ kepr offers pending
$ kepr offer show a3f9c2
$ kepr offer accept a3f9c2
$ kepr offer decline a3f9c2 --reason "out of scope"

Or via the ops web UI at /ops/review.

Contributor key management

kepr contrib list
kepr contrib revoke <fp>
kepr contrib block <fp>

Per-repo settings

[contributions.repos.koh]
open = true

[contributions.repos.zig-ssh]
open = false

22 Agentic review

Kepr pre-reviews contributions using a local language model. The assessment waits for you when you open the review page. Never blocking. Always optional.

Setup

[review]
agent_enabled   = true
inference_url   = "http://llm.local:8080/v1/chat/completions"
inference_model = "your-model-name"
agent_timeout_s = 180

Any local inference server with a chat completions API works. Your code never leaves your network.

Assessment format

VERDICT: REVIEW CAREFULLY
CONFIDENCE: HIGH

SUMMARY: ...
WHAT CHANGED: ...
CONCERNS: ...
WHAT LOOKS GOOD: ...
RECOMMENDATION: ...

What the agent cannot do

The agent cannot accept or decline. You always decide. The accept button requires viewing every changed file and waiting 60 seconds regardless of the agent's verdict.

When the inference endpoint is unavailable, the review page works fully without the assessment.

23 Instance images

A complete portable snapshot — data, configuration, and the exact binary that produced it. See kepr image in §14.

The content checksum is Blake3 over the sorted concatenation of every manifest and object. Verifiable with any Blake3 implementation, independently of Kepr.

The README.txt in every image has plain-text restoration instructions. Plain ASCII. Readable on any terminal, in any future, without any tools.

Schedule in config:

[images]
schedule = "monthly"
keep     = 3

24 Ops web UI

The operator interface lives at /ops. Always protected — either by Tailscale IP restriction (ops.tailscale_only = true) or by username and password (default).

Set credentials at kepr init. Manage:

kepr ops password
kepr ops sessions
kepr ops sessions revoke --all

Pages

RoutePurpose
/opsDashboard
/ops/reposRepo management
/ops/repos/:nameRepo detail
/ops/storageStorage and trend
/ops/keysKey management
/ops/configVisual config editor
/ops/logLive log tail
/ops/reviewContribution queue
/ops/recoverRecovery wizard

The attention section surfaces anything needing action. Empty means all clear.

25 NixOS module

# private backup instance
services.kepr = {
  enable       = true;
  domain       = "kepr.local";
  tailscale    = true;
  public       = false;
  maxSnapshots = 10;
  authorizedKeys = [ "ssh-ed25519 AAAA... wanshitong@library" ];
};

# public portal instance
services.kepr = {
  enable       = true;
  domain       = "kepr.example.com";
  tailscale    = false;
  public       = true;
  maxSnapshots = 0;
  authorizedKeys = [ "ssh-ed25519 AAAA... wanshitong@library" ];
};

The module handles systemd units, users, directories, firewall, Caddy virtual host, and Tailscale IP binding.

authorizedKeys is the source of truth for operator keys — adding or removing a key and running nixos-rebuild switch takes effect immediately.

Contributor keys (web UI registered) are stored separately — nixos-rebuild switch never touches them.

Scheduled images:

services.kepr.images = {
  schedule = "monthly";
  keep     = 3;
};

26 Signal handling

SignalBehavior
SIGTERMGraceful drain, exit 0
SIGINTSame as SIGTERM
SIGHUPReload non-restart-required config keys
SIGUSR1Reopen log file
SIGUSR2Trigger storage GC

SIGHUP applies immediately: retention.*, contributions.*, review.*, images.*.

Keys requiring restart (server.*, storage.data_dir, instance.public) are logged as warnings on SIGHUP but not applied.

27 Upgrading

kepr upgrade [--version <x.y.z>] [--dry-run] [--rollback]

Verifies SHA-256 and Minisign signature before touching anything. Takes a backup if none exists in the last 24 hours. Swaps binary, restarts, verifies health. Auto-rollbacks if health check fails within 30 seconds.

Previous binary kept at data_dir/kepr.prev for one version. --rollback restores it manually.