Files
jarvis-selfhost/README.md
T

187 lines
10 KiB
Markdown

# Self-hosting Jarvis
Jarvis is an AI-assisted infrastructure administration platform for MSPs. This repository runs it from
published container images — no source, no build, no account with the project.
## What you need
- Docker with Compose v2, on anything Linux.
- A hostname and a TLS terminator in front of it. Jarvis speaks plain HTTP and reads
`X-Forwarded-Proto`; it does not manage certificates.
- An API key for an OpenAI-compatible endpoint.
- Roughly 2 GB of RAM for the stack and room for Postgres to grow.
## Install
```sh
git clone https://git.luxit.be/Luxit/jarvis-selfhost.git
cd jarvis-selfhost
cp .env.example .env
$EDITOR .env # every comment in it is load-bearing; the secrets note especially
docker compose pull
docker compose up -d
```
First boot syncs the database schema and runs its data backfills before the API listens, so give it
about a minute. Then point your reverse proxy at `JARVIS_PORT` and open the app.
**The first account created becomes super-admin.** Self-registration is open by default, so sign up
immediately after the stack is up and then close registration under Settings → Platform → General.
Leaving it open means anyone who reaches the sign-in page gets an account.
## Your reverse proxy has two requirements
Both are the kind that produce confusing symptoms rather than clean errors.
- **Forward the WebSocket upgrade.** Without it the chat cannot stream and no agent can connect.
- **Give it a long read timeout** — 300s or so. A reasoning model can go 90+ seconds without emitting
a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather
than a timeout, which reads like a bug in Jarvis.
## Upgrading
```sh
docker compose pull && docker compose up -d
```
That is the whole upgrade: both services track `latest`. Schema changes apply themselves when the api
starts, and the api and the web are versioned independently — their numbers are not meant to match,
because usually only one side changed.
**`latest` does not mean the app forgets which build it is.** The tag is a second name on the same image
as the version tag, and the version is stamped into the image when it is built — so the footer in the
app and `/version.json` keep reporting the real number, whichever name you pulled it under. That is
what lets you tell somebody which build you are on when something goes wrong.
Once you are in production, consider pinning: set `JARVIS_IMAGE_API` and `JARVIS_IMAGE_WEB` in `.env`
to explicit tags. It makes an upgrade a decision rather than a side effect of pulling.
Take a database dump before an upgrade that moves the api's minor version:
```sh
docker compose exec -T postgres pg_dump -U jarvis jarvis | gzip > jarvis-$(date +%F).sql.gz
```
## What you get, and what you do not
Working: the assistant with its tool-calling loop, plan-level approvals for destructive operations,
the encrypted vault, the asset registry, multi-tenant RBAC, real-time conversations, documents and
exports, the audit trail, and the connectors — SSH, Proxmox, Microsoft 365 and MikroTik.
**The Jarvis agent is an optional overlay, off by default.** Enrolling a machine downloads a compiled
binary that the api serves from `AGENT_RELEASE_DIR`, and a compose-only deployment has no way to
produce one. `docker-compose.agent.yml` supplies it as a pullable image instead. Leaving it off is a
supported state rather than a broken one: everything else works, the installer answers 503 saying no
build is published, and the connectors above reach machines without it.
## The agent
Turn it on by adding one line to `.env`, so that every later `docker compose` command picks up both
files with no extra flags:
```sh
COMPOSE_FILE=docker-compose.yml:docker-compose.agent.yml
```
then `docker compose pull && docker compose up -d`. A one-shot `agent-releases` service copies the
release into a volume the api reads, and exits. From there, Settings → Agents hands you the install
one-liner for each platform.
Three things worth knowing about it:
- **Until that publisher exits cleanly, the api does not start.** That is deliberate — a release that
failed to arrive should stop the deploy loudly rather than leave you handing 404s to every installer
you run this week. The cost is that an unreachable registry blocks the whole stack. The comment in
the file names the three lines to drop if you would rather it degraded quietly.
- **Upgrading it does not restart anything.** The api computes each download's checksum from the bytes
on disk on every request, so a new release in the volume is served immediately.
- **The agent version is its own number.** It moves independently of the api and the web, and a Jarvis
release usually does not touch it at all. Pin it with `JARVIS_IMAGE_AGENT` when you pin the others.
The agent runs as root on Linux and macOS and as LocalSystem on Windows, deliberately — its purpose is
to administer the machine. What it may actually *do* is decided by Jarvis' autonomy policy and its
approval gates, not by the account it runs under. Read that section of the main documentation before
enrolling anything you care about.
## Things worth knowing before you trust it with production
These are deliberate and documented rather than surprises waiting to be found.
- **`VAULT_MASTER_KEY` has no recovery.** Read the note in `.env.example`. A database backup does not
protect the vault — the backup holds ciphertext encrypted under that key.
- **SSH host keys are not verified.** Every SSH connection trusts whatever key answers. This is the
one gap in the execution path with no compensating control.
- **Access tokens cannot be revoked.** Revoking a session or changing a password invalidates refresh
tokens; a stolen access token stays valid for up to 15 minutes.
- **The api must run as a single replica.** In-flight runs, pending approvals and presence live in
per-process memory. Scaling it out silently drops cancels and approvals answered on the wrong one.
- **Both containers run as root**, and `/api/health` answers 200 with `status: "degraded"` when the
database is unreachable, so the container healthcheck alone is not a liveness signal for the DB.
- The assistant executes real operations on real infrastructure. Destructive ones require in-chat
human approval; mutating ones do not. Decide your autonomy level per conversation accordingly.
## Backups
Postgres holds everything except the vault key. Two volumes matter:
```sh
docker compose exec -T postgres pg_dump -U jarvis jarvis | gzip > jarvis.sql.gz
```
Plus `VAULT_MASTER_KEY`, stored somewhere that is not this host. A dump without the key is a database
whose credentials cannot be read.
## Licence keys, and what your instance reports
**Jarvis needs a licence key.** Ask your provider for one and put it in `.env` as
`JARVIS_LICENSE_KEY`. The key is a signed token your instance verifies **offline** — it carries your
term and your limits, and it needs no network to be checked.
Without one, an instance keeps running everything already set up — every organization, every user,
every agent, and the assistant itself — and refuses to create anything NEW. So an existing
deployment does not stop working when this reaches it, and a fresh install gets as far as its first
administrator account and then needs a key.
**An unlicensed instance contacts nobody.** No check-in, no telemetry, nothing leaves your network at
all. The reporting below starts only once a key is in place.
A licensed instance then reports to the address written into that key, at an interval your provider
sets — every ten minutes on the current arrangement, so that a renewal or a revocation reaches you
promptly rather than tomorrow. This is everything it sends, in full:
| Field | What it is |
| ----------------------- | ------------------------------------------------- |
| Product | The literal string `jarvis` |
| Licence id | Which licence this is |
| Instance id + public key | A key pair your instance generated, identifying it |
| Version | Which Jarvis build you are running |
| Contract version | Which set of limits this build understands — a checksum, not a document |
| Counts | How many organizations, users, agents and assets |
| Public URL | Your instance's address — always sent, see below |
| Timestamps | When the process started, and when it reported |
**Counts, not contents.** No names, no email addresses, no conversation text, no asset inventory, no
credentials, nothing about what you administer.
**The public URL is the one field that names your network rather than measuring something, and on a
licensed instance it is sent.** It used to be a switch on the licence screen; it is not any more,
because an installation the publisher can identify only by a fingerprint is one where "which of these
is the customer calling about" has no answer. The screen now shows you the exact address that leaves,
under Settings → Licence. If that is not acceptable for your deployment, the answer is to run
unlicensed — which contacts nobody at all — or to take it up with your provider before installing.
The reply can carry a renewed key, which your instance adopts on its own — so a renewal reaches you
without anybody re-pasting anything.
**Your platform does not stop working because of a licence.** Expiry gives you a grace period, then
refuses only the creation of new organizations, users and agents — everything already set up keeps
running, and so does the assistant. There is no state in which Jarvis disables, deletes or locks you
out of something you are already using. If the check-in cannot reach the server, nothing changes at
all: the key you hold is what governs, and it is checked without a network.
## Licence
The images are provided as-is with no warranty, no support and no commitment to future availability.
The source is not public and no rights to it are granted. Ask the maintainer before deploying this
commercially or for third parties.