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>
141 lines
5.1 KiB
Plaintext
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=
|