# 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: ```sh 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 ```sh 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 ```sh 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 ```sh 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: ```sh curl -fsS http://127.0.0.1:7117/health # {"status":"ok"} ``` ### 5. Reverse proxy #### Option A — Caddy (recommended) If Caddy isn't installed yet: ```sh 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: ```sh 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: ```sh 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`). ```sh 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: ```sh 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 ```sh 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: ```sh 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: ```sh 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 ```sh 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) ```sh 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: ```sh curl -fsS https://levcs.example.com/levcs/v1/repos//info | jq ``` ### 6. (Optional) Verify the server-side state SSH to the VPS and look at what got persisted: ```sh sudo ls /var/lib/levcs/ sudo ls /var/lib/levcs//.levcs/refs/branches/ sudo cat /var/lib/levcs//.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 ```sh 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 `/` directories of repos you no longer want." ### Updating the binary ```sh 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`: ```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.