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>
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)
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>
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>
- 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>
- .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>
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>
- 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>