Files
landing-api/.env.production.example
Josep Piguillem 748b485c9b
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
Tests / test-postgres (push) Has been cancelled
Tests / mcp (push) Has been cancelled
Add an MCP server for the API, deployable next to the application
mcp/ exposes both APIs as MCP tools, one per operation in openapi.yaml,
named after the Laravel route each lands on (public_projects_index,
admin_pages_update, ...). Nothing is written by hand: the tools are
derived from the document the target application serves, which the
contract tests already hold to the truth.

Two transports over one core:

- stdio (`node mcp/server.mjs`), registered in .mcp.json, signing in
  with credentials from the environment and refreshing the token.
- Streamable HTTP (`--http`), stateless, deployed as the landing-mcp
  service. It holds no credentials: each request brings its caller's
  Pireon Basic credentials or bearer token, so the API applies that
  person's permissions. Sessions live in memory keyed by a hash,
  refused credentials are remembered for a minute, and the spec is
  reloaded every minute so new endpoints appear without a redeploy.

Left out on purpose: the token exchange, telemetry, and the contact and
apply forms, which notify real people. The preview secret is never sent
for anonymous callers, and is unset in the deployment by default.

A CI job runs the Node tests, including the HTTP mode end to end
against a fake application.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:58:14 +02:00

141 lines
5.1 KiB
Plaintext

###############################################################################
# Production environment for the Docker stack.
#
# cp .env.production.example .env.production
# # fill in at least DB_PASSWORD below — docker-compose.yml refuses to
# # interpolate at all without it, even for the next command
# docker compose --env-file .env.production run --rm --no-deps landing-app php artisan key:generate --show
#
# Everything the containers need is here; docker-compose.yml sets the database,
# cache, session and queue drivers itself so they cannot drift.
#
# This file only matters for a manual deploy. A platform that injects its own
# "Environment Variables" (Dokploy, Coolify, ...) exports them as real process
# environment variables instead, which docker-compose.yml's `${VAR}`
# interpolation reads directly — no file needed on the server at all.
###############################################################################
APP_NAME="Pireon Admin"
APP_ENV=production
APP_DEBUG=false
# Required. Without it sessions cannot be decrypted and the signed preview
# links cannot be verified — the entrypoint refuses to start rather than
# failing later in a way nobody connects to this.
APP_KEY=
# The public address of the panel itself, https included. It is what the API
# builds absolute image URLs from, so getting it wrong means every image on the
# public site points at the wrong host.
APP_URL=https://admin.example.org
APP_LOCALE=es
APP_FALLBACK_LOCALE=es
LOG_CHANNEL=stack
LOG_STACK=single
LOG_LEVEL=warning
# Port the stack listens on. Put a reverse proxy in front of it and terminate
# TLS there.
APP_PORT=8000
###############################################################################
# Database
###############################################################################
DB_DATABASE=pireon
DB_USERNAME=pireon
# No default on purpose: compose refuses to start without it.
DB_PASSWORD=
###############################################################################
# Pireon OAuth2 identity server
#
# The panel's sign-in form uses the password grant, so the registered client
# must allow it.
###############################################################################
PIREON_SERVER=https://oauth.pireon.dev
PIREON_CLIENT_ID=
PIREON_CLIENT_SECRET=
PIREON_REDIRECT_URI="${APP_URL}/auth/callback"
PIREON_SCOPES=account:read
PIREON_PASSWORD_GRANT=true
PIREON_INTROSPECTION_TTL=60
PIREON_DEFAULT_ROLE=viewer
PIREON_REQUIRE_ACTIVE=true
PIREON_REQUIRE_APPROVED=true
###############################################################################
# Public site (separate, server-rendered application)
###############################################################################
SITE_URL=https://example.org
SITE_PREFIX_DEFAULT_LOCALE=false
SITE_CACHE_SECONDS=60
# Generate with: php -r "echo bin2hex(random_bytes(24)), PHP_EOL;"
SITE_PREVIEW_TOKEN=
# With SSR every visitor's page view arrives from the frontend's address, so
# this budget is for the whole site at once. Add that address here and it
# bypasses the limit entirely.
SITE_RATE_LIMIT=600
SITE_TRUSTED_IPS=
# Shared secret for the 404 reports the frontend sends back. Empty disables
# the endpoint. Generate with: php -r "echo bin2hex(random_bytes(24)), PHP_EOL;"
SITE_INGEST_TOKEN=
SITE_TELEMETRY_RATE_LIMIT=60
SITE_TELEMETRY_MAX_BATCH=50
SITE_ACCEPT_SUBMISSIONS=true
SITE_HONEYPOT_FIELD=website
SITE_SUBMISSION_RATE_LIMIT=3
SITE_SUBMISSION_HOURLY_LIMIT=60
SITE_SUBMISSION_DEDUPE_MINUTES=1440
SITE_SUBMISSION_MAIL=false
###############################################################################
# Images
#
# `public` is right for this stack: the storage volume is mounted in the web
# container and Caddy serves /storage/* straight off disk without touching PHP.
###############################################################################
MEDIA_DISK=public
MEDIA_MAX_PIXELS=50000000
###############################################################################
# Git repositories
###############################################################################
GIT_PROVIDER=gitea
GITEA_URL=
GITEA_TOKEN=
GITEA_ORG=
GIT_SYNC_CRON=hourly
###############################################################################
# Mail — only needed if SITE_SUBMISSION_MAIL is true
###############################################################################
MAIL_MAILER=log
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM_ADDRESS="admin@example.org"
MAIL_FROM_NAME="${APP_NAME}"
###############################################################################
# MCP server (landing-mcp)
#
# Holds no credentials: every caller signs in with their own. Its domain is
# added in Dokploy's "Domains" tab, pointing at port 3000.
###############################################################################
# "true" refuses callers without credentials instead of offering them the
# public API — which is already public, so the default is to allow it.
LANDING_MCP_REQUIRE_AUTH=false
# Empty on purpose. Set to SITE_PREVIEW_TOKEN's value and anyone who can sign
# in sees every draft through the public tools, whatever their role.
LANDING_MCP_PREVIEW_TOKEN=