feat/initial-build #1
92
OPERATOR-CHECKLIST.md
Normal file
92
OPERATOR-CHECKLIST.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 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 and SFTP path.
|
||||
- [ ] In the Hetzner control panel, **enable SSH access** for the main user.
|
||||
- [ ] 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.
|
||||
- [ ] Test SSH: `ssh -i ~/.ssh/cameleer-website-deploy user@hetzner-host "ls -la"`.
|
||||
- [ ] Create a subdirectory for the site (typical path: `public_html/www.cameleer.io/`).
|
||||
- [ ] Grab the SSH host key for pinning:
|
||||
```bash
|
||||
ssh-keyscan -t ed25519 hetzner-host > hetzner-known-hosts.txt
|
||||
```
|
||||
- [ ] Install Let's Encrypt (or use Hetzner's built-in) for the origin hostname. Cloudflare Full (strict) requires a valid origin cert.
|
||||
|
||||
## 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 document root (e.g., `/usr/home/cameleer/public_html/www.cameleer.io`) |
|
||||
| `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` | variable | `https://auth.cameleer.io/sign-in` |
|
||||
| `PUBLIC_AUTH_SIGNUP_URL` | variable | `https://auth.cameleer.io/sign-in?first_screen=register` |
|
||||
| `PUBLIC_SALES_EMAIL` | variable | `sales@cameleer.io` (or whatever sales alias you set up) |
|
||||
|
||||
## 4. Content TBD — before go-live
|
||||
|
||||
- [ ] Fill in `src/pages/imprint.astro` `operator` object with real legal details.
|
||||
- [ ] Fill in `operatorContact` in `src/pages/privacy.astro`.
|
||||
- [ ] Review the "Why us" / nJAMS wording in `src/components/sections/WhyUs.astro` for trademark safety.
|
||||
- [ ] Confirm MID-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other.
|
||||
|
||||
## 5. First deploy
|
||||
|
||||
1. Merge a PR to `main`.
|
||||
2. Watch the Gitea Actions run: `build`, then `deploy`.
|
||||
3. The workflow includes a post-deploy smoke check — if HSTS / CSP / XFO are missing from the live response, the deploy fails and must be debugged at the Cloudflare Transform Rule layer.
|
||||
4. Manually verify:
|
||||
- `curl -sI https://www.cameleer.io/` returns all six security headers.
|
||||
- `https://cameleer.io/` → `https://www.cameleer.io/` 301 redirect.
|
||||
- Open the site in an incognito window on desktop + mobile.
|
||||
44
README.md
Normal file
44
README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# cameleer-website
|
||||
|
||||
Marketing site for [cameleer.io](https://www.cameleer.io) — zero-code observability for Apache Camel.
|
||||
|
||||
This is a **static** Astro 5 site. Hosted on Hetzner Webhosting L, fronted by Cloudflare, deployed via Gitea Actions.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run dev # http://localhost:4321
|
||||
npm run test # vitest — auth config + middleware header tests
|
||||
npm run build # produces dist/
|
||||
npm run preview # serves dist/
|
||||
```
|
||||
|
||||
## Quality gates (run in CI)
|
||||
|
||||
```bash
|
||||
npm run lint:html # html-validate on dist/
|
||||
npm run lint:links # linkinator on dist/
|
||||
npm run lh # Lighthouse CI (>=0.95 on all 4 categories)
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
See `.env.example`. All are `PUBLIC_*` (build-time, embedded in HTML).
|
||||
|
||||
| Var | Purpose |
|
||||
|-----|---------|
|
||||
| `PUBLIC_AUTH_SIGNIN_URL` | Logto sign-in URL (redirected to by "Sign in" buttons) |
|
||||
| `PUBLIC_AUTH_SIGNUP_URL` | Logto sign-up URL (redirected to by "Start free trial") |
|
||||
| `PUBLIC_SALES_EMAIL` | Sales email (`mailto:` target for "Talk to sales") |
|
||||
|
||||
## Deployment
|
||||
|
||||
Push to `main` → Gitea Actions runs tests, builds, lints, then `rsync`s `dist/` to Hetzner over SSH (ed25519 key, host-key-pinned). Rollback is `git revert && git push`.
|
||||
|
||||
See [`OPERATOR-CHECKLIST.md`](./OPERATOR-CHECKLIST.md) for the one-time Hetzner + Cloudflare setup.
|
||||
|
||||
## Design & plan
|
||||
|
||||
- `docs/superpowers/specs/2026-04-24-cameleer-website-design.md` — the approved spec.
|
||||
- `docs/superpowers/plans/2026-04-24-cameleer-website.md` — the implementation plan that built this repo.
|
||||
Reference in New Issue
Block a user