# 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: #JARVIS_IMAGE_WEB=git.luxit.be/luxit/jarvis-web: # 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: # --------------------------------------------------------------------------- # 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= # Whether an instance WITHOUT a licence reports to your provider. On unless you say otherwise. # # It sends the same fields a licensed instance does — counts, the version it runs, its contract # checksum and its public address — to checkin.luxit.be. No names, no conversation content, nothing # about what you administer. The README lists every field. # # Set it to off and nothing leaves this network at all. You keep every community allowance and every # feature; what stops working is the licence request form in Settings → Licence, which needs the same # channel. Accepted values for off: off, false, 0, no. # # A LICENSED instance reports regardless — that is part of what a licence is, and this variable does # not change it. # NOTE, 24 August 2026: this line did nothing before that date, and we are sorry. Compose reads # this file to fill in placeholders INSIDE the compose file — it does not hand it to the services — # and the api service never listed this variable, so the API saw nothing and applied its default of # "on". An instance that had opted out reported anyway. Pull the repository again and # `docker compose up -d`: the fix is in the compose file, not in the images. # # Do not trust this file to tell you whether it took. Ask the instance: # docker compose exec api printenv JARVIS_TELEMETRY # Settings -> Licence answers the same question from what the API actually resolved. #JARVIS_TELEMETRY=on