diff --git a/src/.hugo_build.lock b/src/.hugo_build.lock new file mode 100644 index 0000000..e69de29 diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..5731372 --- /dev/null +++ b/src/README.md @@ -0,0 +1,21 @@ +# cspresso.cafe (Hugo) + +This directory is a Hugo conversion of the original static HTML for **cspresso.cafe**. + +## Local dev + +```bash +hugo server +``` + +## Build + +```bash +hugo +``` + +The generated site will be in `public/`. + +Notes: +- `uglyURLs = true` so the output paths match the original `*.html` URLs (e.g. `docs.html`). +- `recipes.html` includes an alias for `examples.html`. diff --git a/src/content/_index.html b/src/content/_index.html new file mode 100644 index 0000000..532db27 --- /dev/null +++ b/src/content/_index.html @@ -0,0 +1,308 @@ +--- +title: "cspresso - Brew a Content Security Policy" +description: "cspresso crawls a site with headless Chromium (Playwright) and emits a draft Content-Security-Policy based on what loads." +--- +
+
+
+
+
+
Brew a Content Security Policy
+

Turn real page loads into a CSP you can ship.

+

+ cspresso crawls up to N same‑origin pages with headless Chromium (Playwright), watches the assets that load, + and emits a draft Content-Security-Policy header. +

+ + +
+ --json + --evaluate + --bypass-csp + --include-sourcemaps +
+
+ +
+
+ +
pipx install cspresso
+cspresso https://example.com --max-pages 10
+
+# visited: https://example.com/
+Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; ...;
+
+
+ Remember: it’s only a starting point: crawls may not hit every flow, and inline hashing/nonces require care. +
+
+
+
+
+ +
+
+
+
+

Quickstart

+
+
+
+
+
+ +
+                  
+cspresso https://mig5.net \
+  --ignore-non-html \
+  --max-pages 10
+                  
+                
+
+
+ +
+
+
What you’ll get
+
+ A header line you can paste into your vhost, or parseable info with --json +
+
+
+ Tip: if an existing CSP might block loads during analysis, add --bypass-csp. +
+
+
+ +
+
+ +
# Evaluate a candidate CSP (Report-Only) and fail CI on violations
+cspresso https://mig5.net \
+  --bypass-csp \
+  --evaluate "default-src 'self'; img-src 'none';" \
+  --json
+
+{
+  [...]
+  "violations": [
+    {
+      "console": true,
+      "disposition": "report",
+      "documentURI": "https://mig5.net/",
+      "text": "Loading the image 'https://mig5.net/logo.svg' violates the following Content Security Policy directive: \"img-src 'none'\". The policy is report-only, so the violation has been logged but no further action has been taken.",
+      "type": "info"
+    },
+    {
+      "console": true,
+      "disposition": "report",
+      "documentURI": "https://mig5.net/static/mig5.asc",
+      "text": "Applying inline style violates the following Content Security Policy directive 'default-src 'self''. Either the 'unsafe-inline' keyword, a hash ('sha256-4Su6mBWzEIFnH4pAGMOuaeBrstwJN4Z3pq/s1Kn4/KQ='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback. The policy is report-only, so the violation has been logged but no further action has been taken.",
+      "type": "info"
+    }
+  ]
+}
+
+# exit code: 1 if violations detected
+
+
+ +
+
+
+
+
+ +
+
+
+
+

How it works

+

+ cspresso lets the browser do the hard part: execute the page, watch what it loads, and distill origins into directives. +

+
+
+
+
+
+
+
Crawl
+
Visit up to --max-pages same-origin pages and let the app’s JS run.
+
+
+
+
+
+
Observe
+
Track scripts, styles, images, fonts, frames, and “connect-like” requests.
+
+
+
+
+
+
Draft a CSP
+
Emit a baseline policy plus observed origins per directive.
+
+
+
+
+
+
Evaluate
+
Inject a candidate as Report‑Only and capture violations with an exit code for CI.
+
+
+
+ +
+ Inline script/style is tricky: nonces must be generated per response, and hashes must match bytes exactly. + cspresso reports what it sees, but you should review and tighten before enforcing. +
+
+
+
+
+ +
+
+
+
+

Popular flags

+

