# The secrets an operator writes. Minted ONCE, mounted by every pod. # # ─── READ THIS BEFORE YOU GENERATE ANYTHING ─────────────────────────────────────────────────── # # There are two Secrets, and the split is not filing: `jarvis-generated` holds the values that are # random bytes and `jarvis-secrets` — this file — holds the ones that are facts about your estate. # One of those can be regenerated safely and the other cannot, so they do not share an object. # # The three keys below are here as the MANUAL path. If you would rather have them minted for you, # apply `01-secret-job.yaml` instead and leave the `VAULT_*`/`JWT_*` lines out of this file # entirely. Do not do both: `04-api.yaml` reads this Secret last on purpose, so a value typed here # wins over a generated one, and the two quietly disagreeing is the shape of the accident. # # `jarvis init-secrets` is the Compose generator, and it must NEVER become an initContainer # here. Its guarantee that a key is minted exactly once is every writer sharing one filesystem. # Per-pod, on an `emptyDir`, each replica generates its own — and the consequence is not a crash. # Pod A seals a vault credential under a key pod B does not have; pod B reports that credential as # corrupt; both pods log nothing at boot, because from each one's point of view everything is fine. # By the time anybody notices, there are several keys in circulation and no way to tell which # entries belong to which. `jarvis mint-k8s-secret` is the same one-shot done correctly, with # the API server's 409 standing in for the filesystem's exclusion. # # Either way, keep the output. The vault key in particular is the thing every stored credential is # encrypted under — a database backup that travelled with its own key would be a backup that # decrypts itself, which is why this lives apart from the database. # # VAULT_MASTER_KEY openssl rand -base64 32 # JWT_ACCESS_SECRET openssl rand -base64 48 # JWT_REFRESH_SECRET openssl rand -base64 48 # # In anything beyond a trial, do not commit this file with values in it. Use SealedSecrets, # ExternalSecrets, or `kubectl create secret generic` from a password manager. # ────────────────────────────────────────────────────────────────────────────────────────────── apiVersion: v1 kind: Secret metadata: name: jarvis-secrets namespace: jarvis type: Opaque stringData: # Everything in the vault is sealed under this. Lose it and the credentials are unreadable; # leak it and a database dump is enough to read them all. VAULT_MASTER_KEY: "REPLACE_ME" JWT_ACCESS_SECRET: "REPLACE_ME" JWT_REFRESH_SECRET: "REPLACE_ME" # WHEN THE VAULT KEY WAS CREATED, bound to that key. Manual path only: if `01-secret-job.yaml` # mints your key, delete this line with the other `VAULT_*` ones — left here, even as # "REPLACE_ME", it overrides the value the Job wrote beside its key. # # The value is the moment you generate the key and the key's fingerprint, one space apart: # # FP=$(printf %s "$VAULT_MASTER_KEY" | sha256sum | cut -c1-16) # echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) sha256:$FP" # # Restoring a key you backed up? Restore the value saved with it, word for word, and never compute # a new one: today's date bound to an old key is the one mistake this line can make dangerous. A # date without the fingerprint, or bound to another key, is ignored. # # On Compose this is inferred from the key file's modification time. Here there is no file: the # api receives the key through `envFrom`, so this line is the only evidence of the key's age. # (Mounting the Secret as a file would not do better — the kubelet writes it into every pod at # start, so its mtime is the POD's age.) # # What that number decides is narrow and serious. An instance with no accounts can have its first # super-admin claimed by whoever reaches the install screen — unless the vault key is old, which # means the database has gone missing from an instance that already existed (a bad restore, a PVC # pointed at the wrong volume) and the claim needs proof of possession instead. Without this line # the age is unknown and the claim always asks for the key, which costs you nothing but its last # characters. VAULT_KEY_CREATED_AT: "REPLACE_ME" # `connection_limit` is not optional here, and it is the reason this URL is in the Secret rather # than assembled from parts. The api's pool is per-process: N pods times its default will # exhaust `max_connections` long before the application is under any real load. Size it as # (max_connections − reserve) ÷ (replicas + maxSurge + jobs). DATABASE_URL: "postgresql://jarvis:REPLACE_ME@postgres.jarvis.svc.cluster.local:5432/jarvis?schema=public&connection_limit=10&pool_timeout=20" # Required to create new organizations, users or agents. Without it an instance keeps running # everything it already has and creates nothing new. See the project README. JARVIS_LICENSE_KEY: "" OPENAI_API_KEY: "REPLACE_ME"