LeVCS/deploy
Levi Neuwirth 21c6056ae6 levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00
..
Caddyfile.example levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00
README.md levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00
instance.toml.example levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00
levcs-instance.service levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00
nginx.conf.example levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00

README.md

Hosting a LeVCS instance on a VPS

This walkthrough takes you from "I have a VPS" to "the LeVCS source code lives on my LeVCS instance" using the artifacts in this directory.

The instance terminates HTTP, not TLS, so you'll run it behind a reverse proxy (Caddy or nginx). Federation requests are signed at the application layer, so the proxy is just transport security + rate limiting — there's no auth handoff between layers.

Architecture (one-liner)

laptop  --HTTPS-->  Caddy/nginx (TLS)  --HTTP--> levcs-instance (127.0.0.1:7117)
                                                       |
                                                       v
                                                 /var/lib/levcs

What's in this directory

File Purpose
instance.toml.example Annotated config — copy to /etc/levcs/instance.toml.
levcs-instance.service systemd unit. Runs as a levcs user, hardened.
Caddyfile.example Caddy reverse-proxy block (auto-TLS via Let's Encrypt).
nginx.conf.example nginx alternative (use if you already run nginx).

VPS-side install

1. Build the binary

On a build host (the VPS itself or a beefier dev machine cross-compiled to its target), build a release binary:

cargo build --release -p levcs-instance --bin levcs-instance

The binary lands at target/release/levcs-instance. Copy it to /usr/local/bin/levcs-instance on the VPS.

2. Create the service user and directories

sudo useradd --system --home /var/lib/levcs --shell /usr/sbin/nologin levcs
sudo install -d -o levcs -g levcs -m 0755 /var/lib/levcs
sudo install -d -o root -g root -m 0755 /etc/levcs

3. Drop the config in place

sudo cp deploy/instance.toml.example /etc/levcs/instance.toml
sudo $EDITOR /etc/levcs/instance.toml

The defaults (full storage, builtin handlers only, no mirrors, listen on 127.0.0.1:7117) are correct for a single-VPS install. Change root only if /var/lib/levcs doesn't suit your filesystem layout.

4. Install the systemd unit

sudo cp deploy/levcs-instance.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now levcs-instance
sudo systemctl status levcs-instance

Verify it's listening:

curl -fsS http://127.0.0.1:7117/health
# {"status":"ok"}

5. Reverse proxy

If Caddy isn't installed yet:

sudo apt install caddy        # or your distro's package

Edit Caddyfile.example, replace levcs.example.com with your real hostname, then either drop it in as /etc/caddy/Caddyfile or import it from your existing one. Reload Caddy:

sudo cp deploy/Caddyfile.example /etc/caddy/Caddyfile
sudo $EDITOR /etc/caddy/Caddyfile
sudo systemctl reload caddy

Caddy will fetch a Let's Encrypt cert automatically on the first request. Confirm:

curl -fsS https://levcs.example.com/health
# {"status":"ok"}

Option B — nginx (if you already run it)

If your VPS already runs nginx (e.g. fronting Forgejo), use a server block alongside the existing ones rather than introducing Caddy. Make sure you have a TLS cert for the new hostname (certbot: sudo certbot --nginx -d levcs.example.com).

sudo cp deploy/nginx.conf.example /etc/nginx/sites-available/levcs
sudo $EDITOR /etc/nginx/sites-available/levcs
sudo ln -s ../sites-available/levcs /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

The example block handles both 80→443 redirect and the proxy itself. The location regex (/levcs/v1 and /health) ensures everything else returns 404 — there is no web UI yet, and the instance shouldn't appear to host one.

6. Firewall

Open 80 and 443 on the VPS, keep 7117 closed from the public internet:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 7117 stays closed — only Caddy/nginx talk to it.

Laptop-side bootstrap

Now make the LeVCS source code itself a LeVCS repository hosted on the new instance. This is the dogfood claim: you're running the protocol on your own code from this point on.

1. Build the CLI locally

cargo build --release -p levcs-cli --bin levcs
sudo install -m 0755 target/release/levcs /usr/local/bin/levcs

2. Generate or import an identity key

If you don't already have a LeVCS key:

levcs key generate --label primary
levcs key list

The label is yours to choose (alice, primary, your handle — anything). This key is your authority membership credential; it signs every authority object and every push.

3. Init the repo locally

From inside the LeVCS source tree:

levcs init --key primary
levcs track --all
levcs commit -m "initial import"

init writes a .levcs/ directory next to your source, with a genesis authority object that names your key as the sole Owner. The repo_id is the BLAKE3 hash of that authority — globally unique by construction.

4. Point the local repo at the VPS instance

levcs instance --set https://levcs.example.com/levcs/v1
levcs instance --info

The --set value is what the federation client uses for every push and pull on this repo.

5. First push (auto-init)

levcs push refs/branches/main

Behind the scenes the client tries /push first, gets a 404 (the repo doesn't exist on the instance yet), then falls back to /init with the genesis authority object and re-pushes. Output looks like:

repo not yet on instance; initialising
pushed 1 ref(s)

That's it. The repo is now hosted on the VPS, available to anyone over HTTPS:

curl -fsS https://levcs.example.com/levcs/v1/repos/<repo_id>/info | jq

6. (Optional) Verify the server-side state

SSH to the VPS and look at what got persisted:

sudo ls /var/lib/levcs/
sudo ls /var/lib/levcs/<repo_id>/.levcs/refs/branches/
sudo cat /var/lib/levcs/<repo_id>/.levcs/refs/authority/current

You should see your repo_id directory, a main ref pointing at your head commit hash, and a current-authority pointer matching the genesis.


Operating notes

Logs

sudo journalctl -u levcs-instance -f         # live
sudo journalctl -u levcs-instance -p warning # warnings + errors only

The TraceLayer middleware emits one line per HTTP request at info level — method, path, status, latency. Bump to debug via Environment=RUST_LOG=debug in the systemd unit when diagnosing.

Backups

The instance is filesystem-only. A consistent backup is just a snapshot of /var/lib/levcs. Per-push atomicity is per-object plus a serializing per-repo mutex (see crates/levcs-instance/src/lib.rs), which means a snapshot taken at any moment is internally consistent even without quiescing the service. rsync --link-dest for incremental hardlink snapshots works well.

Storage growth

There's no automatic GC on the instance. Run levcs gc from a client that has the repo locally; instance-side GC is a future feature. For now, "GC" on the VPS is "delete entire <repo_id>/ directories of repos you no longer want."

Updating the binary

sudo systemctl stop levcs-instance
sudo install -m 0755 target/release/levcs-instance /usr/local/bin/levcs-instance
sudo systemctl start levcs-instance

The on-disk format is content-addressed and forward-compatible — there's no migration step between versions. If a future release introduces an incompatible change, the release notes will say so.

Mirrors

To set up a read-only mirror of a repo from another instance, add a [[mirrors]] block to /etc/levcs/instance.toml:

[[mirrors]]
repo_id = "abcd1234..."
source = "https://other.example/levcs/v1"
mode = "full"
poll_interval = "5m"
writeback = false

Then sudo systemctl restart levcs-instance. The background poller spawns at startup; journalctl -u levcs-instance will show the sync activity.


What's missing (workflow honesty)

The protocol surface this instance exposes is just refs + objects + auth. There is no web UI, issue tracker, PR/review surface, comment thread, notification hub, or branch-protection layer yet. If you're migrating from Forgejo for the LeVCS source, you'll lose:

  • The web frontend for browsing code, blame, and history.
  • Issues and PR discussions (and any Forgejo-specific automations).
  • CI integration (no webhooks yet — you'd need to poll the refs from your CI).

That's the spec gap that comes next. This deployment is the substrate the workflow layer will sit on top of.