Commit Graph

4 Commits (dac84f024a0d3c5620794dc26427afaa9a44efdb)

Author SHA1 Message Date
nexxo 9352935b88 fix(nginx): the live hostnames were missing from the admin denylist
tests / pest (push) Successful in 6m56s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Successful in 6s Details
The map listed only *.dev.clupilot.com, with the production names commented out
under "add the real public hostnames here before launch". Launching without
them removes the outer of the two layers that keep /admin off the public
internet — the ADMIN_HOSTS check in the app would still answer 404, but the
whole point of the pair is that neither is relied on alone.

admin.clupilot.com stays out of the list on purpose: the list names the hosts
that are PUBLIC, and the console is not one of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 18:43:44 +02:00
nexxo e2b4cdbac4 feat(site): switch the public website and portal off from the console
While the product is still being built, the marketing site and the customer
portal should not be reachable — but they must stay reachable for us.

- A toggle in the console (site.manage, Owner/Admin) stored in a new
  app_settings table, because this has to be flippable without a deploy.
- Outsiders get a placeholder with 503 + noindex, not 200: a 200 invites search
  engines to index the placeholder as the site's content, which is far harder
  to undo than to prevent.
- Anyone on the management VPN, and any signed-in operator, sees the real site.
  The console, Livewire's endpoint, the Stripe webhook and the health check are
  always reachable — otherwise the switch could only be flipped once.
- robots.txt is generated by the app and follows the switch. It had to stop
  being a static file: nginx short-circuited it and Laravel's stock file said
  'Disallow:' with an empty value, so crawlers were never told anything.
- Settings reads fall back to the default when the table is unavailable. The
  gate reads one on every request, so a deploy running new code before migrate
  would otherwise answer the entire site with a 500.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:10:27 +02:00
nexxo 7247edf9d8 feat(security): keep the operator console off the public hostnames
Only the marketing site and the customer portal are public; /admin and the
Proxmox hosts stay on the private network. nginx was a single server_name _
catch-all, so /admin was served on all four dev domains.

Two layers:
- nginx denies /admin on the known-public hostnames before PHP is reached
  (denylist, so an allowlist typo cannot lock the operators out).
- ADMIN_HOSTS is now populated, making the app layer a strict allowlist that
  also covers /livewire/update, which nginx cannot attribute to /admin.

Verified live against the running stack: /admin is 404 on www/app/api/ws and on
any unlisted host, 302->login on admin.dev / the private IP / localhost, while
/ and /dashboard stay reachable everywhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 21:01:02 +02:00
nexxo 2ae7595aff chore: bootstrap CluPilot control-plane (Laravel 13, Docker stack)
- Docker dev stack: app (php-fpm+nginx+vite via supervisor), reverb, queue,
  scheduler, mariadb 11.4, redis 7 — env-driven ports/UID (HOST_UID=1000).
- Laravel 13.8 + Livewire 3.8 (pinned v3 per R2), Tailwind v4, Reverb, Echo,
  phpseclib, wire-elements/modal.
- .env wired to service names; DE default locale (R16); browser Reverb host
  env-driven; self-hosted-fonts-ready vite.config (R14).
- Entrypoint bootstraps vendor/node_modules on fresh checkout; bin/clupilot
  helper (UID/port env-driven) + shell aliases.
- Reviewed with Codex (R15): 7 findings fixed, 1 verified false-positive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 00:21:30 +02:00