207 lines
11 KiB
Markdown
207 lines
11 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: every service tracks the **`stable`** channel by default. 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.
|
|
|
|
### Channels
|
|
|
|
| Channel | What it means |
|
|
| -------- | ---------------------------------------------------------------------------------- |
|
|
| `stable` | Promoted after running on the publisher's own instance. **The default, and what you want.** |
|
|
| `dev` | Every build, as soon as it is published. Nothing has tried it yet. |
|
|
| `latest` | A second name for `stable`, kept so nothing that already used it has to change. |
|
|
|
|
A channel is a **pointer** and a version number is a **fact**. `0.78.2` means one specific set of
|
|
bytes for ever; `stable` means whichever set we currently stand behind, and it moves. A build only
|
|
reaches `stable` by being promoted — and promotion copies the manifest of an image that has already
|
|
been published and already run. It never rebuilds, so the bytes you receive are the same bytes that
|
|
were tested, not a fresh build of the same source.
|
|
|
|
**Channels do not mean the app forgets which build it is.** The version is stamped into the image
|
|
when it is built, so the footer in the app, `/version.json` and the agent manifest 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`, `JARVIS_IMAGE_WEB` and
|
|
`JARVIS_IMAGE_AGENT` in `.env` to explicit version tags. It makes an upgrade a decision rather than a
|
|
side effect of pulling.
|
|
|
|
**Pin the agent one first if you pin only one.** The api and the web change what your own server
|
|
runs; the agent changes what runs on every machine you administer, and it applies itself without
|
|
asking. An enrolled agent also refuses any version that is not strictly newer, so moving that pin
|
|
back stops a rollout rather than reversing it on machines that already took the update.
|
|
|
|
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.
|