# ── 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=redis 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_WEBHOOK_SECRET= # 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 . A-records for each customer instance. HETZNER_DNS_TOKEN= CLUPILOT_DNS_ZONE=clupilot.com # ── 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 # ── Admin-Konsole: erlaubte Hostnamen ──────────────────────────────────── # Die Proxmox-Hosts UND das Admin-Dashboard dürfen nie öffentlich erreichbar # sein. Primär regelt das dein Reverse Proxy (nur www/app/ws/api öffentlich); # dies ist die zweite Verteidigungslinie IN der App: auf jedem anderen Host # antwortet /admin mit 404 (nicht 403 — verrät nicht, dass es die Konsole gibt). # Komma-getrennt. LEER = keine Einschränkung (aktueller Dev-Stand). # Beispiel: # Schluessel fuer gespeicherte VPN-Konfigurationen (32 Byte, base64). # Bewusst getrennt von APP_KEY. Leer = Speichern deaktiviert. # Erzeugen: head -c 32 /dev/urandom | base64 VPN_CONFIG_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. 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://: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