The self-hosting stack

This commit is contained in:
2026-08-24 16:05:14 +02:00
commit 8d28604625
10 changed files with 1920 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
# Jarvis — optional configuration.
#
# THERE IS NOTHING YOU HAVE TO FILL IN. You do not need this file at all:
#
# docker compose up -d
#
# then open the address in a browser and the install screen asks the questions. It creates the first
# administrator, checks the deployment, and stores the address, the model and everything else as
# settings you can change later from a screen.
#
# THIS FILE USED TO BE MANDATORY, and that is the change. It asked for six values before the
# container would start, four of them secrets to be generated with `openssl` — and it failed in the
# worst available way, because an invalid one made the API exit 1 and the restart policy turned a
# typo into a crash loop, with the reason on line 40 of a log nobody had a reason to open.
#
# What is left below is for deployments that prefer to state things in writing: pinning image
# versions, running the agent overlay, choosing a host port. Every value here is optional, and the
# ones that overlap with the install screen SEED it — they are read once, when nothing is stored
# yet, and never again. Change them afterwards and nothing happens; change the setting instead.
#
# cp .env.example .env # only if you want any of this
# ---------------------------------------------------------------------------
# Where your instance lives
# ---------------------------------------------------------------------------
# Host port the web container publishes. Put your TLS terminator in front of it.
#
# The one value on this page that genuinely cannot move to a screen: it is a Docker fact, decided
# before anything in the application is running.
JARVIS_PORT=8080
# The address a browser reaches Jarvis on. ASKED BY THE INSTALL SCREEN, which pre-fills it with the
# address you are already reading it at — so setting it here is only useful if you want it stated in
# writing, or if you are upgrading a stack that already had it.
#
# It decides CORS, what enrolled machines dial, the base of every invitation link, and the WebAuthn
# relying party. Changing it later orphans every passkey already enrolled, so the screen warns you.
#WEB_ORIGIN=https://jarvis.example.com
# PUBLIC_URL IS GONE. It was a second address for the same thing, kept separate for a split-hostname
# deployment nobody ran; the install screen asks once and every consumer reads that one value. An
# instance that still sets it is not broken — it is simply ignored.
# How many proxies rewrite X-Forwarded-For before a request reaches the API.
#
# ASKED BY THE INSTALL SCREEN, which is the only place this can honestly be answered: it shows you
# the header chain your own request actually carried and the address the API resolved from it, and
# you confirm what you see. Nobody knows this number in advance.
#
# One is the web container's own nginx, which is always there; two if your own reverse proxy fronts
# it, which is the usual case. Raising it past the real number is the dangerous direction, because
# the API then trusts that many hops of a header the client can forge — and a caller can choose the
# address that lands in your audit log and in the session list.
#TRUST_PROXY_HOPS=2
# ---------------------------------------------------------------------------
# Secrets — there is nothing to fill in here any more
# ---------------------------------------------------------------------------
#
# This section used to hold four values and four `openssl rand` invocations. They are generated for
# you now, on your first `docker compose up`, by the `init` service — into the `jarvis_secrets` volume,
# where the database password, the two session-signing secrets and the vault master key live.
#
# WHY THEY LEFT THIS FILE. A vault key is not a preference. Asking for one put the single most
# consequential value in the deployment — everything in the vault is encrypted under it — in front of
# whoever was least equipped to look after it, at the moment they were least interested in it, in a
# file they were trying to get through. Nobody ever chose a better key than `openssl rand` would have.
#
# WHAT YOU STILL HAVE TO DO, AND IT IS THE IMPORTANT ONE:
#
# BACK UP THE `jarvis_secrets` VOLUME, SEPARATELY FROM THE DATABASE.
#
# The vault key is in there and NOWHERE ELSE. A database backup does not save you — the backup holds
# the ciphertext. An instance whose key is gone keeps LOOKING configured, with every row in place,
# and fails on every reveal. It is a separate volume from `postgres_data` precisely so that the two
# can be, and must be, backed up to different places: a dump that travelled with the key that opens
# it is a dump that opens itself.
#
# The setup screen shows you the key once, on your first visit, and will not let you finish until you
# have put it somewhere. That is the moment to do this.
#
# UPGRADING FROM A .env THAT ALREADY HAS THESE? Leave them exactly where they are. The generator
# ADOPTS an existing value rather than replacing it, and never overwrites a secret it has already
# written — so your instance keeps its own keys and nothing about your vault changes. You can delete
# them from this file once the stack has come up once, or leave them; they are read only when the
# corresponding file does not exist yet.
#
# Pointing at secrets of your own instead? Every one of them also accepts a `_FILE` variable
# (VAULT_MASTER_KEY_FILE, JWT_ACCESS_SECRET_FILE, …), which is the convention the postgres image and
# most others already use — so a `docker secret` can be mounted at those paths with nothing else
# changing.
# ---------------------------------------------------------------------------
# The model — asked by the install screen, with a test button
# ---------------------------------------------------------------------------
#
# All four moved. The install screen asks for the endpoint and the key, lists the models the endpoint
# actually serves, and ASKS THE MODEL FOR A TOKEN before it saves anything — so a key that is
# well-formed and wrong, an account out of credit or a model id the provider does not serve is a
# sentence on screen instead of a fault in somebody's first conversation.
#
# The key is stored encrypted under this instance's vault key, exactly like the outbound mail secret,
# and is never shown again. Change any of it later under Settings -> Platform -> Model.
#
# Setting them here still works and seeds an instance that has nothing stored — for a deployment
# that would rather state its model in a file. They are read once and never again.
#OPENAI_BASE_URL=https://api.openai.com/v1
#OPENAI_API_KEY=
#OPENAI_MODEL=gpt-4o
# minimal | low | medium | high. Higher costs latency and tokens and is worth it for real work.
#OPENAI_THINKING_LEVEL=medium
# ---------------------------------------------------------------------------
# Unattended installation (for fleets and for CI)
# ---------------------------------------------------------------------------
#
# Everything above is optional because the install screen asks for it. This is the other direction:
# somebody rolling out fifty instances from a template has answered those questions once already,
# and making them answer each one in a browser is exactly what this product stopped doing to people.
#
# Set this and the instance installs itself at boot from whatever the variables above provide, marks
# itself installed, and never shows the wizard.
#
#JARVIS_UNATTENDED=on
#
# The first administrator. REQUIRED when JARVIS_UNATTENDED is on and the database is empty; the
# password must be at least 12 characters.
#
# If they are missing or too short the instance says so loudly in its logs and leaves setup
# OUTSTANDING rather than completing — an instance marked installed with no account is one nobody
# can ever get into, and the claim is gated on setup being unfinished, so there would be no
# way back. Falling through to the ordinary install screen is strictly better than that.
#
#JARVIS_ADMIN_EMAIL=
#JARVIS_ADMIN_PASSWORD=
#JARVIS_ADMIN_NAME=Administrator
#
# Worth setting alongside them, since nobody will be there to be asked: WEB_ORIGIN, OPENAI_API_KEY
# and OPENAI_MODEL above, and JARVIS_LICENSE_KEY below. Each is optional even here — an unattended
# instance with no model comes up and reports that it has none, rather than refusing to start.
# ---------------------------------------------------------------------------
# Channels and pinning (optional, recommended in production)
# ---------------------------------------------------------------------------
# Every image tracks the `stable` channel unless you set these, so `docker compose pull && up -d`
# upgrades you to whatever has most recently been promoted. `stable` moves only after a build has
# run on the publisher's own instance; `dev` moves on every build and nothing has tried it yet.
# `latest` is a second name for `stable`, kept so nothing that already used it has to change.
#
# Which channel you are on is yours to state, because nothing in the image knows it — a channel is
# decided after a build and moves afterwards. Set it and the app shows it beside the version
# numbers; leave it empty if you pin below, because then you follow no channel.
#JARVIS_CHANNEL=stable
# Pinning makes an upgrade a decision instead of a side effect of pulling. The app reports its real
# version either way — a channel tag is a second name on the same image, not a build that forgot
# its number.
#
# THERE IS NO EXAMPLE NUMBER HERE ON PURPOSE. Pin the version you are ALREADY RUNNING, which the app
# footer shows as `web … · api …`. Nothing in the publishing path bumps a number written into this
# file, so any number printed here is one that went stale while nobody was looking — and moving the
# api pin BACKWARDS runs an old build against a schema that has already been migrated forward.
#JARVIS_IMAGE_API=git.luxit.be/luxit/jarvis-api:<the api version in your footer>
#JARVIS_IMAGE_WEB=git.luxit.be/luxit/jarvis-web:<the web version in your footer>
# Only read when the agent overlay is enabled, just below. Its version is the AGENT's, and moves
# independently of the two above — a Jarvis release usually does not change the agent at all.
#
# PIN THIS ONE FIRST if you pin only one. The two above change what your own server runs; this one
# changes what runs on every machine you administer. Pinning it decides which build your instance
# publishes; a separate UPDATE POLICY decides when a machine takes it, and its default is "let the
# agent decide" — which in practice means the next time its service starts. Set that policy
# instance-wide under Settings → Platform → General, per organization under Settings → Organization →
# Agent updates. An enrolled agent also refuses any version that is not strictly newer, so moving
# this back stops a rollout rather than reversing it on machines that already took the update.
#
# The app footer does not carry this number. Each enrolled machine reports the build it runs, on the
# Agents page — that is the one to pin.
#JARVIS_IMAGE_AGENT=git.luxit.be/luxit/jarvis-agent-dist:<version>
# ---------------------------------------------------------------------------
# The Jarvis agent (optional)
# ---------------------------------------------------------------------------
# Enrolling a machine downloads a compiled binary, which the api serves from a directory it can
# only read. `docker-compose.agent.yml` supplies that directory as a pullable image, so the release
# arrives the same way the rest of the stack does. Uncomment this and every later `docker compose`
# command picks up both files with no extra flags:
#
#COMPOSE_FILE=docker-compose.yml:docker-compose.agent.yml
#
# Leaving it off is a supported state, not a broken one: everything except the agent works, and the
# installer answers 503 saying no build is published. The SSH, Proxmox, Microsoft 365 and MikroTik
# connectors all reach machines without it.
#
# AGENT_RELEASE_DIR is set by that overlay and should NOT be set here — a value in this file would
# point the api at a path nothing populates, turning the honest 503 into a 404 per platform.
#AGENT_HEARTBEAT_INTERVAL_SEC=30
#RUN_SHUTDOWN_GRACE_SEC=25
# How long a sign-in lasts, in seconds. Both were fixed in the compose file until now, which made
# them the only tuning values on this page you could read about and not change.
#
# JWT_ACCESS_TTL is how long an access token stays valid — and therefore how long a revoked session
# keeps working before it notices. Lower it if that window matters to you; the cost is a refresh
# round trip more often.
#
# JWT_REFRESH_TTL IS AN IDLE TIMEOUT, NOT A SESSION LIFETIME, and the difference will matter to you
# if you are here because of a policy. The refresh token rotates on every use and the new one starts
# its full term from that moment, so nothing anywhere measures how old a sign-in is: somebody who
# keeps a tab open stays signed in indefinitely. What this value really sets is how long a session
# survives being left alone. Fourteen days is the default. To end sessions by age rather than by
# idleness, revoke them — Settings → Security lists them and kills them individually.
#JWT_ACCESS_TTL=900
#JWT_REFRESH_TTL=1209600
# ---------------------------------------------------------------------------
# Licence (optional — without one you get the community edition)
# ---------------------------------------------------------------------------
# Leave this empty and the instance runs the COMMUNITY EDITION: 1 organization, 3 users, 5 agents,
# 10 assets, no expiry and nothing to renew. Every feature works inside
# those numbers. A full limit refuses the NEXT thing of that kind and touches nothing already there.
#
# A key raises the limits — ask antoine@luxit.be for one. It is verified offline, on this machine. A
# licensed instance then reports counts and its own address to your provider; the README lists every
# field it sends. A key that expires or is withdrawn drops back to the allowances above, never to
# nothing.
#
# This value SEEDS the database on first boot and does not govern it afterwards: a renewal arrives
# through the check-in and is stored, so leaving an old key here cannot roll you back. To replace a
# key, use Settings → Licence.
#JARVIS_LICENSE_KEY=