A few options that tend to matter in real deployments.

+
+
+ +
+
+
--bypass-csp
+
Strip existing CSP response headers so they don’t block discovery or evaluation.
+
+ +
+
--evaluate
+
Inject a candidate policy as Report‑Only and exit 1 if any violations are detected.
+
+ +
+
--include-sourcemaps
+
Heuristically discover sourcemap origins and add them to connect-src.
+
+ +
+
--upgrade-insecure-requests
+
Emit upgrade-insecure-requests in the proposed policy.
+
+ +
+
--browsers-path
+
Control where Playwright installs Chromium (handy for AppImage/CI caches).
+
+ +
+
--json
+
Machine-readable output: CSP, visited URLs, notes, and evaluation violations.
+
+
+
+
+ +
+
+
+
+

Install

+

pipx, pip, Poetry, or a standalone AppImage from Releases.

+
+ +
+ + + +
+
+
+
+
+ +
# Recommended
+pipx install cspresso
+
+# Or plain pip (use a venv)
+pip install cspresso
+
+
+
+
+
Playwright browsers
+
+ cspresso can auto-install Chromium for Playwright if it isn’t present. By default it installs into ./.pw-browsers + for deterministic builds and easy CI caching. +
+
+
+ Override with --browsers-path or PLAYWRIGHT_BROWSERS_PATH. +
+
+
+
+
+ +
+
+
+
+ +
poetry add cspresso
+
+
+
+
+
Linux deps
+
+ If Chromium won’t start due to missing libraries, try --with-deps (may require elevated privileges). +
+
+
+
+
+ +
+
+
+
+ +
chmod +x cspresso.AppImage
+./cspresso.AppImage https://example.com \
+  --browsers-path "$HOME/.cache/cspresso/pw-browsers"
+
+
+
+
+
Tip
+
+ AppImages mount read-only - use --browsers-path to install browsers into a writable cache directory. +
+
+
+ Verify releases with the mig5 GPG key (fingerprint 00AE817C24A10C2540461A9C1D7CDE0234DB458D). +
+
+
+
+
+ +
+
+
+
diff --git a/src/content/docs.html b/src/content/docs.html new file mode 100644 index 0000000..9492b05 --- /dev/null +++ b/src/content/docs.html @@ -0,0 +1,155 @@ +--- +title: "cspresso Docs" +description: "Docs for cspresso: crawl a site with headless Chromium and generate a draft Content-Security-Policy; evaluate candidates in Report-Only mode." +--- +
+
+
+
+
+
On this page
+ + +
+ Prefer canonical docs? See the README. +
+
+
+ +
+
+
Docs
+

Usage

+

+ cspresso crawls up to --max-pages same-origin pages in Chromium, observes what loads, and emits a draft CSP. +

+
+ +
+

Install

+
+
+
+ +
# Recommended
+pipx install cspresso
+
+# Or plain pip (use a venv)
+pip install cspresso
+
+# An AppImage is also available on the
+# git repo Releases page.
+
+
+
+
+
+
Python + Playwright
+
+ You need Python 3.10+ and Playwright’s Chromium. cspresso can auto-install Chromium if missing. +
+
+
+ Verify Releases artifacts with the mig5 key: + https://mig5.net/static/mig5.asc + (fingerprint 00AE817C24A10C2540461A9C1D7CDE0234DB458D). +
+
+
+
+
+ +
+

Run

+
+ +
cspresso https://example.com --max-pages 10
+
+
+ The crawl stays on the same origin (it follows internal links) and will wait for networkidle plus a small “settle” + delay to catch late fetches. +
+
+ +
+

Output

+

+ Default output prints visited URLs as comments and then the proposed header line. + Use --json for machine-readable output. +

+
+ +
cspresso https://example.com --json
+
+
+ +
+

Inline scripts & styles

+
+
Why this is hard
+
+ If the page uses nonces, you must generate a new nonce per HTML response and inject it into both the CSP header and the HTML tags. + Hashes only work when inline content is stable byte-for-byte. For style="..." and on* attributes, + browsers require 'unsafe-hashes' for hashes to apply.
Not to worry, cspresso will detect these and generate the hashes + in its response, offering them as style-src-attr options or with unsafe-hashes for older browsers. +
+
+
+ +
+

