Files
pad/.env.example
T
xarmian 5d641b70d0 docs: MCP over HTTP on a self-hosted install (TASK-3304, PLAN-2310 U7) (#1669)
* docs: MCP over HTTP on a self-hosted install (TASK-3304, PLAN-2310 U7)

docs/deployment.md gains "MCP for agents (self-hosted)": off by default;
the origin (PAD_URL / toml url / --url, else PUBLIC_URL, with no
host:port fallback), PAD_MCP_PUBLIC_URL, PAD_AUTH_SERVER_URL and
PAD_MCP_ENABLED with their precedence and startup-time nature; the admin
toggle (GET/PUT /api/v1/admin/mcp, the env lock, the readiness readout);
https means OAuth plus tokens and http tokens only; the reverse-proxy Host
requirement and PAD_TRUSTED_PROXIES; and the known gaps. .env.example,
CLAUDE.md's API and MCP sections, and README follow.

Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz

* docs: .env.example says MCP's toggle and origin are self-host rules (TASK-3304, codex r1)

Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz

* docs: the MCP host check and rate limits as merged (TASK-3304, PLAN-2310 U7)

Checked against U3 (#1665) and U4 (#1667) on main:
- The 421: honours X-Forwarded-Host only from a PAD_TRUSTED_PROXIES
  peer; the body names the received host, its header and the configured
  origin; the first refusal per host is a rate-limited WARN; it runs
  before auth and after the availability gate.
- The DR-9 limits as a table. Claim redemption is keyed per signed-in
  user, not per address, as the earlier draft said.
- Pre-auth /mcp refusals are counted in pad_mcp_preauth_denied_total
  with a WARN per address, not audit rows. The once-per-process WARN
  when a proxied request resolves inside PAD_TRUSTED_PROXIES.

Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz

* docs: the console toggle and the pre-auth counter's scope (TASK-3304, codex r2)

- The console now has the MCP section (#1666): Admin → Settings → MCP for
  agents. The "console does not show it yet" sentence is gone, and
  .env.example names both ways to toggle.
- codex r2: pad_mcp_preauth_denied_total counts missing/invalid-token
  refusals and the pre-auth 429s, not the 421 host refusals, which
  happen before it.

Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz

* docs: OAuth follows the issuer URL; tokens keep their scopes (TASK-3304, codex r3)

Claude-Session: https://claude.ai/code/session_01NidgPePY7UvHcEJCMkD2Qz
2026-09-29 17:29:55 -04:00

198 lines
10 KiB
Bash

# Pad — Docker Compose environment example
#
# Copy to .env, fill in values, then run `docker compose up -d`.
#
# If you don't already have a POSTGRES_PASSWORD, generate one with:
# openssl rand -base64 32
# …and paste it below. Docker Compose WILL refuse to start with the password
# missing; this is deliberate so a fresh deploy can never inherit a known-weak
# default credential.
# REQUIRED — PostgreSQL password for the `pad` user.
POSTGRES_PASSWORD=
# REQUIRED on Postgres — AES-256 encryption key for TOTP seeds and other
# sensitive fields. 64-character hex string (32 bytes). Pad will auto-
# generate one on SQLite deployments but refuses to on Postgres because
# multi-replica deployments would generate divergent keys and fail cross-
# instance decryption. Generate with:
# openssl rand -hex 32
PAD_ENCRYPTION_KEY=
# OPTIONAL — Which host interface the Pad web UI publishes to.
# Default (loopback only) is the safest choice on a fresh install, because
# the bootstrap endpoint is reachable until the first admin is created.
# 127.0.0.1 → localhost only (default)
# 0.0.0.0 → every interface (LAN + internet if no firewall)
# 10.0.0.5 → bind to a specific LAN IP
# PAD_BIND_ADDR=127.0.0.1
# OPTIONAL — Container UID/GID for the pad process (Docker only).
# Default: 1000/1000 with docker-compose; 99/100 with raw `docker run`
# (Unraid `nobody:users` convention). Set these to match your host
# volume's ownership to avoid permission errors on /data writes.
# Must be POSITIVE integers — 0 (root) is rejected by the entrypoint.
# See docker-entrypoint.sh.
# PUID=1000
# PGID=1000
# OPTIONAL — Redis auth (docker-compose.prod.yml only).
# If set, Redis starts with `--requirepass` and Pad connects with the password.
# REDIS_PASSWORD=
# OPTIONAL — Pad Cloud sidecar shared secret (cloud mode only).
# Comma-separated list supports rotation for INBOUND calls: pad accepts
# any listed secret when pad-cloud calls pad's admin endpoints.
# PAD_CLOUD_SECRET=
# OPTIONAL — Base URL pad uses to call pad-cloud back (reverse direction,
# e.g. Stripe cancel-customer when a user deletes their account). Required
# in cloud mode to cascade account deletion through to Stripe billing;
# without it, pad deletes local rows but never cancels the subscription.
# PAD_CLOUD_SIDECAR_URL=http://pad-cloud:7778
# OPTIONAL — Exact outbound secret pad sends on reverse calls. Use this
# during a cloud-secret rotation when pad-cloud is still validating against
# the OLD value: pin the outbound here so the reverse call matches, then
# roll pad-cloud, then unpin. When unset, pad falls back to the LAST entry
# of PAD_CLOUD_SECRET (the older rotation value).
# PAD_CLOUD_OUTBOUND_SECRET=
# OPTIONAL — Encryption key for sensitive fields (32-byte hex string).
# If unset, Pad stores 2FA seeds in plaintext — set this for production.
# openssl rand -hex 32
# PAD_ENCRYPTION_KEY=
# OPTIONAL — Trusted reverse-proxy CIDRs. Only peers in these CIDRs may set
# X-Forwarded-For / X-Real-IP. Leave unset for direct-exposed deploys.
# PAD_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12
# OPTIONAL — Bearer token required to scrape /metrics. When unset, /metrics
# is reachable only from loopback (so a Prometheus on the same host keeps
# working without config); when set, every scrape must send
# "Authorization: Bearer $PAD_METRICS_TOKEN".
# openssl rand -hex 32
# PAD_METRICS_TOKEN=
# OPTIONAL — Session IP-change policy.
# Default (unset) logs an ActionSessionIPChanged audit entry when a session
# presents a new client IP and updates the stored IP. "strict" additionally
# destroys the session and forces re-login. Strict mode breaks legitimate
# mobility (mobile roaming, VPN toggles, carrier NAT); enable only for
# high-sensitivity deployments.
# PAD_IP_CHANGE_ENFORCE=strict
# ─────────────────────────────────────────────────────────────────────
# Core server — see docs/deployment.md for full reference.
# ─────────────────────────────────────────────────────────────────────
# Listen address. 0.0.0.0 for containers, 127.0.0.1 for loopback-only.
# PAD_HOST=0.0.0.0
# Listen port.
# PAD_PORT=7777
# Public-facing base URL. Used to build invitation + password-reset links.
# Required on deployments that bind to 0.0.0.0 — without it, emailed links
# point at "http://0.0.0.0:7777" which recipients cannot reach (BUG-899).
# PAD_URL=https://pad.example.com
# Alternative to PAD_URL using the generic env-var convention. Read by the
# server only (does not flip the CLI to remote mode, does not contaminate
# the CLI's BaseURL, and is intentionally NOT persisted to config.toml on
# `pad init` / `pad configure` saves), so it's safe to set on hosts that
# already use PUBLIC_URL for other tooling. Pad-cloud sets it on its
# sidecar and forwards it to the pad service in the same compose.
# Precedence: PAD_URL > PUBLIC_URL > constructed http://host:port.
# PUBLIC_URL=https://pad.example.com
# ── MCP over HTTP for agents (off by default) ───────────────────────────
# See docs/deployment.md, "MCP for agents (self-hosted)". On a self-hosted
# install MCP is served at /mcp only when an admin turns it on AND PAD_URL
# (or PUBLIC_URL) is set: unlike emailed links, MCP never falls back to
# http://host:port. (Pad Cloud always serves it; these settings do not
# apply there.)
# An https issuer URL (PAD_AUTH_SERVER_URL, default the origin) gives OAuth
# plus personal API tokens; an http one gives tokens only. Behind a proxy,
# forward the Host header and set PAD_TRUSTED_PROXIES.
#
# Force MCP on or off and lock the admin toggle (true/false). Unset leaves
# it to the toggle (console: Admin → Settings → MCP for agents, or
# PUT /api/v1/admin/mcp).
# PAD_MCP_ENABLED=
#
# Override the MCP URL clients connect to (default <origin>/mcp). It is
# also the OAuth audience: changing it invalidates issued OAuth tokens.
# PAD_MCP_PUBLIC_URL=https://pad.example.com/mcp
#
# Override the OAuth issuer (default: the origin).
# PAD_AUTH_SERVER_URL=https://pad.example.com
# Writable data directory — SQLite DB, config, logs. Default: ~/.pad
# PAD_DATA_DIR=/data
# Log level: debug | info | warn | error
# PAD_LOG_LEVEL=info
# Operating mode: local | remote | cloud. Controls how the CLI
# discovers the daemon and which credential keying rules apply.
# PAD_MODE=local
# ─────────────────────────────────────────────────────────────────────
# Database — SQLite default; set PAD_DB_DRIVER=postgres for Postgres.
# ─────────────────────────────────────────────────────────────────────
# PAD_DB_DRIVER=postgres
# PAD_DB_PATH=/data/pad.db # SQLite path, ignored on Postgres
# PAD_DATABASE_URL=postgres://pad:secret@postgres:5432/pad?sslmode=disable
# ─────────────────────────────────────────────────────────────────────
# Real-time events — cross-instance fan-out via Redis. Carries three things:
# SSE activity events, watch/push notifications, and the session-presence
# registry that `pad push` and the web UI's agent-session picker read.
# Without this, all three stay in-process (fine for single-node).
# Use a non-evicting maxmemory-policy — see docs/deployment.md.
# ─────────────────────────────────────────────────────────────────────
# PAD_REDIS_URL=redis://redis:6379
# Scopes every Redis key and channel to THIS installation. Leave unset
# unless two Pad installations share one Redis endpoint — unset keeps the
# historical key names, so upgrading changes nothing. Lowercase letters,
# digits, '-' and '_' only; a whitespace-only value is refused at startup
# rather than treated as unset. Changing it on a running deployment is a
# cutover, not a tweak — see docs/deployment.md.
# PAD_REDIS_NAMESPACE=prod
# Streaming connection caps, all PER INSTANCE (no shared counter — a
# three-replica deployment admits 3x these). MAX_CONNECTIONS and
# MAX_PER_USER cover BOTH SSE endpoints; MAX_PER_WORKSPACE covers
# /api/v1/events only. UPGRADING: MAX_CONNECTIONS used to bound only
# /api/v1/events, so a tuned value now bounds more than it did.
# Full detail, including the refusal contract: docs/deployment.md.
# PAD_SSE_MAX_CONNECTIONS=1000
# PAD_SSE_MAX_PER_WORKSPACE=100
# PAD_SSE_MAX_PER_USER=50
# Open collaborative-editing WebSockets per user, per instance (BUG-1308).
# One per open item pane; a dial over it is 429 collab_limit_exceeded.
# PAD_COLLAB_MAX_PER_USER=50
# ─────────────────────────────────────────────────────────────────────
# Security hardening for production behind TLS.
# ─────────────────────────────────────────────────────────────────────
# Set Secure flag on session cookies — requires TLS.
# PAD_SECURE_COOKIES=true
# Comma-separated list of allowed CORS origins. Leave unset to disable CORS.
# PAD_CORS_ORIGINS=https://pad.example.com,https://app.example.com
# ─────────────────────────────────────────────────────────────────────
# Transactional email via Maileroo. Optional — without it, workspace
# invites fall back to CLI-copyable join codes.
# ─────────────────────────────────────────────────────────────────────
# PAD_MAILEROO_API_KEY=your-sending-key
# PAD_EMAIL_FROM=noreply@example.com
# PAD_EMAIL_FROM_NAME=Pad