Files
pig/.env.example
T
claude 99d165b5e5
CI / verify (push) Successful in 4m57s
CI / publish (push) Has been skipped
Rebuild Piggy's interface, and give the demo book a business to describe
Piggy answered in raw markdown, threw away every tool result it streamed,
and fought the reader's scroll on every token. The three surfaces that
made it worth having — what it read, how it reasoned, what it cost — were
all on the wire and none of them reached the screen.

The transcript is now composed of five parts under components/piggy:
answers render through streamdown, the container sticks to the bottom
without pinning the reader there, tool steps say what they read and link
to the record, and each turn carries its model and token count. Three
lifecycle bugs went with them: Stop left a permanent spinner, a truncated
stream was indistinguishable from thinking, and a failed send destroyed
the message it failed to send.

Underneath, the inference path grew timeouts, jittered retries on 429 and
5xx, tolerance of the malformed frames a 30B model emits, and an
agent_runs row per turn so chat spend is observable. The system prompt now
states that a field ending in Cents is cents — without it nemotron renders
costPerGpuHourCents: 189 as "$189 per GPU-hour", which is a 100x error on
the most scrutinised number in the room.

The demo book was arithmetically incoherent: every deal's value
contradicted its own allocation revenue by up to 3.6x, nothing had ever
closed, no customer had any paper, and the marketplace was empty. Deal
value is now derived from the allocation, the book clears 5.3% across five
blocks with one deliberately underwater, and the renewal, compliance and
agent-provenance machinery finally has rows to act on. A --clear that
deleted every obligation, SLA term and capacity request in the database
regardless of origin is scoped to the demo's own ids.

Around that: accounts have a detail page, ⌘K searches the book, Settings
can mint the API keys it always claimed to, and deploy.sh actually ships
the agent instead of silently skipping its compose profile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:34:18 -07:00

250 lines
12 KiB
Bash