Evaluate (Report-Only)

+

+ Provide a CSP string to --evaluate and cspresso will inject it as + Content-Security-Policy-Report-Only on HTML responses during the crawl. If any violations are detected, it exits with code 1. +

+

This is a great way of testing a cspresso-brewed CSP before actually adding it to your site.

+ +
+ +
cspresso https://example.com \
+  --bypass-csp \
+  --evaluate "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;" \
+  --json
+
+ +
+ Recommendation: use --bypass-csp with --evaluate so existing CSP enforcement doesn’t change the page’s behaviour during testing. +
+
+ +
+

Flags

+

A condensed reference of the most-used options:

+ +
+
+
Crawling
+
--max-pages --timeout-ms --settle-ms --headed
+
+
+
Policy shaping
+
--include-sourcemaps --upgrade-insecure-requests --allow-blob --unsafe-eval
+
+
+
Playwright
+
--browsers-path --no-install --with-deps
+
+
+
Evaluation
+
--bypass-csp --evaluate --ignore-non-html --json
+
+
+ +
+ +
+
+
+
diff --git a/src/content/evaluate.html b/src/content/evaluate.html new file mode 100644 index 0000000..5fd64fd --- /dev/null +++ b/src/content/evaluate.html @@ -0,0 +1,100 @@ +--- +title: "cspresso Evaluate" +description: "Evaluate a candidate Content-Security-Policy by injecting it as CSP Report-Only and failing if violations are detected." +--- +
+
+
+
Evaluate
+

Test a CSP before you enforce it

+

+ Use --evaluate to inject a candidate policy as Content-Security-Policy-Report-Only, + collect violations, and fail the run if anything would break. +

+
+ +
+
+
+
+ +
cspresso https://example.com \
+  --bypass-csp \
+  --evaluate "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;" \
+  --json
+
+
+
+
+
Exit codes
+
+
0 → no Report‑Only violations detected
+
1 → violations detected (ideal for CI gates)
+
+
+
+ Tip: keep your CSP string quoted; it usually contains spaces and semicolons. +
+
+
+
+
+ +
+

Why --bypass-csp matters

+

+ If the target site already sets an enforcing CSP, it can block loads and change runtime behaviour. + That can hide potential violations in your candidate policy. Using --bypass-csp strips existing CSP headers + on HTML responses during the crawl. +

+
+
Safety note
+
+ Bypassing CSP means you’re letting the page execute without those protections. Run evaluation only on sites you trust, + or in a sandboxed environment. +
+
+
+ +
+

CI example (GitHub Actions)

+
+ +
- name: Evaluate CSP
+  run: |
+    pipx install cspresso
+    cspresso https://example.com \
+      --bypass-csp \
+      --evaluate "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;" \
+      --json
+
+
+ If you cache Playwright browsers, set --browsers-path to a persistent directory. +
+
+ +
+

Troubleshooting

+
+
+
+
Sourcemaps causing connect-src noise
+
+ DevTools often fetches sourcemaps even when headless browsing doesn’t. If you want to model those requests, + use --include-sourcemaps to add sourcemap origins to connect-src. +
+
+
+
+
+
Non-HTML crawled resources
+
+ If your site has downloadable files on the same origin, consider --ignore-non-html to avoid edge cases + like browser “word-wrap” injected styles affecting hashes. +
+
+
+
+
+
+
diff --git a/src/content/recipes.html b/src/content/recipes.html new file mode 100644 index 0000000..4a5efab --- /dev/null +++ b/src/content/recipes.html @@ -0,0 +1,87 @@ +--- +title: "cspresso Recipes" +description: "cspresso recipes: common commands for crawling, debugging, sourcemaps, AppImage usage, and CI evaluation." +aliases: + - "examples.html" +--- +
+
+
+
Recipes
+

Practical workflows

+

A handful of commands that cover most real-world cspresso usage.

+
+ +
+
+

Draft a CSP

+
+ +
cspresso https://example.com --max-pages 10
+
+
+ Start here, then audit the output. Crawls won’t cover every flow (auth-only pages, conditional loads, A/B tests, etc.). +
+
+ +
+

Headed debugging

