Commit Graph

57 Commits

Author SHA1 Message Date
hsiegeln
ca2a725953 ci(deploy): merge build+deploy into one job, manual trigger only
All checks were successful
ci / build-test (push) Successful in 4m0s
Two changes:

1. Merge build and deploy jobs into a single 'deploy' job. This
   eliminates the actions/upload-artifact@v3 round-trip, which was
   silently stripping dotfiles (.htaccess) from the artifact and
   leaving the deployed origin without security headers. The built
   dist/ (including .htaccess) now flows directly into rsync in the
   same workspace.

2. Remove the 'push: branches: [main]' trigger so deploy runs only
   on workflow_dispatch (manual click in Gitea Actions UI).
   Merges to main no longer auto-deploy — production promotion is
   an explicit user action.

The concurrency group at workflow level still prevents overlapping
deploys. All secrets remain unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:24:42 +02:00
hsiegeln
fdb0411c35 Sync main into feat/initial-build before merge-jobs refactor 2026-04-24 21:23:58 +02:00
hsiegeln
461b5e0cd6 Merge branch 'feat/initial-build' into main
Some checks failed
deploy / build (push) Successful in 36s
deploy / deploy (push) Failing after 14s
ci / build-test (push) Successful in 3m54s
Copy public/.htaccess into dist after Astro build (Astro/Vite drops
dotfiles from public/ otherwise, leaving the origin without HSTS).

# Conflicts:
#	.gitea/workflows/deploy.yml
2026-04-24 21:09:35 +02:00
hsiegeln
0d743402ac ci(deploy): copy public/.htaccess into dist after build
All checks were successful
ci / build-test (push) Successful in 3m47s
Astro/Vite drops dotfiles from public/ during build, so .htaccess
never makes it into dist/. The deployed Apache origin then has no
header rules to apply, leaving the site without HSTS, X-Frame-Options,
Referrer-Policy, etc. — caught today by the post-deploy smoke test
("HSTS missing").

Copy the file explicitly after build. test -f makes the step fail
loudly if public/.htaccess goes missing, rather than silently
shipping a header-less site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:08:51 +02:00
hsiegeln
28fcaf16c5 Merge branch 'feat/initial-build' into main
Some checks failed
ci / build-test (push) Successful in 4m2s
deploy / build (push) Successful in 30s
deploy / deploy (push) Failing after 13s
Revert to rsync, route through Hetzner's SSH port 222 (the shell port,
as opposed to port 22 which is SFTP-only).
2026-04-24 20:24:33 +02:00
hsiegeln
e3fbbbada7 ci(deploy): revert to rsync via SSH port 222 (Hetzner shell port)
All checks were successful
ci / build-test (push) Successful in 3m57s
Hetzner Webhosting exposes SSH on TWO ports:
  port 22  — SFTP only, refuses remote command exec
  port 222 — full SSH with shell, supports rsync

