chore(config): documented env placeholders + SSH keys from file paths

- .env.example gains a full, commented CluPilot operations block (Stripe, Hetzner
  DNS, Traefik, WireGuard hub, SSH identity, monitoring, prod mail) with a note
  that everything is optional for local dev
- config/provisioning.php: CLUPILOT_SSH_{PUBLIC,PRIVATE}_KEY_PATH read the key
  from a file (a multi-line PEM cannot live in .env), falling back to inline vars
- documents that Uptime Kuma's REST API is read-only (monitor CRUD is Socket.IO),
  so it needs a bridge rather than being drop-in for the generic REST client

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 19:37:25 +02:00
parent 32fa1028cc
commit f06f00c428
3 changed files with 86 additions and 3 deletions

BIN
..env.swp Normal file

Binary file not shown.

View File

@ -71,10 +71,77 @@ VITE_APP_NAME=CluPilot
# when developing against a remote host; localhost for local-only.
VITE_HMR_HOST=localhost
# ── Mail (dev → log) ─────────────────────────────────────────────────────
# ── 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=tls # tls | 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 <subdomain>.<zone> 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}
# Uptime Kuma's own REST API is READ-ONLY (badges/status page/push) — creating
# monitors goes through Socket.IO, so Kuma needs a small REST bridge in front
# of it (or a dedicated client). Leave blank to disable monitoring entirely:
# provisioning then records a stable breadcrumb and its own health checks
# (occ status, TLS, admin user) still gate acceptance.
MONITORING_API_URL=
MONITORING_API_TOKEN=
# ── 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

View File

@ -8,6 +8,21 @@ $wgSubnet = env('CLUPILOT_WG_SUBNET', '10.66.0.0/24');
$wgBase = ip2long($wgNetwork) & (0xFFFFFFFF << (32 - (int) $wgBits)) & 0xFFFFFFFF;
$wgHubDefault = long2ip($wgBase + 1); // first usable address in the CIDR
/**
* Read a secret from a FILE path if given, else from the inline env var.
* SSH keys are multi-line PEM, which a plain .env cannot hold pointing at a
* file keeps the key out of .env entirely (and off the environment).
* Note: after `config:cache`, changing the file needs `php artisan config:clear`.
*/
$secretFrom = function (string $pathVar, string $inlineVar): string {
$path = (string) env($pathVar, '');
if ($path !== '' && is_readable($path)) {
return (string) file_get_contents($path);
}
return (string) env($inlineVar, '');
};
return [
/*
| Ordered step classes per pipeline. ::class is textual listing steps that
@ -104,8 +119,9 @@ return [
// SSH identity CluPilot deploys to each host after first password login.
'ssh' => [
'public_key' => env('CLUPILOT_SSH_PUBLIC_KEY', ''),
'private_key' => env('CLUPILOT_SSH_PRIVATE_KEY', ''),
// Prefer *_PATH (a file on disk); fall back to the inline env value.
'public_key' => $secretFrom('CLUPILOT_SSH_PUBLIC_KEY_PATH', 'CLUPILOT_SSH_PUBLIC_KEY'),
'private_key' => $secretFrom('CLUPILOT_SSH_PRIVATE_KEY_PATH', 'CLUPILOT_SSH_PRIVATE_KEY'),
// Per-command SSH timeout; below the job timeout (2100s) but far above
// the phpseclib ~10s default so apt full-upgrade etc. can finish.
'command_timeout' => (int) env('CLUPILOT_SSH_COMMAND_TIMEOUT', 2000),