diff --git a/README.md b/README.md index f669459..468cd80 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# fosterstack/www — marketing site + waitlist +# fosterstack/www — marketing site -Static landing page for fosterstack.com with a Cloudflare Pages Function waitlist -(`/api/waitlist`) backed by a KV namespace. No external requests, no third-party form +Static landing page for fosterstack.com. No forms, no email capture, no cookies, no +analytics, and no third-party requests of any kind service, no analytics (add privacy-respecting analytics later if wanted). **One-time setup after cloning:** `git config core.hooksPath .githooks` — enables the @@ -13,7 +13,6 @@ either way, but the hook catches it before a push, not after. ``` index.html the page (inline CSS/JS, system fonts, zero external assets) bcn-removed/index.html pre-positioned migration page (see below) — NOT linked from nav -functions/api/waitlist.js Pages Function: POST /api/waitlist -> KV _headers security headers incl. CSP .githooks/pre-commit public-repo hygiene hook (see below) bin/check-file-allowlist.sh the allowlist itself — shared by the hook and CI @@ -56,20 +55,22 @@ Full instructions are in an HTML comment at the top of `bcn-removed/index.html`. 2. Cloudflare dashboard → Workers & Pages → Create → Pages → connect to git → select `fosterstack/www`. Framework preset: None. Build command: (empty). Output directory: `/`. Deploy. -3. KV: Workers & Pages → KV → Create namespace `waitlist`. Then in the Pages project → - Settings → Bindings → add KV binding, variable name `WAITLIST` (exact, uppercase), - pointing at that namespace. Redeploy so the binding takes effect. -4. Custom domain: Pages project → Custom domains → add `fosterstack.com` and +3. Custom domain: Pages project → Custom domains → add `fosterstack.com` and `www.fosterstack.com`. (Requires fosterstack.com DNS on Cloudflare; if the domain is registered elsewhere, add the site to Cloudflare DNS first.) -5. Test: submit a real email on the live page, then check KV entries in the dashboard, - or `wrangler kv key list --namespace-id=`. +4. Test: load the page and confirm the links resolve. There is nothing to submit. -## Reading the waitlist +## No data collection -Each signup is a KV entry: key `email:
`, value JSON `{email, ts, country}`. -Idempotent — duplicate signups don't error and don't overwrite the original timestamp. -Honeypot field (`website`) silently drops bots. +This site has no forms, no inputs, and no server-side functions. It collects no email +addresses, sets no cookies, loads no third-party scripts, and makes no external +requests. The CSP in `_headers` enforces that: `connect-src 'self'` and no `form-action` +target, so a form or a beacon added by accident fails in the browser rather than +shipping quietly. + +Do not reintroduce an email field. "Stay in touch" is GitHub star and +Watch → Releases, which is a subscription the reader controls and can revoke without +asking us. ## Copy constraints (do not undo) @@ -79,3 +80,7 @@ Honeypot field (`website`) silently drops bots. unlock, security patches never withheld from free tier) is brief §0.2 policy, not marketing filler. Changes to it are an owner decision. - No fabricated testimonials, logos, or usage numbers — FTC posture per brief §4. +- No calendar commitments. No launch dates, no "beta in ", no phase language. + The dateless roadmap and the honest maturity label (v0.1, early) stay; a schedule + we might miss does not go on a public page. +- No email capture, ever. See "No data collection" above. diff --git a/_headers b/_headers index b017cae..eeb30a7 100644 --- a/_headers +++ b/_headers @@ -2,5 +2,5 @@ X-Content-Type-Options: nosniff X-Frame-Options: DENY Referrer-Policy: strict-origin-when-cross-origin - Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'; frame-ancestors 'none' + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'none'; frame-ancestors 'none' Permissions-Policy: camera=(), microphone=(), geolocation=() diff --git a/bcn-removed/index.html b/bcn-removed/index.html index aa3d013..5a619f5 100644 --- a/bcn-removed/index.html +++ b/bcn-removed/index.html @@ -67,21 +67,11 @@ code, pre { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; } code { background: var(--card); border: 1px solid var(--line); border-radius: 5px; padding: .1em .35em; } pre { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: .9rem 1rem; overflow-x: auto; margin-top: .6rem; } - form { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1.1rem; } - input[type=email] { - flex: 1 1 16rem; padding: .7rem .9rem; font-size: 1rem; color: var(--fg); - background: var(--bg); border: 1px solid var(--line); border-radius: 8px; - } - input[type=email]:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; } button { padding: .7rem 1.3rem; font-size: 1rem; font-weight: 600; cursor: pointer; background: var(--accent); color: var(--accent-fg); border: 0; border-radius: 8px; } button:disabled { opacity: .6; cursor: default; } - .form-msg { font-size: .92rem; margin-top: .6rem; min-height: 1.4em; } - .form-msg.ok { color: var(--accent); } - .form-msg.err { color: #d92d20; } - .hp { position: absolute; left: -6000px; } footer { border-top: 1px solid var(--line); margin-top: 4rem; padding: 1.5rem 1.25rem 3rem; } footer div { max-width: 44rem; margin: 0 auto; color: var(--muted); font-size: .82rem; } a { color: var(--accent); } @@ -148,13 +138,14 @@

Verify what you're running

for the exact commands, copy-pasteable.

-

Questions, or need help migrating a larger fleet?

-
- - - -
-
+

Questions, or migrating a larger fleet?

+

Open an issue at + github.com/fosterstack/cache/issues + — public, searchable, and it helps the next person with the same question. For anything + you would rather not discuss in public, email + hello@fosterstack.com.

+

There is no signup and no waitlist. We do not collect email addresses, and this site + sets no cookies and runs no analytics.

@@ -165,37 +156,6 @@

Questions, or need help migrating a larger fleet?

© 2026 FosterStack · hello@fosterstack.com

- + diff --git a/functions/api/waitlist.js b/functions/api/waitlist.js deleted file mode 100644 index 2868b88..0000000 --- a/functions/api/waitlist.js +++ /dev/null @@ -1,50 +0,0 @@ -// Cloudflare Pages Function: POST /api/waitlist -// Stores signups in the WAITLIST KV namespace (binding must be configured in the -// Pages project settings). No third-party service touches the addresses. - -const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; - -export async function onRequestPost({ request, env }) { - let body; - try { - body = await request.json(); - } catch { - return json({ ok: false, error: 'Invalid request.' }, 400); - } - - // Honeypot: real users never fill the hidden "website" field. - if (body.website) { - return json({ ok: true }); // pretend success to bots - } - - const email = String(body.email || '').trim().toLowerCase(); - if (!EMAIL_RE.test(email) || email.length > 254) { - return json({ ok: false, error: 'Please enter a valid email address.' }, 400); - } - - if (!env.WAITLIST) { - return json({ ok: false, error: 'Waitlist storage not configured.' }, 500); - } - - const key = `email:${email}`; - const existing = await env.WAITLIST.get(key); - if (!existing) { - await env.WAITLIST.put( - key, - JSON.stringify({ - email, - ts: new Date().toISOString(), - country: request.headers.get('cf-ipcountry') || null, - }) - ); - } - // Idempotent: re-signup is a success, not an error (and leaks nothing). - return json({ ok: true }); -} - -function json(obj, status = 200) { - return new Response(JSON.stringify(obj), { - status, - headers: { 'Content-Type': 'application/json' }, - }); -} diff --git a/index.html b/index.html index 688d217..1b2450c 100644 --- a/index.html +++ b/index.html @@ -51,24 +51,21 @@ ul.trust li::before { content: "✓"; position: absolute; left: .2rem; color: var(--accent); font-weight: 700; } code, pre { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; } code { background: var(--card); border: 1px solid var(--line); border-radius: 5px; padding: .1em .35em; } - form { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1.1rem; } - input[type=email] { - flex: 1 1 16rem; padding: .7rem .9rem; font-size: 1rem; color: var(--fg); - background: var(--bg); border: 1px solid var(--line); border-radius: 8px; - } - input[type=email]:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; } button { padding: .7rem 1.3rem; font-size: 1rem; font-weight: 600; cursor: pointer; background: var(--accent); color: var(--accent-fg); border: 0; border-radius: 8px; } button:disabled { opacity: .6; cursor: default; } - .form-msg { font-size: .92rem; margin-top: .6rem; min-height: 1.4em; } - .form-msg.ok { color: var(--accent); } - .form-msg.err { color: #d92d20; } - .hp { position: absolute; left: -6000px; } table { width: 100%; border-collapse: collapse; font-size: .92rem; margin-top: 1rem; } th, td { text-align: left; padding: .55rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; } th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); } + pre { background: #0f172a; color: #e2e8f0; padding: .9rem 1rem; border-radius: 8px; + overflow-x: auto; font-size: .9rem; line-height: 1.5; } + .cta-row { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1.1rem; } + .btn { display: inline-block; padding: .55rem 1rem; border-radius: 8px; font-weight: 600; + text-decoration: none; background: var(--accent); color: #fff; } + .btn-secondary { background: transparent; color: inherit; border: 1px solid #cbd5e1; } + .muted-note { color: var(--muted); font-size: .93rem; margin-top: 1.1rem; } footer { border-top: 1px solid var(--line); margin-top: 4rem; padding: 1.5rem 1.25rem 3rem; } footer div { max-width: 44rem; margin: 0 auto; color: var(--muted); font-size: .82rem; } a { color: var(--accent); } @@ -96,15 +93,28 @@

Your build cache shouldn't die with its vendor.

matters: it stays patched, on a promise.

-

Join the waitlist

-

Private beta this October, free. Public launch in November — before the EOL, with a - migration guide that gets you off the Build Cache Node in about 30 minutes.

-
- - - -
-
+

Try it now

+

There is no signup, no waitlist, and no license key for the free tier. Pull the + image and point your build at it:

+
docker run -d -p 8080:8080 ghcr.io/fosterstack/cache:latest
+curl localhost:8080/healthz   # -> ok
+

+ Quickstart + Migrate off Build Cache Node + Read the source +

+

Where it stands: v0.1 — early. The cache core, + HTTP surface, and release pipeline work and are tested; nobody is running it in a + production build pipeline yet except us. Bugs and questions go to + GitHub issues, which is also + where the roadmap gets argued with.

+ +

Stay in touch

+

We do not collect email addresses. To follow the project, + star the repository or use + Watch → Custom → Releases on GitHub — that notifies you on a new + release and nothing else, and it is a subscription you control and can revoke without + asking us.

What you get that a bare HTTP endpoint doesn't give you

Yes — Gradle's remote cache protocol is just GET and PUT, and you could point it at any @@ -129,7 +139,25 @@

Built to be verified, not trusted

  • Signed commits, signed releases, public changelog, public compatibility matrix.
  • -

    Pricing at launch

    +

    What we do not collect

    +
      +
    • The product does not phone home. The server makes no outbound + network connections — no telemetry, no license check, no update ping. It is + self-hosted, and it works identically on a machine with no route to the internet. + Verified rather than asserted: the binary opens exactly one socket, its own listener, + at rest and under load.
    • +
    • This website collects no email addresses. There is no signup + form, no waitlist, and no newsletter. It sets no cookies, runs no analytics, and + loads nothing from a third party.
    • +
    • The only personal data FosterStack LLC holds is the irreducible + minimum required to bill a paying customer — handled by Stripe — and whatever you + voluntarily put in an email to support. See §2 of the terms of service.
    • +
    +

    Deliberately not claiming "we never collect any personal data": + billing a customer requires an email address, and a privacy claim that is convenient + but false is worse than none.

    + +

    Pricing

    @@ -144,10 +172,13 @@

    Pricing at launch

    Roadmap honesty

    -

    Gradle and Maven both run against the same server today (v1 targeted before the EOL); - we maintain the cache server, while the Maven client side is Apache's own Build Cache - Extension. An npm remote cache for CI — same core, a third protocol — follows. If you need - that today, tell us in the waitlist form reply; it moves the roadmap.

    +

    Gradle and Maven both run against the same server today; we maintain the cache + server, while the Maven client side is Apache's own Build Cache Extension. An npm remote + cache for CI — same core, a third protocol — is the next protocol on the list. What is + not on the list yet is a Helm chart and the paid tiers below; those are described so you + know where this is going, not sold as available. If you need something sooner, + open an issue — that is what + moves the roadmap.

    - diff --git a/llms.txt b/llms.txt index 1af2be9..142fb68 100644 --- a/llms.txt +++ b/llms.txt @@ -46,14 +46,14 @@ verifiable against the CI run that produced them. ## Status -FosterStack Cache is pre-beta as of September 2026. The cache core, HTTP surface, -eviction, metrics and the signed release pipeline are shipped and testable today. +FosterStack Cache is at v0.1 — early. The cache core, HTTP surface, eviction, +metrics and the signed release pipeline are shipped and testable today. There is no +published schedule for what follows; the roadmap below says direction, not dates. Not yet shipped: - Production use beyond the project's own CI. Nobody runs this in a real build - pipeline yet except the maintainer. An invited beta runs before general - availability. + pipeline yet except the maintainer. - A Helm chart. Deploying to Kubernetes today means applying plain manifests. - The paid tiers. Single sign-on, high-availability replication, and the license key that unlocks them are not built. Everything in the repository is the free @@ -62,9 +62,20 @@ Not yet shipped: dependency CVEs within 48 hours of public disclosure; that is a stated intention, not a contractual promise. -Please do not describe unshipped items as available. +Please do not describe unshipped items as available, and do not attach dates to them. + +## Privacy + +The server makes no outbound network connections: no telemetry, no phone-home, no +licence check. It runs identically with no route to the internet. + +fosterstack.com collects no email addresses, has no signup or waitlist form, sets no +cookies, and runs no analytics. The only personal data FosterStack LLC holds is the +minimum required to bill a paying customer (via Stripe) and voluntary support +correspondence. Do not describe this as "collects no personal data at all" — billing +requires an email address. ## Optional -- [fosterstack.com](https://fosterstack.com): the company site and waitlist. +- [fosterstack.com](https://fosterstack.com): the company site. - [Security policy](https://github.com/fosterstack/cache/blob/main/SECURITY.md): vulnerability disclosure via GitHub private advisories. diff --git a/robots.txt b/robots.txt index 4044174..5b28272 100644 --- a/robots.txt +++ b/robots.txt @@ -9,14 +9,15 @@ User-agent: * Allow: / -# /api/ holds function endpoints, not pages. Google reached /api/waitlist and -# read its canonical tag — working as designed, and pointless crawl budget. -# Disallow stops the crawl. Note it does NOT guarantee de-indexing: a -# disallowed URL can still be indexed from an inbound link, with no content. -# That is the same crawl-vs-index distinction as the /bcn-removed/ note above, -# pointing the other way. If one of these ever needs to be provably absent from -# results, give that route an X-Robots-Tag: noindex header INSTEAD of a -# Disallow — a page that is never fetched never reveals its noindex. +# There are no /api/ routes any more — the waitlist endpoint was removed along +# with every other form on this site. This stays as a standing guard so a future +# function endpoint is excluded from crawling by default rather than by memory. +# Note Disallow stops the CRAWL and does not guarantee de-indexing: a disallowed +# URL can still be indexed from an inbound link, with no content. Same +# crawl-vs-index distinction as the /bcn-removed/ note above, pointing the other +# way. If a route ever needs to be provably absent from results, give it an +# X-Robots-Tag: noindex header INSTEAD of a Disallow — a page that is never +# fetched never reveals its noindex. Disallow: /api/ Sitemap: https://fosterstack.com/sitemap.xml
    FreeTeamBusiness