# ---------------------------------------------------------------------------
# PIG — environment
# Copy to .env and fill in. Never commit .env.
# ---------------------------------------------------------------------------
# --- Database ---------------------------------------------------------------
# PIG owns this database exclusively. Do not point it at a database shared with
# another application.
DATABASE_URL=postgres://pig:CHANGEME@localhost:5432/pig
#
# Compose only, and REQUIRED there: docker-compose.yml interpolates it with
# `${POSTGRES_PASSWORD:?…}`, so every compose command — including
# `docker compose config` — fails outright until it is set. It is also half of
# the DATABASE_URL compose builds for the containers, which is why running from
# source needs the line above and running in containers needs this one.
# Generate a fresh one; never reuse another service's.
POSTGRES_PASSWORD=CHANGEME
POSTGRES_USER=pig
POSTGRES_DB=pig
# --- Auth (Supabase) --------------------------------------------------------
# PIG uses Supabase for authentication ONLY. It stores no passwords and issues
# no sessions of its own; it verifies incoming JWTs against the project JWKS.
#
# IMPORTANT: authorization does NOT follow from having a Supabase account.
# A user must also have a row in PIG's `users` table. If this Supabase project
# is shared with another application, that application's users get nothing here
# until they are explicitly invited.
SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
SUPABASE_ANON_KEY=
# Service key is only needed for administrative user provisioning. Omit it and
# PIG runs fine in invite-only mode. Treat it as the most powerful secret here.
SUPABASE_SERVICE_KEY=
# --- Auth: on-premises (OIDC) ---------------------------------------------
# Set PIG_OIDC_ISSUER to authenticate against your own identity provider —
# Okta, Entra, Keycloak, Auth0, Authentik, Google Workspace, anything
# standards-compliant. It TAKES PRECEDENCE over the Supabase values above, so
# an on-prem install can leave those in place.
#
# PIG never sees a password. It verifies the token your provider issued and
# reads two things: a stable subject, and an email. Everything else — teams,
# roles, capabilities — is PIG's own data keyed on that subject, so users are
# provisioned in PIG by invite, not by your directory.
PIG_OIDC_ISSUER=
# Optional. Discovered from the issuer's /.well-known/openid-configuration when
# omitted. Set it to skip discovery entirely on an air-gapped network.
PIG_OIDC_JWKS_URI=
# STRONGLY recommended. Without it, a token your provider issued for ANY other
# application in the same tenant is accepted here as a PIG session.
PIG_OIDC_AUDIENCE=
# Comma-separated, in preference order. Defaults to email,preferred_username,upn
# which covers most providers; Entra sometimes needs upn first.
PIG_OIDC_EMAIL_CLAIMS=
# --- Application ------------------------------------------------------------
PIG_PORT=8920
PIG_PUBLIC_URL=http://localhost:8920
NODE_ENV=development
# --- Learn videos, hosted by PIG ---------------------------------------------
# PIG serves its own Learn videos from disk, as a native <video> — no embed
# host, no iframe, and therefore nothing to add to the proxy's frame-src.
#
# PIG_MEDIA_DIR is where the application READS them. Running from source that
# is a path on this machine, relative to the repository root; in the container
# it is always /app/media and docker-compose sets it for you.
PIG_MEDIA_DIR=./media
#
# PIG_MEDIA_HOST_DIR is the HOST directory docker-compose bind-mounts there,
# read-only. Two names for the two sides of the mount, on purpose. It must
# exist before `compose up` — Docker creates a missing bind source as an empty
# root-owned directory, which serves 404s and cannot be written to without
# sudo. On the deployment host this is normally /opt/pig/media.
PIG_MEDIA_HOST_DIR=./media
#
# Filenames are CONTENT-ADDRESSED — `<slug>.<hash>.mp4` — because the files
# themselves are served without authentication while the listing behind
# /api/learn stays code-gated. The hash is what makes a URL unguessable. See
# deploy/README.md, "Learn videos", for the trade this makes and its cost.
# --- Deployment: which image to run -----------------------------------------
# Leave EMPTY to build from the working tree, which is what a development or
# self-hosted-from-source install wants. Set it to a published tag and
# scripts/deploy.sh pulls instead of building, and compose runs exactly that
# image for both the app and Piggy — never one version of each.
#
# Set automatically by scripts/autodeploy.sh; you only put it here to pin a
# specific release by hand.
# PIG_IMAGE=git.karti.ai/pig/pig:release-2026-08-13
PIG_IMAGE=
# The loopback port the app is published on. TLS belongs to the proxy in front.
PIG_HOST_PORT=8920
# Comma-separated emails granted platform-admin rights.
# Every address listed here MUST already have an account. An address listed but
# unregistered is a standing offer of admin to whoever claims it first.
PIG_ADMIN_EMAILS=
# Invite code gating self-serve profile creation. Rotate freely.
PIG_INVITE_CODE=
# Encrypts the credentials an admin types into the settings UI — the Notion and
# Google OAuth secrets in particular, which the API refuses to accept without
# it. Base64-encoded 32 bytes, and NOT interchangeable with any other secret
# here:
#
# openssl rand -base64 32
#
# Rotating it does not re-encrypt what is already stored; anything written
# under the old key has to be entered again.
PIG_SETTINGS_ENCRYPTION_KEY=
# --- Prime Intellect compute API -------------------------------------------
# Used to sync GPU availability into `inventory_listings`.
# Mint a key at https://app.primeintellect.ai/dashboard/tokens with the
# NARROWEST scope that works: `Availability -> Read`. PIG never provisions
# infrastructure and must not hold a key that could. Set an expiry.
PRIME_API_KEY=
PRIME_API_BASE=https://api.primeintellect.ai
# Rate limits are undocumented upstream; the sync backs off empirically.
PRIME_SYNC_ENABLED=false
PRIME_SYNC_INTERVAL_MINUTES=30
# --- Piggy (the in-app agent) ----------------------------------------------
# Piggy drains a leased queue and serves chat on an authenticated internal
# listener. Generate one internal token and give the same value to API + Piggy.
# Never publish the Piggy listener or put this token in a URL.
#
# THREE keys turn the agent on, and all three are required together:
#
# PIGGY_ENABLED=true the API offers the chat surface
# PIGGY_INFERENCE_API_KEY the model credential, held only by Piggy
# PIGGY_INTERNAL_TOKEN 32+ characters, the same value for API and Piggy
#
# (PIGGY_INTERNAL_URL is the fourth thing the API needs, and docker-compose.yml
# already sets it to http://piggy:8931. Only a Piggy running outside Compose
# needs the line below.)
#
# Under Compose that is the whole configuration: scripts/deploy.sh reads
# PIGGY_ENABLED from this file and adds `--profile piggy` to the pull, the
# build, the `up` and the rollback, so the agent ships with the app rather than
# being started by hand and then quietly left on an old image. Everything else
# below has a working default and exists to be lowered.
#
# EVERY Piggy setting is read from this environment ONCE, at Piggy's boot. None
# of it is admin-selectable at runtime: changing the model or a budget means
# editing this file and restarting the container.
PIGGY_ENABLED=false
# Required to turn the agent on. Missing, Piggy exits at boot with
# "PIGGY_INFERENCE_API_KEY is required." and, under `restart: unless-stopped`,
# crash-loops. Mint it at https://app.primeintellect.ai — it is an INFERENCE
# credential and buys tokens, so it is not the same key as PRIME_API_KEY above.
PIGGY_INFERENCE_API_KEY=
# Required to turn the agent on. 32 characters minimum; anything shorter is
# refused at boot rather than accepted as weak.
# openssl rand -hex 32
PIGGY_INTERNAL_TOKEN=
# Where the API reaches the chat server. Under Compose this is set for you to
# http://piggy:8931; set it here only when running Piggy from source.
PIGGY_INTERNAL_URL=http://127.0.0.1:8931
# Model and host. Read by both the API (to display) and Piggy (to call).
PIGGY_MODEL=nvidia/nemotron-3-nano-30b-a3b
PIGGY_INFERENCE_BASE=https://api.pinference.ai/api/v1
PIGGY_CHAT_HOST=127.0.0.1
PIGGY_CHAT_PORT=8931
# Only containers on a private network need this; never combine it with a
# published Piggy port. Compose sets it to true for the container, because the
# API calls Piggy across the Compose network.
PIGGY_CHAT_ALLOW_NON_LOOPBACK=false
# Tuning. COMMENTED OUT ON PURPOSE, and worth understanding before you
# uncomment one: an empty line here is not the same as an absent one. Compose
# passes `PIGGY_MAX_TOKENS=` through as the empty string, which coerces to 0 and
# fails Piggy's positive-integer check at boot. Leave a setting commented to get
# the default from apps/piggy/src/config.ts; give it a value or nothing at all.
#
# PIGGY_LEASE_SECONDS=300 # queue lease, renewed at half the interval
# PIGGY_POLL_INTERVAL_MS=2000 # how often an idle worker looks for a task
# PIGGY_MAX_TOKENS=1024 # per queued task
# PIGGY_CHAT_MAX_TOKENS=2048 # per interactive answer; tools return tables
# PIGGY_MAX_TURNS=4 # model calls per chat turn, tool round trips included
# PIGGY_WORKER_ID= # defaults to hostname:pid; only set it if you run two
#
# Reasoning is off. The default model thinks aloud when asked to, reasoning
# tokens bill like any other, and the chat panel is on every page — so the
# volume is set by how often people type. Raise it to make the UI's reasoning
# panel reachable while chasing a wrong figure, not in normal operation.
# PIGGY_REASONING_EFFORT=none # none | low | medium | high
#
# Model price in CENTS PER MILLION TOKENS, which is what makes the recorded cost
# of a run exact integer arithmetic. These are the published prices of the
# default model and must be changed with it: a stale price is worse than none,
# because it still looks like a measurement.
# PIGGY_PRICE_INPUT_CENTS_PER_MTOK=5
# PIGGY_PRICE_OUTPUT_CENTS_PER_MTOK=20
# --- Deployment: the release poller -----------------------------------------
# Only relevant on a host running scripts/autodeploy.sh. These belong in
# /etc/pig/autodeploy.env (read by the systemd unit), not here — they are
# listed here so the whole deployment surface is in one file to read.
#
# The registry credential is NOT an environment variable. It is a file, mode
# 0600, holding a pull-only token and nothing else:
#
# /etc/pig/registry-token
#
# Mint it in Gitea as a token with `read:package` scope ONLY. A token that can
# write packages, or push to the repository, defeats the point: the reason CI
# cannot deploy to production is that no build-side credential should be able
# to change what production runs, and a write-capable token here reintroduces
# exactly that from the other end.
#
# PIG_REGISTRY_USER=pig-deploy # the Gitea user that owns the token
# PIG_REGISTRY=git.karti.ai
# PIG_IMAGE_REPO=pig/pig # Gitea lowercases the owner
# PIG_REGISTRY_TOKEN_FILE=/etc/pig/registry-token
# PIG_REPO_DIR=/opt/pig
# PIG_RELEASE_TAG_PREFIX=release-
#
# The public origin deploy.sh checks AFTER the container is healthy, to catch a
# proxy that is answering 200 with an empty body. Defaults to PIG_PUBLIC_URL
# above, then to the production origin.
# PIG_DEPLOY_PUBLIC_URL=https://primeintellectgrowth.com
# A string the real application always renders. Change it only if index.html's
# mount point changes.
# PIG_DEPLOY_PUBLIC_MARKER=<div id="root">
# --- Slack ------------------------------------------------------------------
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_APP_TOKEN=
# --- Buzz (https://github.com/block/buzz) -----------------------------------
# Buzz agents reach PIG through the MCP server, so no PIG-specific credential is
# required. These are only for PIG pushing notifications into a Buzz relay.
BUZZ_RELAY_URL=
NOTION_CLIENT_ID=
NOTION_CLIENT_SECRET=
NOTION_REDIRECT_URI=http://localhost:8920/api/imports/notion/oauth/callback
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Must use the PIG_PUBLIC_URL origin and exact /oauth/google/callback path.
GOOGLE_REDIRECT_URI=http://localhost:8920/oauth/google/callback
BUZZ_PRIVATE_KEY=
# Optional NIP-OA owner attestation JSON for an agent identity.
BUZZ_AUTH_TAG=