Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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=<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:<address>`, 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)

Expand All @@ -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 <month>", 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.
2 changes: 1 addition & 1 deletion _headers
Original file line number Diff line number Diff line change
Expand Up @@ -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=()
58 changes: 9 additions & 49 deletions bcn-removed/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down Expand Up @@ -148,13 +138,14 @@ <h2>Verify what you're running</h2>
for the exact commands, copy-pasteable.
</p>

<h2 id="waitlist">Questions, or need help migrating a larger fleet?</h2>
<form id="wl" action="/api/waitlist" method="post">
<input type="email" name="email" required placeholder="you@yourcompany.com" aria-label="Work email">
<input class="hp" type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true">
<button type="submit">Get in touch</button>
</form>
<div class="form-msg" id="wl-msg" role="status"></div>
<h2 id="help">Questions, or migrating a larger fleet?</h2>
<p>Open an issue at
<a href="https://github.com/fosterstack/cache/issues">github.com/fosterstack/cache/issues</a>
— public, searchable, and it helps the next person with the same question. For anything
you would rather not discuss in public, email
<a href="mailto:hello@fosterstack.com">hello@fosterstack.com</a>.</p>
<p>There is no signup and no waitlist. We do not collect email addresses, and this site
sets no cookies and runs no analytics.</p>
</main>
<footer>
<div>
Expand All @@ -165,37 +156,6 @@ <h2 id="waitlist">Questions, or need help migrating a larger fleet?</h2>
<p style="margin-top:.6rem">© 2026 FosterStack · <a href="mailto:hello@fosterstack.com">hello@fosterstack.com</a></p>
</div>
</footer>
<script>
const form = document.getElementById('wl');
const msg = document.getElementById('wl-msg');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('button');
btn.disabled = true; msg.className = 'form-msg'; msg.textContent = '';
try {
const res = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: form.email.value.trim(),
website: form.website.value
})
});
const data = await res.json();
if (res.ok && data.ok) {
msg.className = 'form-msg ok';
msg.textContent = "Got it — we'll follow up.";
form.email.value = '';
} else {
throw new Error(data.error || 'Something went wrong.');
}
} catch (err) {
msg.className = 'form-msg err';
msg.textContent = 'Could not submit: ' + err.message + ' — or email hello@fosterstack.com';
} finally {
btn.disabled = false;
}
});
</script>

</body>
</html>
50 changes: 0 additions & 50 deletions functions/api/waitlist.js

This file was deleted.

111 changes: 55 additions & 56 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down Expand Up @@ -96,15 +93,28 @@ <h1>Your build cache shouldn't die with its vendor.</h1>
matters: it stays patched, on a promise.
</p>

<h2 id="waitlist">Join the waitlist</h2>
<p>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.</p>
<form id="wl" action="/api/waitlist" method="post">
<input type="email" name="email" required placeholder="you@yourcompany.com" aria-label="Work email">
<input class="hp" type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true">
<button type="submit">Get early access</button>
</form>
<div class="form-msg" id="wl-msg" role="status"></div>
<h2 id="try">Try it now</h2>
<p>There is no signup, no waitlist, and no license key for the free tier. Pull the
image and point your build at it:</p>
<pre><code>docker run -d -p 8080:8080 ghcr.io/fosterstack/cache:latest
curl localhost:8080/healthz # -&gt; ok</code></pre>
<p class="cta-row">
<a class="btn" href="https://github.com/fosterstack/cache#quickstart-docker">Quickstart</a>
<a class="btn btn-secondary" href="https://github.com/fosterstack/cache/blob/main/docs/migrate-from-bcn.md">Migrate off Build Cache Node</a>
<a class="btn btn-secondary" href="https://github.com/fosterstack/cache">Read the source</a>
</p>
<p class="muted-note"><strong>Where it stands:</strong> 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
<a href="https://github.com/fosterstack/cache/issues">GitHub issues</a>, which is also
where the roadmap gets argued with.</p>

<h2>Stay in touch</h2>
<p>We do not collect email addresses. To follow the project,
<a href="https://github.com/fosterstack/cache">star the repository</a> or use
<strong>Watch → Custom → Releases</strong> 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.</p>

<h2>What you get that a bare HTTP endpoint doesn't give you</h2>
<p>Yes — Gradle's remote cache protocol is just GET and PUT, and you could point it at any
Expand All @@ -129,7 +139,25 @@ <h2>Built to be verified, not trusted</h2>
<li>Signed commits, signed releases, public changelog, public compatibility matrix.</li>
</ul>

<h2>Pricing at launch</h2>
<h2>What we do not collect</h2>
<ul class="trust">
<li><strong>The product does not phone home.</strong> 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.</li>
<li><strong>This website collects no email addresses.</strong> There is no signup
form, no waitlist, and no newsletter. It sets no cookies, runs no analytics, and
loads nothing from a third party.</li>
<li><strong>The only personal data FosterStack LLC holds</strong> 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.</li>
</ul>
<p class="muted-note">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.</p>

<h2>Pricing</h2>
<table>
<tr><th>Free</th><th>Team</th><th>Business</th></tr>
<tr>
Expand All @@ -144,10 +172,13 @@ <h2>Pricing at launch</h2>
</p>

<h2>Roadmap honesty</h2>
<p>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.</p>
<p>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,
<a href="https://github.com/fosterstack/cache/issues">open an issue</a> — that is what
moves the roadmap.</p>
</main>
<footer>
<div>
Expand All @@ -160,37 +191,5 @@ <h2>Roadmap honesty</h2>
<p style="margin-top:.6rem">© 2026 FosterStack · <a href="https://github.com/fosterstack/cache">github.com/fosterstack/cache</a> · <a href="mailto:hello@fosterstack.com">hello@fosterstack.com</a></p>
</div>
</footer>
<script>
const form = document.getElementById('wl');
const msg = document.getElementById('wl-msg');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('button');
btn.disabled = true; msg.className = 'form-msg'; msg.textContent = '';
try {
const res = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: form.email.value.trim(),
website: form.website.value
})
});
const data = await res.json();
if (res.ok && data.ok) {
msg.className = 'form-msg ok';
msg.textContent = "You're on the list. We'll email when the beta opens — nothing else.";
form.email.value = '';
} else {
throw new Error(data.error || 'Something went wrong.');
}
} catch (err) {
msg.className = 'form-msg err';
msg.textContent = 'Could not sign you up: ' + err.message + ' — or email hello@fosterstack.com';
} finally {
btn.disabled = false;
}
});
</script>
</body>
</html>
23 changes: 17 additions & 6 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Loading