+
+ +
cspresso https://example.com --headed --settle-ms 2500
+
+
+ Useful when the site does delayed loads or you want to visually confirm what’s happening during the crawl. +
+
+ +
+

Sourcemaps & connect-src

+
+ +
cspresso https://example.com --include-sourcemaps
+
+
+ If browsers/devtools fetch *.map files from a CDN, this helps make sure the CDN origin lands in connect-src. +
+
+ +
+

Upgrade insecure requests

+
+ +
cspresso https://example.com --upgrade-insecure-requests
+
+
+ Handy during migrations when you still have a few stray HTTP URLs. +
+
+ +
+

AppImage (writable browser cache)

+
+ +
./cspresso.AppImage https://example.com \
+  --browsers-path "$HOME/.cache/cspresso/pw-browsers"
+
+
+ AppImages mount read-only. Set --browsers-path so Playwright can install Chromium into a writable directory. +
+
+ +
+

CI gate with --evaluate

+
+ +
cspresso https://example.com \
+  --bypass-csp \
+  --evaluate "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;" \
+  --json
+
+
+ Exits 1 if the candidate policy would be violated - great for PR checks. +
+
+
+
+
diff --git a/src/content/security.html b/src/content/security.html new file mode 100644 index 0000000..e03a402 --- /dev/null +++ b/src/content/security.html @@ -0,0 +1,73 @@ +--- +title: "cspresso Security" +description: "Security notes for cspresso: it runs a real browser; what bypassing existing CSP means; and how to use it safely." +--- +
+
+
+
Security
+

Security notes

+

+ cspresso runs a real browser. That’s the point - and also the main safety consideration. +

+
+ +
+
+
+

What cspresso does

+

+ cspresso launches Chromium via Playwright and loads your target pages. The site’s JavaScript and CSS execute like a normal browser session. + Network requests are observed to build a draft CSP, and (optionally) a candidate policy is injected as Report‑Only to capture violations. +

+
+ +
+

About --bypass-csp

