R.E.C.R.E.C. rec.farm
Valtr docs 3 pages

Valtr docs

Overview.

Self-hosted secrets for your apps. Part of the rec.farm family.

Keep each project's secrets per environment, encrypted at rest. Hand a CI job or a server an API key that can read (or write) one project and nothing else, pull everything as a .env file or JSON, and see who touched what in the audit log. One small Rust binary and one SQLite file.

  • Encrypted at rest. Every value is sealed with AES-256-GCM under your VALTR_MASTER_KEY. Listings never include values.
  • Environments. New projects get development, staging and production. Add as many as you like.
  • Secret history. Every write is a new version. Old values stay encrypted, can be read (audited) and restored.
  • Shared projects. Invite people with a one-time link as read or write members. The owner decides who's in.
  • Scoped API keys. vltr_… keys belong to one project, read-only or read-write, and can expire and be rotated. They never do more than the person holding them. Only their hash is stored.
  • A CLI for CI and servers. valtr run production -- ./server starts a process with its secrets; valtr pull production > .env writes them out.
  • Exports. GET …/secrets/.env and …/secrets/json.
  • Audit log. Creates, updates, reads, exports and deletions, with the client IP.
  • Sign in with Handstamp. People sign in with Handstamp (or any OpenID Connect provider), which also decides who may. Signing out there signs you out here (back-channel logout). Machines use API keys. No passwords.

A web app covers all of it: projects and the people in them, environments, a secrets editor with history, API keys and the audit log, in Dark and Paper. Everything it does goes through the same API, so scripts and CI can do it too.

Docs: API · Deploying

Run it

Needs Rust 1.92, Node 24 and pnpm. The dev server runs through portless, like the rest of the family. Install it globally once with npm install -g portless. Don't add it to the project.

pnpm install
cp .env.example .env              # then set VALTR_MASTER_KEY (openssl rand -hex 32)
pnpm dev                          # http://valtr.localhost:1355

Open it and use Dev login: in development you sign in as a local developer, without Handstamp. To try the real sign-in, add the OIDC_* settings from .env.example for a Handstamp you run (../handstamp) with Valtr registered as an app.

pnpm dev runs Vite with hot reload in front of the Rust server, which it builds and restarts with the script. pnpm dev:direct skips portless and serves on http://localhost:3000 (PORT to change). Both read .env and keep the database in ./data/valtr.db. In Herdr, start them through herdr-run.

Or with Docker, once .env has the Handstamp settings: docker compose up -d --build. Outside development Valtr doesn't start without them.

From a terminal, the same as the web app (the dev login here; in production, a session from the web app or an API key):

VALTR=http://valtr.localhost:1355
curl -c jar -X POST $VALTR/api/auth/dev-login
curl -b jar -H 'content-type: application/json' $VALTR/api/projects -d '{"name":"My App"}'
curl -b jar -H 'content-type: application/json' \
  $VALTR/api/projects/my-app/environments/production/secrets \
  -d '{"key":"DATABASE_URL","value":"postgres://…"}'
curl -b jar -H 'content-type: application/json' $VALTR/api/api-keys \
  -d '{"name":"deploy","projectSlug":"my-app"}'      # → {"key":"vltr_…", …} shown once

In CI and on servers

The same valtr binary is the client. Give it the server and an API key:

export VALTR_URL=https://valtr.example.com VALTR_API_KEY=vltr_…
valtr run production -- ./server          # the secrets are in its environment
valtr pull production > .env              # or a .env file (--json for JSON)

An API key sees only its project, so there's nothing else to say; --project <slug> picks one when it's ambiguous. A secret wins over an environment variable of the same name, and VALTR_API_KEY isn't passed on to the command. Secrets that can make a program load other code (PATH, LD_*, DYLD_*, NODE_OPTIONS, PYTHONPATH, BASH_ENV and the like) are skipped with a warning; set those yourself. That only blocks the obvious ways: whoever can write a project's secrets configures what run starts, so give write access only to people you'd trust with the machines that run it. run replaces itself with the command, so signals and the exit status are the command's own. The key only travels over https:// (or to localhost).

Without the binary, it's one request:

curl -H "Authorization: Bearer $VALTR_API_KEY" \
  $VALTR_URL/api/projects/my-app/environments/production/secrets/.env > .env

Develop

pnpm test                         # unit tests + HTTP API + OIDC sign-in against a mock provider
pnpm check                        # prettier, rustfmt, clippy -D warnings, tests, release build
pnpm format
pnpm valtr <command>              # the binary against .env: serve, migrate, accounts, link, pull, run

The server is backend/ (Rust, axum). The web app is apps/web (Vite, React, Tailwind, on the R.E.C. design system); the server serves its build. Migrations run on start; pnpm db:migrate runs them alone.

Path What
backend/src/routes.rs Projects, people, environments, secrets and their history, exports, API keys, audit log
backend/src/auth.rs Sessions, accounts, the dev login, API keys and their scoping
backend/src/oidc.rs Sign in with Handstamp (OpenID Connect relying party)
backend/src/crypto.rs AES-256-GCM, API keys, session tokens
backend/src/client.rs valtr pull and valtr run
backend/src/db.rs SQLite schema and migrations
backend/src/web.rs Serving the web app, its CSP and security headers
apps/web/src/pages The web app's pages

Coming from the TypeScript version

Valtr was a Bun + Hono + Drizzle server. The Rust version speaks the same HTTP API and opens the same database: point VALTR_DB_PATH at the old valtr.db and keep the same VALTR_MASTER_KEY. API keys and encrypted values carry over. The differences:

  • Handstamp instead of passwords. People sign in with Handstamp; the register and login endpoints are gone. Old accounts keep their projects but have no Handstamp identity yet, and Valtr never matches one by email. Have each person sign in once to get their Handstamp sub (or read it in Handstamp's admin), then link it: valtr link me@example.com <sub>. valtr accounts shows who is linked.

  • API keys are enforced. A key now only reaches its own project, read keys can't change anything, and keys can't create or delete projects or manage keys. Before, any key acted as its owner.

  • .env and json exports work. The /:key route used to catch them.

  • .env values are quoted whenever they hold anything beyond plain characters, so a value can't inject extra lines.

  • Projects and environments with history can be deleted. The audit log keeps plain ids now instead of foreign keys that blocked the delete.

  • Sessions survive restarts. They live in the database, not in process memory. Everyone signs in once after the upgrade.

  • More is audited: reads, exports, environment and API-key changes, and the client IP.

  • Stricter names. Environment names and secret keys use letters, digits, _, - and .. Existing data isn't touched.

  • No way back. The upgrade drops the old password hashes (users.password_hash), so the TypeScript server can't open the database afterwards. Back it up first.

License

Apache-2.0

From rec-farm/valtr/README.md · master@3d56e62 · 2026-10-10