Files
cameleer-website/OPERATOR-CHECKLIST.md
hsiegeln 37897f07c3
All checks were successful
ci / build-test (push) Successful in 4m12s
chore: remove all nJAMS references from the live site
Per Hendrik's direction, no nJAMS references on the website. The
founder pedigree claim now stands on the years and the customer
segment ("15 years building integration monitoring for banks,
insurers, and logistics operators") without naming a prior product.

Changes:
- src/components/sections/SocialProofStrip.astro: drop the
  ' · ex-nJAMS' suffix from the founder attribution; collapse the
  multi-step PENDING comment into a single-line founder-name TODO.
- src/components/sections/WhyUs.astro: drop the trademark-review
  comment (no longer relevant — the body text never named the
  prior product, only the comment did).
- OPERATOR-CHECKLIST.md: remove the 'Why us / nJAMS wording review'
  pre-publish task.  Also rename the adjacent 'MID-tier retention'
  TODO to 'Starter-tier retention' to match the relaunched tier
  taxonomy.
- docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md:
  update §4, §6.2, §12 to reflect the removed wording.  Only the
  founder-name placeholder remains as a pre-publish blocker.

Historical specs / plans under docs/superpowers/{specs,plans}/
keep their original wording — they're records of past decisions
and are not on the website.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:41:59 +02:00

104 lines
6.4 KiB
Markdown

# Operator Checklist — `cameleer-website`
One-time setup that lives outside code. Do these before the first `main` merge that ships live.
## 1. Hetzner Webhosting L
- [ ] Provision Webhosting L plan. Note the SSH hostname (e.g. `wwwNNN.your-server.de`) and the user login.
- [ ] In konsoleH, **enable SSH access** for the user.
- [ ] In konsoleH → Domainverwaltung, register the production domain (`www.cameleer.io`) on this hosting and confirm what document root Apache uses for it. On Webhosting L, the Apache vhost docroot for the addon domain is typically the bare `~/public_html/` (NOT a subdirectory). The `SFTP_PATH` secret must match this exactly — wrong path = 404 from origin.
- [ ] Generate an ed25519 SSH key pair locally (once):
```bash
ssh-keygen -t ed25519 -f ~/.ssh/cameleer-website-deploy -C "cameleer-website CI"
```
- [ ] Add the **public** key to `~/.ssh/authorized_keys` on the Hetzner account (or via konsoleH SSH-Schlüssel UI).
- [ ] Test SSH on **port 222** (Hetzner Webhosting splits SFTP=22 / SSH-shell=222; rsync needs 222):
```bash
ssh -p 222 -i ~/.ssh/cameleer-website-deploy user@wwwNNN.your-server.de "ls -la"
```
- [ ] Grab the SSH host key for pinning, also on **port 222**:
```bash
ssh-keyscan -p 222 -t rsa,ed25519,ecdsa wwwNNN.your-server.de > hetzner-known-hosts.txt
```
Verify the fingerprint against what your manual SSH session displayed before saving the secret — `ssh-keyscan` doesn't authenticate.
- [ ] **Origin TLS:** Cloudflare Full (strict) requires a valid origin cert. Hetzner Webhosting auto-issues Let's Encrypt — confirm the cert is active in konsoleH → SSL → SSL-Zertifikate.
- [ ] **`.htaccess` caveat (important):** Hetzner Webhosting L runs Apache with `AllowOverride None` on the user docroot, so any `.htaccess` file you `rsync` is **silently ignored** by Apache. The only way to set Apache directives on this tier is via konsoleH → Einstellungen → Serverkonfiguration (per-directory wrench panel). This repo therefore owns no `.htaccess`; all response headers live in Cloudflare (see §2). The konsoleH `.htaccess` panel is left empty by default; defense-in-depth header copies there are optional and survive rsync deploys (different storage location).
## 2. Cloudflare (zone: cameleer.io)
### DNS
- [ ] `A` record `www.cameleer.io` → Hetzner IP. **Proxied (orange).**
- [ ] `A` record `@` (apex) → Hetzner IP. **Proxied (orange).**
- [ ] `A`/`CNAME` for `auth.cameleer.io` → SaaS ingress. **Proxied.**
- [ ] `A`/`CNAME` for `platform.cameleer.io` → SaaS ingress. **Proxied.**
- [ ] NO bare MX. If email is needed at `@cameleer.io`, use **Cloudflare Email Routing** or a distinct hostname on a different provider.
### SSL/TLS
- [ ] Mode: **Full (strict)**.
- [ ] Minimum TLS: **1.2**.
- [ ] TLS 1.3: **on**.
- [ ] Always Use HTTPS: **on**.
- [ ] Automatic HTTPS Rewrites: **on**.
- [ ] HSTS: `max-age=31536000; includeSubDomains; preload`. (Add the domain to `https://hstspreload.org/` only after the site is stable and serving HSTS cleanly for a couple of weeks.)
### Security
- [ ] WAF → **Cloudflare Managed Ruleset**: enabled (Free plan includes this since 2024).
- [ ] Bot Fight Mode: **on**.
- [ ] Browser Integrity Check: **on**.
- [ ] Security Level: **medium**.
- [ ] Email Obfuscation: **on**.
- [ ] Rate Limiting rule: 20 req/min per IP on `/*` (marketing pages).
### Transform Rules (edge-level security headers)
Create a Transform Rule — "Modify Response Header" — matching `http.host eq "www.cameleer.io"`:
| Operation | Header | Value |
|-----------|--------|-------|
| Set | `Content-Security-Policy` | `default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; object-src 'none'` |
| Set | `X-Content-Type-Options` | `nosniff` |
| Set | `X-Frame-Options` | `DENY` |
| Set | `Referrer-Policy` | `strict-origin-when-cross-origin` |
| Set | `Permissions-Policy` | `geolocation=(), microphone=(), camera=(), payment=(), usb=()` |
### Page Rules / Redirect
- [ ] `cameleer.io/*` → `https://www.cameleer.io/$1` (301 permanent).
## 3. Gitea Actions secrets (in the repo settings)
Add these under Repository settings → Actions → Secrets (or variables):
| Name | Type | Value |
|------|------|-------|
| `SFTP_HOST` | secret | Hetzner SSH hostname |
| `SFTP_USER` | secret | Hetzner SSH user |
| `SFTP_PATH` | secret | Absolute path to the Apache vhost docroot configured in konsoleH (typically `/usr/www/users/<login>/public_html`). Mismatch → 404 on origin. |
| `SFTP_KEY` | secret | Contents of `~/.ssh/cameleer-website-deploy` (private key, PEM) |
| `SFTP_KNOWN_HOSTS` | secret | Contents of `hetzner-known-hosts.txt` (captured via `ssh-keyscan`) |
| `PUBLIC_AUTH_SIGNIN_URL` | secret | `https://app.cameleer.io/sign-in` |
| `PUBLIC_AUTH_SIGNUP_URL` | secret | `https://app.cameleer.io/sign-in?first_screen=register` |
| `PUBLIC_SALES_EMAIL` | secret | `sales@cameleer.io` (or whatever sales alias you set up) |
These three are not actually secret (they end up in the built HTML), but Gitea's
Actions UI puts them in the **Secrets** tab alongside the SFTP credentials. The
workflows read them via the `${{ secrets.* }}` context.
## 4. Content TODO — before go-live
- [ ] Fill in `src/pages/imprint.astro` `operator` object with real legal details.
- [ ] Fill in `operatorContact` in `src/pages/privacy.astro`.
- [ ] Confirm Starter-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other.
## 5. First deploy
The `deploy` workflow is **manual-only** — it does NOT auto-fire on push to `main`. After merging, trigger it explicitly.
1. Merge a PR to `main`.
2. In Gitea: **Actions → deploy → Run workflow** on `main`.
3. Watch the single `deploy` job (build + tests + rsync + smoke test in one step).
4. The workflow's post-deploy smoke check verifies HSTS / CSP / X-Frame-Options on the live response. If any fail, the deploy step exits non-zero — debug at the **Cloudflare Transform Rule** layer (§2 above), since headers no longer come from the origin.
5. Manually verify:
- `curl -sI https://www.cameleer.io/` returns all 5 security headers (HSTS, CSP, XFO, X-Content-Type-Options, Referrer-Policy, Permissions-Policy).
- `https://cameleer.io/` → `https://www.cameleer.io/` 301 redirect.
- Open the site in an incognito window on desktop + mobile.