265 lines
13 KiB
Bash
265 lines
13 KiB
Bash
# ── CluPilot .env template ───────────────────────────────────────────────
|
|
# Copy to .env, then: docker compose up -d && clupilot artisan key:generate
|
|
# Shared by Laravel AND docker-compose. No ${} interpolation (compose passes
|
|
# it literally). Values below are DEV defaults — set strong secrets for real
|
|
# deployments and NEVER commit the resulting .env.
|
|
|
|
APP_NAME=CluPilot
|
|
APP_ENV=local
|
|
APP_KEY=
|
|
APP_DEBUG=true
|
|
APP_URL=http://localhost
|
|
|
|
APP_LOCALE=de
|
|
APP_FALLBACK_LOCALE=en
|
|
APP_FAKER_LOCALE=de_DE
|
|
|
|
APP_MAINTENANCE_DRIVER=file
|
|
BCRYPT_ROUNDS=12
|
|
|
|
LOG_CHANNEL=stack
|
|
LOG_STACK=single
|
|
LOG_DEPRECATIONS_CHANNEL=null
|
|
LOG_LEVEL=debug
|
|
|
|
# ── Database (MariaDB service) ───────────────────────────────────────────
|
|
# Dev defaults so `docker compose up` boots out of the box. Override for prod.
|
|
DB_CONNECTION=mariadb
|
|
DB_HOST=mariadb
|
|
DB_PORT=3306
|
|
DB_DATABASE=clupilot
|
|
DB_USERNAME=clupilot
|
|
DB_PASSWORD=clupilot
|
|
DB_ROOT_PASSWORD=clupilot_root
|
|
|
|
# ── Redis (cache/session/queue) ──────────────────────────────────────────
|
|
REDIS_CLIENT=phpredis
|
|
REDIS_HOST=redis
|
|
REDIS_PASSWORD=null
|
|
REDIS_PORT=6379
|
|
|
|
SESSION_DRIVER=database
|
|
SESSION_LIFETIME=120
|
|
SESSION_ENCRYPT=false
|
|
SESSION_PATH=/
|
|
SESSION_DOMAIN=null
|
|
|
|
CACHE_STORE=redis
|
|
QUEUE_CONNECTION=redis
|
|
FILESYSTEM_DISK=local
|
|
|
|
# ── Broadcasting / Reverb ────────────────────────────────────────────────
|
|
BROADCAST_CONNECTION=reverb
|
|
REVERB_APP_ID=clupilot
|
|
REVERB_APP_KEY=change-me-app-key
|
|
REVERB_APP_SECRET=change-me-app-secret
|
|
# server-side: internal compose service name
|
|
REVERB_HOST=reverb
|
|
REVERB_PORT=8080
|
|
REVERB_SCHEME=http
|
|
REVERB_SERVER_HOST=0.0.0.0
|
|
REVERB_SERVER_PORT=8080
|
|
|
|
# browser-side (baked into built assets): the address a browser can reach
|
|
VITE_REVERB_APP_KEY=change-me-app-key
|
|
VITE_REVERB_HOST=localhost
|
|
VITE_REVERB_PORT=443
|
|
VITE_REVERB_SCHEME=https
|
|
VITE_APP_NAME=CluPilot
|
|
|
|
# Vite HMR host the browser connects to (dev). Set to the reachable VM address
|
|
# when developing against a remote host; localhost for local-only.
|
|
VITE_HMR_HOST=localhost
|
|
|
|
# ── Mail ─────────────────────────────────────────────────────────────────
|
|
# DEV: `log` writes mails to storage/logs (nothing is sent).
|
|
# PROD: switch to smtp and fill in the credentials below.
|
|
MAIL_MAILER=log
|
|
MAIL_FROM_ADDRESS=hello@clupilot.local
|
|
MAIL_FROM_NAME=CluPilot
|
|
# MAIL_MAILER=smtp
|
|
# MAIL_HOST=smtp.your-provider.tld
|
|
# MAIL_PORT=587
|
|
# MAIL_USERNAME=
|
|
# MAIL_PASSWORD=
|
|
# MAIL_SCHEME=smtp # smtp | smtps (465)
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# CluPilot operations — fill these in to run real provisioning.
|
|
# Everything below is OPTIONAL for local dev: left blank, the services are
|
|
# no-ops/fakes and the app + test suite still run. Only real infrastructure
|
|
# work (host onboarding, customer provisioning) needs them.
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
# ── Stripe (payments) ────────────────────────────────────────────────────
|
|
# WEBHOOK_SECRET is READ BY CODE today (signature check on /webhooks/stripe).
|
|
# Outside local/testing a missing secret makes the webhook fail closed.
|
|
# Stripe hands out a SEPARATE signing secret per operating mode (test/live) —
|
|
# fill in the one matching each. Which one applies follows platform.mode
|
|
# (App\Support\StripeWebhookSecret), not this file.
|
|
STRIPE_WEBHOOK_SECRET=
|
|
STRIPE_WEBHOOK_SECRET_TEST=
|
|
# Not read yet — needed once the Checkout flow is built (currently the portal
|
|
# only records a purchase intent):
|
|
STRIPE_KEY=
|
|
STRIPE_SECRET=
|
|
|
|
# ── DNS (Hetzner DNS API) ────────────────────────────────────────────────
|
|
# Creates <subdomain>.<zone> A-records for each customer instance.
|
|
HETZNER_DNS_TOKEN=
|
|
CLUPILOT_DNS_ZONE=clupilot.cloud
|
|
|
|
# Host names (fsn-01.node.…) are NOT published here — that would leak the
|
|
# internal WireGuard subnet. They go into this directory instead, which the
|
|
# vpn-dns container reads (dnsmasq --hostsdir) over the dns-hosts volume; see
|
|
# docker-compose.yml. Only change if that volume is mounted somewhere else.
|
|
CLUPILOT_DNS_HOSTS_DIR=/etc/clupilot/dns-hosts
|
|
|
|
# ── Traefik (reverse proxy + TLS) ────────────────────────────────────────
|
|
# Directory ON THE PROXMOX HOST where CluPilot writes dynamic route files.
|
|
TRAEFIK_DYNAMIC_PATH=/etc/traefik/dynamic
|
|
|
|
# ── WireGuard hub (this CluPilot VM) ─────────────────────────────────────
|
|
# Hosts join this hub during onboarding; CluPilot reaches them over the tunnel.
|
|
CLUPILOT_WG_SUBNET=10.66.0.0/24
|
|
CLUPILOT_WG_HUB_IP=10.66.0.1
|
|
CLUPILOT_WG_ENDPOINT= # public host:port peers dial, e.g. vpn.clupilot.com:51820
|
|
CLUPILOT_WG_HUB_PUBKEY= # `wg pubkey < /etc/wireguard/privatekey`
|
|
CLUPILOT_WG_CONFIG_PATH=/etc/wireguard/wg0.conf
|
|
|
|
# ── SSH identity for host onboarding ─────────────────────────────────────
|
|
# Deployed to each fresh server after the one-time root password login.
|
|
# PREFERRED: point at files (a multi-line PEM cannot live in .env).
|
|
# ssh-keygen -t ed25519 -N '' -C clupilot -f storage/app/ssh/clupilot
|
|
CLUPILOT_SSH_PUBLIC_KEY_PATH=
|
|
CLUPILOT_SSH_PRIVATE_KEY_PATH=
|
|
# Alternative (single-line values only):
|
|
CLUPILOT_SSH_PUBLIC_KEY=
|
|
CLUPILOT_SSH_PRIVATE_KEY=
|
|
CLUPILOT_SSH_COMMAND_TIMEOUT=2000
|
|
|
|
# ── Monitoring ───────────────────────────────────────────────────────────
|
|
# The built-in client speaks a GENERIC REST API:
|
|
# GET/POST /monitors, GET/DELETE /monitors/{id}
|
|
# For Uptime Kuma use the bundled bridge below (Kuma's own REST API is
|
|
# read-only — monitor CRUD goes through Socket.IO):
|
|
# MONITORING_API_URL=http://kuma-bridge:8080
|
|
# Leave blank to disable monitoring entirely: provisioning records a stable
|
|
# breadcrumb and its own health checks (occ status, TLS, admin user) still
|
|
# gate acceptance.
|
|
MONITORING_API_URL=
|
|
MONITORING_API_TOKEN=
|
|
|
|
# ── Uptime Kuma (über die mitgelieferte Bridge) ───────────────────────────
|
|
# Kuma kann Monitore nur über Socket.IO anlegen, daher die Bridge:
|
|
# docker compose --profile monitoring up -d --build kuma-bridge
|
|
# Danach hier MONITORING_API_URL auf die Bridge zeigen lassen:
|
|
# MONITORING_API_URL=http://kuma-bridge:8080
|
|
# MONITORING_API_TOKEN ist gleichzeitig das Bridge-Token (frei wählbar, lang).
|
|
KUMA_URL=
|
|
KUMA_USERNAME=
|
|
KUMA_PASSWORD=
|
|
KUMA_TOTP=
|
|
|
|
# Monitoring-Verhalten bei Ausfall:
|
|
# MONITORING_REQUIRED=false -> Bereitstellung läuft weiter (Warn-Event), Standard
|
|
# MONITORING_REQUIRED=true -> Lauf schlägt fehl, wenn Monitoring nicht erreichbar
|
|
MONITORING_REQUIRED=false
|
|
MONITORING_ATTEMPTS=2
|
|
|
|
# ── VPN-Konfigurationen ──────────────────────────────────────────────────
|
|
# Schlüssel für hier gespeicherte WireGuard-Konfigurationen (32 Byte, base64).
|
|
# Bewusst getrennt von APP_KEY. Leer = Speichern deaktiviert.
|
|
# Erzeugen: head -c 32 /dev/urandom | base64
|
|
VPN_CONFIG_KEY=
|
|
|
|
# ── Zugangsdaten der Konsole (Stripe, DNS, Monitoring, Postfächer) ────────
|
|
# Eigener Schlüssel, ebenfalls getrennt von APP_KEY — der wird routinemäßig
|
|
# gewechselt und würde sonst jedes gespeicherte Geheimnis unlesbar machen.
|
|
# LEER = die Konsole speichert gar keine Zugangsdaten und sagt das auch.
|
|
# Erzeugen: head -c 32 /dev/urandom | base64
|
|
SECRETS_KEY=
|
|
|
|
# Netze, die als "wir" gelten, solange die Website versteckt ist
|
|
# (WireGuard-Subnetz). Alles andere sieht die Platzhalterseite.
|
|
TRUSTED_RANGES=10.66.0.0/24,127.0.0.1
|
|
|
|
# Umsatzsteuersatz des Verkäufers in Prozent. Preise in der Konfiguration
|
|
# sind NETTO; die Oberfläche weist beides aus.
|
|
CLUPILOT_TAX_PERCENT=20
|
|
|
|
# ── Wo die Konsole antwortet ─────────────────────────────────────────────
|
|
#
|
|
# ADMIN_HOSTS: Hostnamen, unter denen die Konsole erreichbar ist. Der ERSTE
|
|
# ist der kanonische — für ihn werden URLs erzeugt; die weiteren sind
|
|
# Ausweichadressen, über die jemand hereinkommt, der ausgesperrt ist.
|
|
#
|
|
# ADMIN_HOST_EXCLUSIVE: darf auf diesen Namen NUR die Konsole antworten?
|
|
#
|
|
# true Die Konsole liegt an der WURZEL ihres Hostnamens
|
|
# (https://admin.…/), und Portal wie Website antworten dort mit 404.
|
|
# Dann gehört hier ausschliesslich der Konsolen-Name hinein — eine
|
|
# IP oder localhost mit aufzunehmen schaltet Portal und Website auf
|
|
# genau dieser Adresse ab. Das ist die Betriebsform.
|
|
#
|
|
# false Die Konsole liegt unter /admin auf jedem gelisteten Namen, und
|
|
# diese Namen bedienen weiterhin auch Portal und Website. Nur für
|
|
# eine Maschine ohne eigene DNS-Namen sinnvoll.
|
|
#
|
|
# Ohne Eintrag gilt false — dann zeigt https://admin.…/ das PORTAL, nicht die
|
|
# Konsole, und das sieht wie ein Fehler aus, obwohl es die Vorgabe ist.
|
|
#
|
|
# APP_HOST: Hostname des Kundenportals, z. B. app.clupilot.com. Gesetzt, ist
|
|
# JEDE Portal-Route an diesen Host gebunden und existiert nirgends sonst —
|
|
# www.clupilot.com/dashboard ist dann ein 404, kein funktionierender Aufruf.
|
|
# Leer = Portal antwortet ueberall (Vorgabe, und was jede dev-Maschine ueber
|
|
# eine blanke IP braucht).
|
|
APP_HOST=
|
|
|
|
# SITE_HOST: Hostname der oeffentlichen Website. Gesetzt, antwortet die
|
|
# Startseite NUR dort — jeder andere Host (das Portal, eine blanke IP) zeigt
|
|
# unter "/" die Anmeldung bzw. das Dashboard. Leer heisst: Startseite ueberall,
|
|
# Mehrere Namen kommagetrennt, der ERSTE ist der kanonische — er liefert aus,
|
|
# alle weiteren leiten dauerhaft dorthin um (Pfad und Query bleiben erhalten):
|
|
# SITE_HOST=www.clupilot.com,clupilot.com
|
|
# und dann liefert app.clupilot.com die Website aus. Gebunden sind Startseite,
|
|
# robots.txt und die Legal-Seiten — dadurch erzeugt route('legal.impressum')
|
|
# auch aus einer Mail heraus eine www-Adresse.
|
|
SITE_HOST=
|
|
|
|
ADMIN_HOSTS=admin.dev.clupilot.com
|
|
ADMIN_HOST_EXCLUSIVE=true
|
|
|
|
# ── Nextcloud blueprint template ─────────────────────────────────────────
|
|
# NOT an env var: set the Proxmox template VMID per plan in
|
|
# config/provisioning.php → plans.*.template_vmid (default 9000).
|
|
|
|
# ── docker-compose knobs (host-side) ─────────────────────────────────────
|
|
HOST_UID=1000
|
|
HOST_GID=1000
|
|
# Behind a reverse proxy — which is every production install — these must stay
|
|
# on loopback: Docker publishes ports ahead of UFW, so anything on 0.0.0.0 is
|
|
# reachable from the internet regardless of the firewall, and reaching a backend
|
|
# directly skips every hostname and address rule the proxy enforces.
|
|
# For local development without a proxy, set APP_PORT=80.
|
|
APP_PORT=127.0.0.1:8080
|
|
# Vite dev server (HMR). Off by default: over the HTTPS domains the
|
|
# browser cannot load assets from http://<ip>:5173. Set true only when
|
|
# working over http://10.10.90.185, then run: docker compose up -d app
|
|
VITE_AUTOSTART=false
|
|
VITE_PORT=5173
|
|
REVERB_HOST_PORT=127.0.0.1:8081
|
|
DB_HOST_PORT=3306
|
|
|
|
# ── CI (Gitea Actions) ──────────────────────────────────────────────────────
|
|
# Registrierungs-Token aus Gitea: Repo → Settings → Actions → Runners →
|
|
# "Create new runner". Danach: docker compose --profile ci up -d runner
|
|
GITEA_INSTANCE_URL=https://git.bave.dev
|
|
GITEA_RUNNER_TOKEN=
|
|
GITEA_RUNNER_NAME=clupilot-local
|
|
# Passend zur Server-Version halten (1.20 verträgt keinen aktuellen Runner).
|
|
GITEA_RUNNER_VERSION=0.2.6
|
|
|
|
# Zone in der Zeiten ANGEZEIGT werden. Gespeichert wird immer UTC.
|
|
APP_DISPLAY_TIMEZONE=Europe/Vienna
|