+
+
It can change risk
+
+ Bypassing CSP strips existing CSP headers on HTML responses. This option is provided in order to avoid the outcome of the rendering negatively influencing what cspresso thinks a good CSP should be. + If a site is compromised, CSP might have been limiting what injected scripts could do (that's the whole point of a CSP!); bypassing removes that layer. +
+
+
+ Recommendation: only use --bypass-csp on sites you trust, or run cspresso inside a sandboxed environment (VM/container). +
+
+
+ +
+

Data handling

+

+ cspresso’s primary output is a policy string and metadata (visited URLs, notes, and - in evaluation mode - detected violations). + Treat the output as sensitive if your site URLs or CSP reveal internal endpoints. +

+
+ +
+

Hardening tips

+
+
+
Prefer CI / disposable environments
+
Running in CI makes it easy to isolate and to cache Chromium via --browsers-path.
+
+
+
Limit crawl scope
+
Keep --max-pages small and start from a stable landing page to reduce surprises.
+
+
+
Review before enforcing
+
cspresso emits a draft. Tighten directives (especially script-src/connect-src) and consider nonces.
+
+
+
Verify releases
+
mig5 key: https://mig5.net/static/mig5.asc
Fingerprint: 00AE817C24A10C2540461A9C1D7CDE0234DB458D
+
+
+
+
+ +
+
+
diff --git a/src/examples.html b/src/examples.html deleted file mode 100644 index 73af062..0000000 --- a/src/examples.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - cspresso - Recipes - - - - -

Redirecting to recipes.html

- - diff --git a/src/hugo.toml b/src/hugo.toml new file mode 100644 index 0000000..df47864 --- /dev/null +++ b/src/hugo.toml @@ -0,0 +1,11 @@ +baseURL = "https://cspresso.cafe/" +languageCode = "en" +title = "cspresso" +disableKinds = ["taxonomy", "term"] +uglyURLs = true +enableRobotsTXT = true + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true diff --git a/src/layouts/_default/baseof.html b/src/layouts/_default/baseof.html new file mode 100644 index 0000000..22fc5cc --- /dev/null +++ b/src/layouts/_default/baseof.html @@ -0,0 +1,30 @@ + + + + + + {{ .Title }} + {{ with .Description }}{{ end }} + + + + + + + + + + + + + {{ with .Description }}{{ end }} + + + +{{ partial "nav.html" . }} +{{ block "main" . }}{{ end }} +{{ partial "footer.html" . }} + + + + diff --git a/src/layouts/_default/single.html b/src/layouts/_default/single.html new file mode 100644 index 0000000..3a342e5 --- /dev/null +++ b/src/layouts/_default/single.html @@ -0,0 +1,3 @@ +{{ define "main" }} +{{ .Content | safeHTML }} +{{ end }} diff --git a/src/layouts/index.html b/src/layouts/index.html new file mode 100644 index 0000000..3a342e5 --- /dev/null +++ b/src/layouts/index.html @@ -0,0 +1,3 @@ +{{ define "main" }} +{{ .Content | safeHTML }} +{{ end }} diff --git a/src/layouts/partials/footer.html b/src/layouts/partials/footer.html new file mode 100644 index 0000000..1922cdb --- /dev/null +++ b/src/layouts/partials/footer.html @@ -0,0 +1,41 @@ + diff --git a/src/layouts/partials/nav.html b/src/layouts/partials/nav.html new file mode 100644 index 0000000..634a366 --- /dev/null +++ b/src/layouts/partials/nav.html @@ -0,0 +1,29 @@ +{{ $here := .RelPermalink }} + diff --git a/src/assets/css/site.css b/src/public/assets/css/site.css similarity index 100% rename from src/assets/css/site.css rename to src/public/assets/css/site.css diff --git a/src/assets/img/cspresso.svg b/src/public/assets/img/cspresso.svg similarity index 100% rename from src/assets/img/cspresso.svg rename to src/public/assets/img/cspresso.svg diff --git a/src/assets/img/hamburger.svg b/src/public/assets/img/hamburger.svg similarity index 100% rename from src/assets/img/hamburger.svg rename to src/public/assets/img/hamburger.svg diff --git a/src/assets/js/site.js b/src/public/assets/js/site.js similarity index 100% rename from src/assets/js/site.js rename to src/public/assets/js/site.js diff --git a/src/docs.html b/src/public/docs.html similarity index 91% rename from src/docs.html rename to src/public/docs.html index a7f0552..cb86710 100644 --- a/src/docs.html +++ b/src/public/docs.html @@ -5,7 +5,7 @@ cspresso Docs - + @@ -14,17 +14,18 @@ - + + + +
@@ -61,7 +68,7 @@
- Prefer canonical docs? See the README. + Prefer canonical docs? See the README.
@@ -197,12 +204,13 @@ pip install cspresso
+ + - + diff --git a/src/evaluate.html b/src/public/evaluate.html similarity index 88% rename from src/evaluate.html rename to src/public/evaluate.html index 5ec9913..d28d861 100644 --- a/src/evaluate.html +++ b/src/public/evaluate.html @@ -5,7 +5,7 @@ cspresso Evaluate - + @@ -14,17 +14,18 @@ - + + + +
@@ -142,12 +149,13 @@
+
+ - + diff --git a/src/public/examples.html b/src/public/examples.html new file mode 100644 index 0000000..eeb1e81 --- /dev/null +++ b/src/public/examples.html @@ -0,0 +1,10 @@ + + + + https://cspresso.cafe/recipes.html + + + + + + diff --git a/src/favicon.ico b/src/public/favicon.ico similarity index 100% rename from src/favicon.ico rename to src/public/favicon.ico diff --git a/src/index.html b/src/public/index.html similarity index 94% rename from src/index.html rename to src/public/index.html index 13eda37..eb83f8a 100644 --- a/src/index.html +++ b/src/public/index.html @@ -1,11 +1,12 @@ + cspresso - Brew a Content Security Policy - + @@ -14,17 +15,18 @@ - + + + +
@@ -350,12 +358,13 @@ pip install cspresso
+
+ - + diff --git a/src/public/index.xml b/src/public/index.xml new file mode 100644 index 0000000..a342def --- /dev/null +++ b/src/public/index.xml @@ -0,0 +1,39 @@ + + + + cspresso - Brew a Content Security Policy on cspresso + https://cspresso.cafe/ + Recent content in cspresso - Brew a Content Security Policy on cspresso + Hugo + en + + + cspresso Docs + https://cspresso.cafe/docs.html + Mon, 01 Jan 0001 00:00:00 +0000 + https://cspresso.cafe/docs.html + On this page Install Run Output Inline scripts & styles Evaluate (Report-Only) Flags Prefer canonical docs? See the README. Docs Usage cspresso crawls up to --max-pages same-origin pages in Chromium, observes what loads, and emits a draft CSP. Install Copy # Recommended pipx install cspresso # Or plain pip (use a venv) pip install cspresso # An AppImage is also available on the # git repo Releases page. Python + Playwright You need Python 3. + + + cspresso Evaluate + https://cspresso.cafe/evaluate.html + Mon, 01 Jan 0001 00:00:00 +0000 + https://cspresso.cafe/evaluate.html + Evaluate Test a CSP before you enforce it Use --evaluate to inject a candidate policy as Content-Security-Policy-Report-Only, collect violations, and fail the run if anything would break. Copy cspresso https://example.com \ --bypass-csp \ --evaluate "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net;" \ --json Exit codes 0 → no Report‑Only violations detected 1 → violations detected (ideal for CI gates) Tip: keep your CSP string quoted; it usually contains spaces and semicolons. Why --bypass-csp matters If the target site already sets an enforcing CSP, it can block loads and change runtime behaviour. + + + cspresso Recipes + https://cspresso.cafe/recipes.html + Mon, 01 Jan 0001 00:00:00 +0000 + https://cspresso.cafe/recipes.html + Recipes Practical workflows A handful of commands that cover most real-world cspresso usage. Draft a CSP Copy cspresso https://example.com --max-pages 10 Start here, then audit the output. Crawls won’t cover every flow (auth-only pages, conditional loads, A/B tests, etc.). Headed debugging Copy cspresso https://example.com --headed --settle-ms 2500 Useful when the site does delayed loads or you want to visually confirm what’s happening during the crawl. Sourcemaps & connect-src Copy cspresso https://example. + + + cspresso Security + https://cspresso.cafe/security.html + Mon, 01 Jan 0001 00:00:00 +0000 + https://cspresso.cafe/security.html + Security Security notes cspresso runs a real browser. That’s the point - and also the main safety consideration. What cspresso does cspresso launches Chromium via Playwright and loads your target pages. The site’s JavaScript and CSS execute like a normal browser session. Network requests are observed to build a draft CSP, and (optionally) a candidate policy is injected as Report‑Only to capture violations. About --bypass-csp It can change risk Bypassing CSP strips existing CSP headers on HTML responses. + + + diff --git a/src/recipes.html b/src/public/recipes.html similarity index 88% rename from src/recipes.html rename to src/public/recipes.html index d0107f9..5e93ff8 100644 --- a/src/recipes.html +++ b/src/public/recipes.html @@ -5,7 +5,7 @@ cspresso Recipes - + @@ -14,17 +14,18 @@ - + + + +
@@ -127,12 +134,13 @@
+
+ - + diff --git a/src/public/robots.txt b/src/public/robots.txt new file mode 100644 index 0000000..4f9540b --- /dev/null +++ b/src/public/robots.txt @@ -0,0 +1 @@ +User-agent: * \ No newline at end of file diff --git a/src/security.html b/src/public/security.html similarity index 88% rename from src/security.html rename to src/public/security.html index eda79ef..3762202 100644 --- a/src/security.html +++ b/src/public/security.html @@ -5,7 +5,7 @@ cspresso Security - + @@ -14,17 +14,18 @@ - + + + +
@@ -115,12 +122,13 @@
+
+ - + diff --git a/src/public/sitemap.xml b/src/public/sitemap.xml new file mode 100644 index 0000000..c208a26 --- /dev/null +++ b/src/public/sitemap.xml @@ -0,0 +1,15 @@ + + + + https://cspresso.cafe/ + + https://cspresso.cafe/docs.html + + https://cspresso.cafe/evaluate.html + + https://cspresso.cafe/recipes.html + + https://cspresso.cafe/security.html + + diff --git a/src/static/assets/css/site.css b/src/static/assets/css/site.css new file mode 100644 index 0000000..f15e34d --- /dev/null +++ b/src/static/assets/css/site.css @@ -0,0 +1,167 @@ +:root{ + --cspresso-mint:#2DD4BF; + --cspresso-violet:#A78BFA; + --cspresso-ink:#0b1220; + --cspresso-ink-2:#111827; + --cspresso-cream:#F6F0E7; + + --bs-link-color: var(--cspresso-mint); + --bs-link-hover-color: var(--cspresso-violet); + --bs-link-color-rgb: 45, 212, 191; + --bs-link-hover-color-rgb: 167, 139, 250; + + --bs-nav-pills-link-active-bg: rgba(45,212,191,0.18); + --bs-nav-pills-link-active-color: var(--cspresso-ink); +} + +html{scroll-behavior:smooth;} + +body{ + font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; + color: var(--cspresso-ink); +} + +.navbar{ + backdrop-filter: blur(10px); +} + +.navbar-toggler-icon{ + background-image: url("../img/hamburger.svg"); +} + +.brand-mark{width:84px;height:84px;} + +.hero{ + background: + radial-gradient(900px 600px at 15% 15%, rgba(45,212,191,0.28), rgba(45,212,191,0.00) 60%), + radial-gradient(900px 600px at 85% 35%, rgba(167,139,250,0.26), rgba(167,139,250,0.00) 60%), + linear-gradient(180deg, rgba(11,18,32,0.02), rgba(11,18,32,0.00)); +} + +.hero .lead{ + color: rgba(15,23,42,0.78); +} + +.hero-card{ + background: rgba(255,255,255,0.70); + border: 1px solid rgba(15,23,42,0.08); + box-shadow: 0 18px 48px rgba(15,23,42,0.10); + border-radius: 1.25rem; +} + +.kicker{ + display: inline-flex; + align-items: center; + gap: .5rem; + font-weight: 600; + font-size: .95rem; + padding: .45rem .75rem; + border-radius: 999px; + background: rgba(45,212,191,0.12); + border: 1px solid rgba(45,212,191,0.25); + color: var(--cspresso-ink); +} + +.section-title{ + letter-spacing: -0.02em; +} + +.icon-pill{ + width: 42px; + height: 42px; + border-radius: 14px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(45,212,191,0.12); + border: 1px solid rgba(45,212,191,0.25); + color: var(--cspresso-ink); +} + +.feature-card{ + border: 1px solid rgba(15,23,42,0.08); + border-radius: 1.25rem; + box-shadow: 0 12px 30px rgba(15,23,42,0.06); +} + +.terminal{ + background: #0b1220; + color: #e5e7eb; + border-radius: 1rem; + padding: 1.25rem; + border: 1px solid rgba(255,255,255,0.06); + box-shadow: 0 18px 52px rgba(11,18,32,0.35); +} + +.terminal .prompt{ color: #93c5fd; } +.terminal code{ color: #e5e7eb; } + +.codeblock{ + position: relative; +} + +.copy-btn{ + position: absolute; + top: .75rem; + right: .75rem; +} + +.badge-soft{ + background: rgba(15,23,42,0.06); + border: 1px solid rgba(15,23,42,0.10); + color: rgba(15,23,42,0.85); +} + +.callout{ + border: 1px solid rgba(15,23,42,0.10); + border-radius: 1rem; + background: rgba(255,255,255,0.78); +} + +footer{ + background: + radial-gradient(900px 300px at 20% 0%, rgba(45,212,191,0.12), rgba(45,212,191,0.00) 60%), + radial-gradient(900px 300px at 80% 0%, rgba(167,139,250,0.10), rgba(167,139,250,0.00) 60%), + linear-gradient(180deg, #ffffff 0%, #f8fafc 100%); + border-top: 1px solid rgba(15,23,42,0.06); +} + +.smallprint{ color: rgba(15,23,42,0.65); } + +/* Make anchor scrolling nicer under sticky nav */ +.scroll-mt-nav{ scroll-margin-top: 90px; } + +a{ text-decoration-color: rgba(45,212,191,0.55); } +a:hover{ text-decoration-color: rgba(167,139,250,0.85); } + +/* Quickstart pills */ +.nav-pills .nav-link{ + border: 1px solid rgba(45,212,191,0.25); + color: var(--cspresso-ink); + background: rgba(45,212,191,0.10); + border-radius: 999px; +} +.nav-pills .nav-link:hover{ + background: rgba(167,139,250,0.10); + border-color: rgba(45,212,191,0.55); +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link{ + background: rgba(45,212,191,0.18); + color: var(--cspresso-ink); + border-color: rgba(15,23,42,0.18); +} + + +.chip{ + display: inline-flex; + align-items: center; + gap: .35rem; + padding: .25rem .55rem; + border-radius: 999px; + font-size: .85rem; + background: rgba(15,23,42,0.06); + border: 1px solid rgba(15,23,42,0.10); + color: rgba(15,23,42,0.88); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} diff --git a/src/static/assets/img/cspresso.svg b/src/static/assets/img/cspresso.svg new file mode 100644 index 0000000..b95751d --- /dev/null +++ b/src/static/assets/img/cspresso.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 'csp' + + + diff --git a/src/static/assets/img/hamburger.svg b/src/static/assets/img/hamburger.svg new file mode 100644 index 0000000..5994d3e --- /dev/null +++ b/src/static/assets/img/hamburger.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/static/assets/js/site.js b/src/static/assets/js/site.js new file mode 100644 index 0000000..6866eb2 --- /dev/null +++ b/src/static/assets/js/site.js @@ -0,0 +1,60 @@ +(function(){ + // Copy-to-clipboard for code blocks + function setupCopyButtons(){ + document.querySelectorAll('[data-copy-target]').forEach(function(btn){ + btn.addEventListener('click', async function(){ + var sel = btn.getAttribute('data-copy-target'); + var el = document.querySelector(sel); + if(!el) return; + var text = el.innerText || el.textContent || ''; + try{ + await navigator.clipboard.writeText(text.trim()); + var old = btn.innerHTML; + btn.innerHTML = 'Copied'; + btn.classList.add('btn-success'); + btn.classList.remove('btn-outline-secondary'); + setTimeout(function(){ + btn.innerHTML = old; + btn.classList.remove('btn-success'); + btn.classList.add('btn-outline-secondary'); + }, 1200); + }catch(e){ + // Fallback + var ta = document.createElement('textarea'); + ta.value = text.trim(); + document.body.appendChild(ta); + ta.select(); + try{ document.execCommand('copy'); }catch(_){} + document.body.removeChild(ta); + } + }); + }); + } + + // Asciinema embed helper: + // Put
+ // Or provide a self-hosted player by swapping the script URL. + function setupAsciinema(){ + document.querySelectorAll('.asciicast[data-asciinema-id]').forEach(function(el){ + var id = el.getAttribute('data-asciinema-id'); + if(!id || id === 'REPLACE_ME'){ + el.innerHTML = '
Add your asciinema id here: data-asciinema-id.
'; + return; + } + // Avoid injecting twice + if(document.getElementById('asciinema-embed-'+id)) return; + + var s = document.createElement('script'); + s.src = 'https://asciinema.org/a/' + encodeURIComponent(id) + '.js'; + s.id = 'asciinema-embed-'+id; + s.async = true; + // The script replaces a placeholder div with the player if it's directly after it. + el.appendChild(s); + }); + } + + document.addEventListener('DOMContentLoaded', function(){ + setupCopyButtons(); + setupAsciinema(); + }); +})(); diff --git a/src/static/favicon.ico b/src/static/favicon.ico new file mode 100644 index 0000000..7d6a071 Binary files /dev/null and b/src/static/favicon.ico differ diff --git a/upload.sh b/upload.sh index 79697a4..89c4ed4 100755 --- a/upload.sh +++ b/upload.sh @@ -5,6 +5,10 @@ set -eou pipefail SRC="src" DEST="/opt/www/cspresso.cafe" -rsync -aHPvz ${SRC}/ root@lupin.mig5.net:${DEST}/ +DEBIAN_FRONTEND=noninteractive sudo apt-get install -y --no-install-recommends hugo + +cd "${SRC}" && hugo +cd ../ +rsync -aHPvz ${SRC}/public/ root@lupin.mig5.net:${DEST}/ ssh root@lupin.mig5.net "chown -R web:web ${DEST}"