Previous deploys hit "exec request failed on channel 0" because we
were using port 22. Switch back from lftp to plain rsync, but route
it through port 222 with --rsync-path=/usr/bin/rsync (Hetzner's
locked-down PATH doesn't include rsync by default) and BatchMode=yes
to disable interactive prompts.

Mirrors the working local command:
  rsync -avz --rsync-path=/usr/bin/rsync \
    -e "ssh -p 222 -i ~/.ssh/id_ed25519_gitea -o BatchMode=yes" \
    ./ apibny@www691.your-server.de:/usr/www/users/apibny/www.cameleer.io

Keeps host-key pinning (StrictHostKeyChecking + UserKnownHostsFile)
which the local command omits because the user's personal known_hosts
already trusts the host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:24:27 +02:00
hsiegeln
cb21be71f0 Merge branch 'feat/initial-build' into main
Some checks failed
ci / build-test (push) Successful in 3m55s
deploy / build (push) Successful in 28s
deploy / deploy (push) Failing after 12s
Fix lftp auth (explicit -u USER, + unindented heredoc body).
2026-04-24 20:08:29 +02:00
hsiegeln
5417565e34 ci(deploy): fix lftp auth — explicit empty password + unindented script
All checks were successful
ci / build-test (push) Successful in 4m0s
Two issues from the previous lftp run:
- "GetPass() failed -- assume anonymous login" / "Password required":
  without `-u USER,` (trailing comma = empty password), lftp tries
  to prompt for a password instead of relying on the ssh key passed
  via sftp:connect-program.
- Heredoc body was indented with leading whitespace; lftp can mis-
  parse leading-whitespace lines as command continuations.

Also bump verbosity (`debug 3`) so the ssh command lftp launches
is logged — makes the next failure easier to read — and bound
retries to 1 so we fail fast in CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:08:22 +02:00
hsiegeln
60813e44d9 Merge branch 'feat/initial-build' into main
Some checks failed
ci / build-test (push) Successful in 4m1s
deploy / build (push) Successful in 31s
deploy / deploy (push) Failing after 27s
Switch deploy from rsync-over-SSH to lftp mirror over SFTP —
Hetzner Webhosting is SFTP-only and refuses remote exec.
2026-04-24 19:49:54 +02:00
hsiegeln
64aa8f426b ci(deploy): switch from rsync to lftp mirror (SFTP-only hosting)
All checks were successful
ci / build-test (push) Successful in 3m55s
Hetzner Webhosting accepts SSH for file transfer but refuses remote
command exec, failing rsync with:

  exec request failed on channel 0
  rsync error: error in rsync protocol data stream (code 12)

rsync over SSH requires spawning a remote rsync binary, which isn't
possible on SFTP-only tiers. Switch the mirror to lftp, which speaks
SFTP end-to-end. Same semantics (upload + delete removed files), same
key + known_hosts pinning via sftp:connect-program.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:49:42 +02:00
hsiegeln
c438d67469 Merge branch 'feat/initial-build' into main
Some checks failed
deploy / build (push) Successful in 33s
ci / build-test (push) Successful in 4m18s
deploy / deploy (push) Failing after 13s
Brings in the CI infrastructure fixes:
- ci.yml: probe Chromium binary; fall back to Playwright (95977c8)
- tailwind: lift text-faint to meet WCAG AA contrast (2fde385)
- deploy.yml: pin artifact actions to v3 for Gitea (bbd68ec)
2026-04-24 19:12:55 +02:00
hsiegeln
bbd68eca1f ci(deploy): pin upload/download-artifact to v3 for Gitea Actions
All checks were successful
ci / build-test (push) Successful in 3m59s
actions/upload-artifact@v4 and download-artifact@v4 use the
@actions/artifact v2+ client, which targets a github.com-only
backend and fails on Gitea / Forgejo / GHES with:

  GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+
  and download-artifact@v4+ are not currently supported on GHES.

Pin both to v3, which uses the older artifact protocol that Gitea
Actions implements.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:12:34 +02:00
bb6b8e63d7 .gitea/workflows/deploy.yml aktualisiert
Some checks failed
deploy / build (push) Failing after 32s
deploy / deploy (push) Has been skipped
ci / build-test (push) Failing after 57s
2026-04-24 19:04:16 +02:00
hsiegeln
2fde385ecf theme: lift text-faint to meet WCAG AA contrast
All checks were successful
ci / build-test (push) Successful in 3m39s
text-faint #6b7280 on bg #060a13 measures ~4.06:1 contrast — under the
4.5:1 normal-text threshold — which fails Lighthouse's color-contrast
audit and drops the accessibility score to 0.90 on /pricing and
/privacy (the only pages currently using this token).

#828b9b yields ~5.66:1, clears AA with margin, and stays visually
distinct from text-muted (#9aa3b2, ~7.8:1) so the design hierarchy
between text / text-muted / text-faint is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:57:40 +02:00
hsiegeln
95977c8d6c ci: probe Chromium binary, fall back to Playwright-bundled
Some checks failed
ci / build-test (push) Failing after 3m35s
The Ubuntu runner image ships /usr/bin/chromium-browser as a snap
forwarder stub that exits with "install via snap" when invoked but
is found on PATH. The previous detection used `command -v` only, so
it accepted the stub, set CHROME_PATH to it, and Lighthouse later
failed to launch Chrome (ECONNREFUSED on the debug port).

Probe each candidate with `--version` to confirm it actually runs.
When no working system binary exists, install Playwright's bundled
Chromium (supports linux/arm64) with --with-deps for system libs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:50:28 +02:00
b9b17df0ea .gitea/workflows/ci.yml aktualisiert
Some checks failed
ci / build-test (push) Failing after 2m12s
2026-04-24 18:25:52 +02:00
d772048fb4 .gitea/workflows/ci.yml aktualisiert
Some checks failed
ci / build-test (push) Has been cancelled
2026-04-24 18:10:49 +02:00
259871d34a Merge pull request 'feat/initial-build' (#3) from feat/initial-build into main
Some checks failed
ci / build-test (push) Failing after 1m3s
deploy / build (push) Failing after 51s
deploy / deploy (push) Has been skipped
Reviewed-on: #3
2026-04-24 18:09:37 +02:00
hsiegeln
295e2bcfff replaced TBD with TODO
Some checks failed
ci / build-test (push) Failing after 49s
ci / build-test (pull_request) Failing after 1m6s
2026-04-24 18:06:32 +02:00
hsiegeln
93131461b8 Fix CI build: read PUBLIC_* values from secrets context, broaden TODO guard
Some checks failed
ci / build-test (push) Failing after 46s
- Switch ci.yml + deploy.yml env bindings from ${{ vars.* }} to
  ${{ secrets.* }}. Gitea lets you put non-sensitive Actions values in
  either tab, and the secrets tab was used in practice — workflow was
  reading the wrong context and getting empty strings.
- Broaden the "no TODO markers ship" guard to accept both TODO: and
  legacy TBD: prefixes, matching the imprint/privacy page markers that
  were recently renamed.
- Document the secret-vs-variable choice in OPERATOR-CHECKLIST so the
  next operator doesn't get tripped up by the same thing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:04:16 +02:00
ba6069f14e Merge pull request 'replaced TBD with TODO' (#2) from feat/initial-build into main
Some checks failed
deploy / build (push) Has been cancelled
deploy / deploy (push) Has been cancelled
ci / build-test (push) Failing after 1m6s
Reviewed-on: #2
2026-04-24 18:00:52 +02:00
hsiegeln
9a4644bada replaced TBD with TODO
Some checks failed
ci / build-test (push) Failing after 51s
ci / build-test (pull_request) Failing after 1m4s
2026-04-24 17:58:49 +02:00
65667d9b50 Merge pull request 'feat/initial-build' (#1) from feat/initial-build into main
Some checks failed
ci / build-test (push) Failing after 1m4s
deploy / build (push) Failing after 33s
deploy / deploy (push) Has been skipped
Reviewed-on: #1
2026-04-24 17:56:09 +02:00
hsiegeln
7ecd1ff871 Split CI and deploy into separate workflows
Some checks failed
ci / build-test (push) Failing after 1m19s
ci / build-test (pull_request) Failing after 1m4s
- .gitea/workflows/ci.yml: builds, tests, lints, and runs Lighthouse on
  every push and PR to main. Runs on arm64 self-hosted Gitea runner.
- .gitea/workflows/deploy.yml: deploys to Hetzner on push to main or
  manual workflow_dispatch from Gitea UI. No Lighthouse (that's CI's
  job). Keeps the TBD-marker guard as a last-line safety check.

Both workflows live on the same concurrency group so no two deploys
race. On main push, CI and deploy run in parallel; CI is independent
and non-blocking for the deploy step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:46:04 +02:00
hsiegeln
ea6267d6f7 Make CI arm64-runner-aware for Gitea self-hosted act_runner
Runner: self-hosted arm64. Deploy target: amd64 (Hetzner). Cross-arch is
safe because Astro output is plain static HTML/CSS/JS — nothing in the
bundle is arch-specific.

Changes:
- runs-on: ubuntu-latest (most portable act_runner label — override per your
  runner's registered labels if needed).
- Install Chromium from apt at workflow time (Google Chrome has no Linux/arm64
  stable build; Chromium does). Handles both chromium and chromium-browser
  package names, sudo-less runners, and idempotently skips if already present.
- Export CHROME_PATH so LHCI picks the right binary.
- Add chromeFlags to lighthouserc.cjs: --no-sandbox --headless=new
  --disable-gpu --disable-dev-shm-usage (required in containerized/root
  Chromium on CI runners).
- timeout-minutes on both jobs.
- Defense-in-depth install of rsync + openssh in deploy job if the runner
  image doesn't ship them.
- Null-guard SFTP_KEY and SFTP_KNOWN_HOSTS secrets.
- Switch echo to printf for deterministic newline handling when writing key
  material to ~/.ssh files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:39:34 +02:00
hsiegeln
d98d73b14a Apply final-review cleanup: robots sitemap, CI guards, header parity
- Remove Sitemap line from robots.txt (no @astrojs/sitemap installed; was
  pointing to a 404 that would trip Google Search Console).
- Align Permissions-Policy across all three enforcement layers (middleware,
  .htaccess, Cloudflare Transform Rule in OPERATOR-CHECKLIST) by dropping the
  stray fullscreen=(self) from the middleware.
- Bump Lighthouse CI numberOfRuns from 1 to 3 to dampen CI-runner noise.
- Add CI guard that fails the build if any <TBD:...> marker survives into
  dist/ — prevents a legally incomplete imprint from shipping by accident.
- Add SFTP_* secret null-guard before the rsync --delete step so a missing
  secret fails loudly instead of targeting the SSH user's home root.
- Document the set:html compile-time-constant invariant in DualValueProps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:34:27 +02:00
hsiegeln
7e0d341c89 Add README and operator checklist for Hetzner + Cloudflare + Gitea setup 2026-04-24 17:25:53 +02:00
hsiegeln
92bef08357 Add Gitea Actions workflow: build, test, lint, Lighthouse, rsync deploy with header smoke check 2026-04-24 17:25:02 +02:00
hsiegeln
cc7802e461 Add Lighthouse CI config with >=95 thresholds across 4 categories 2026-04-24 17:24:37 +02:00
hsiegeln
04a1bd0aaf Add CI lint configs (html-validate, linkinator), fix nav a11y and URL routing
- .htmlvalidate.json with relaxed rules for design-system inline styles
- linkinator.config.json skipping mail, external auth/platform origins
- Fix lint:html npm script quoting for Windows-shell compatibility
- Switch astro build.format to 'directory' so /pricing resolves without MultiViews
- trailingSlash: 'ignore' lets both /pricing and /pricing/ work naturally
- Add aria-label to both <nav> landmarks (Primary, Footer) to satisfy html-validate

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:24:21 +02:00
hsiegeln
dfb8419b08 Add .htaccess for origin hardening, HTTPS redirect, and cache headers 2026-04-24 17:22:25 +02:00
hsiegeln
ecbf1f90d7 Add privacy policy page (GDPR-aligned, no-cookies posture documented) 2026-04-24 17:21:56 +02:00
hsiegeln
07de57dda5 Add imprint page (TMG §5 / DDG §5 structure, operator fields marked <TBD>) 2026-04-24 17:21:17 +02:00
hsiegeln
d4449bb404 Add pricing page with 4-tier comparison cards 2026-04-24 17:20:21 +02:00
hsiegeln
6f70e1a642 Assemble homepage — Hero, DualValueProps, HowItWorks, WhyUs, PricingTeaser, FinalCTA 2026-04-24 17:19:47 +02:00
hsiegeln
94b9b844ac Add PricingTeaser section — 4 tier mini-cards linking to /pricing 2026-04-24 17:18:58 +02:00
hsiegeln
9795c633c9 Add WhyUs section — moat (zero-code, bidirectional) + team pedigree 2026-04-24 17:18:33 +02:00
hsiegeln
5af7e0079f Add HowItWorks section — 3-step engineer-facing walkthrough 2026-04-24 17:18:08 +02:00
hsiegeln
6f9e98aeb6 Add DualValueProps section — 3 tiles pairing outcomes with capabilities 2026-04-24 17:17:46 +02:00
hsiegeln
754333226b Add homepage Hero section — headline, subhead, CTAs, diagram 2026-04-24 17:17:24 +02:00
hsiegeln
6b27d8f013 Add RouteDiagram hero SVG: 2 Camel routes with cross-route correlation 2026-04-24 17:16:29 +02:00
hsiegeln
8b4b1ae699 Add shared building-block components: header, footer, CTAs, topographic background
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:14:58 +02:00
hsiegeln
e084177acf Add BaseLayout with meta tags, favicon, robots.txt, and OG card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:13:07 +02:00
hsiegeln
e0a7ec4651 Plan fix: correct Task 4 preview-headers claim (static Astro doesn't run middleware at preview)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:11:21 +02:00
hsiegeln
2945c63f2a Document CSP directive rationale and strengthen inline-script assertion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:11:16 +02:00
hsiegeln
3432d509df Add security-headers middleware with strict CSP (TDD)
Exports buildSecurityHeaders() (pure, testable) and wires it into the
Astro onRequest middleware. Adds astro:middleware alias in vitest config
so the unit tests run outside Astro's build context. 14 tests pass (7
existing + 7 new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:06:45 +02:00
hsiegeln
7f8a41fd34 Plan fix: mirror sign-up URL test coverage additions for Task 3 2026-04-24 17:04:01 +02:00
hsiegeln
3a155efa69 Add sign-up URL test coverage and remove unused beforeEach import 2026-04-24 17:03:57 +02:00
hsiegeln
8ab30ca8fc Add auth URL config module with validation (TDD)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:59:50 +02:00
hsiegeln
4759f88780 Plan fix: use text-display/text-hero (fontSize) not font-display/font-hero (fontFamily) 2026-04-24 16:57:55 +02:00