Public traffic terminates TLS at the Pangolin VPS, which then proxies to the
control-plane nodes over https:443 — the pattern every other k3s-backed
Pangolin resource follows — so the cluster needs its own valid certificate for
the production hostnames, not just the staging one.
Add www.bluecapstrategies.com and bluecapstrategies.com to the tls block as
bluecap-website-tls. Safe to do before cutover, and deliberately done now:
letsencrypt-prod solves DNS-01 through Cloudflare, so issuance only needs a
temporary TXT record in the zone. No A record has to point at the cluster and
the live WordPress site is untouched. The Cloudflare token was extended to
cover the zone on 2026-10-09.
Both hostnames are listed because the canonical-host decision is still open;
holding a cert for each keeps either choice available, and the redirect is
configured separately.
Pangolin's edge certificate is HTTP-01 and cannot be pre-issued this way; it
is obtained on the first HTTPS request after DNS moves.
The contact form could not be made to work by configuration. Two reasons,
both fixed here.
The endpoint never reached the browser. k8s/configmap.yaml carried
PUBLIC_CONTACT_FORM_ENDPOINT, but the Deployment has no env/envFrom pointing
at that ConfigMap, so it was dead config. Wiring it up would not have helped
either: this is a static Astro build, so import.meta.env.PUBLIC_* is inlined
when the image is built and the pod only serves pre-built HTML. Anything the
browser needs has to arrive as a build arg. Add ARG/ENV to the Dockerfile and
pass --build-arg from CI, sourced from a CONTACT_FORM_ENDPOINT Actions
secret. Rewrite the ConfigMap as documentation of what the build expects,
with an explicit warning not to add envFrom.
Submission behaviour was also wrong for visitors. A native POST would have
navigated them to the form provider's page; post in the background with
fetch() and send them to our own /thank-you/ instead. On failure the form now
stays put, re-enables the button and offers the phone and email fallback,
rather than silently losing the inquiry. Add a honeypot (_gotcha, which
Formspree treats as spam) positioned off-screen rather than display:none, so
bots that skip hidden fields still fill it.
Read data-endpoint at submit time rather than caching it at init, keeping the
attribute the single source of truth.
Tests: replace the old contact assertion, which faked a native GET
navigation, with one that mocks the endpoint and exercises the real fetch
path including the posted payload. Add a case for endpoint failure.
typecheck 0/0/0, build 0/0, seo:lint clean on 20 pages, e2e 44 passed.
The 4 remaining failures are the pre-existing stale nav assertions in
docs/launch-checklist.md section 2.1, untouched here.
There was no way to view the rebuild in a browser: the ingress answered only
on websecure for the production hostnames, which have no cert and still
belong to the live WordPress site. Review meant a port-forward.
Add bcs.int.7tl-homelab.com as a staging route. The *.int zone resolves to
the internal traefik LB (10.66.15.30) and is not reachable from the internet,
so this exposes nothing publicly and leaves the real domain untouched. It
follows the same pattern as the existing argocd/lidarr/tautulli ingresses.
Enable cert-manager on the ingress, but scope the tls block to the staging
host only. The production hostnames stay out of it deliberately: requesting a
cert for names the live site still serves is a cutover step, not a
prerequisite.
Fix the issuer name while here. The commented-out placeholder said
"letsencrypt-production"; the ClusterIssuer is actually "letsencrypt-prod",
so the old hint would have silently failed to issue.
Note for cutover: letsencrypt-prod solves via DNS-01 against Cloudflare, so
the production cert can be issued before DNS moves, avoiding the HTTP-01
ordering problem. Conditional on bluecapstrategies.com being reachable by the
cert-manager Cloudflare token, which is not yet verified.
Static Astro 5 site for BlueCap Strategies with exact live-site content
parity, deployed to k3s via GitOps (ArgoCD).
- Page copy in Markdown content collections (services, focus-areas, pages,
insights) with a Zod SEO schema enforced at build time
- Homepage + About restored to exact live copy; real live-site imagery
- Build-time SEO linter (scripts/seo-lint.mjs) and Playwright e2e suite
- Multi-stage Dockerfile (nginx serves dist/) and Kustomize manifests (k8s/)
- Per-agent robots.txt; config-driven PostHog + Umami analytics scaffold
- ADRs and engineering docs under docs/
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>