Serve security headers via static-web-server, drop Cloudflare deploy path

Production (Dokploy/Docker) was sending no security headers at all -
static/_headers and wrangler.toml were Cloudflare Pages/Workers
config that static-web-server never reads. Added sws.toml with the
same CSP/security headers and the two redirects from _redirects,
wired into the Dockerfile. Removed wrangler.toml, static/_headers,
static/_redirects, the wrangler devDependency/deploy script, and
related .gitignore/.dockerignore entries since Cloudflare deploy is
no longer used.

Also fixed a console error in theme.js where the Utterances
postMessage call could fire before the comments iframe navigated to
utteranc.es.
This commit is contained in:
2026-08-19 12:31:44 +03:00
parent a49aa02886
commit f87b818201
11 changed files with 35 additions and 1589 deletions
-1
View File
@@ -1,6 +1,5 @@
public
node_modules
.wrangler
.git
manimations/*.*
/zola
-1
View File
@@ -1,6 +1,5 @@
public
node_modules
.wrangler
manimations/*.*
/zola
/zola-v*.tar.gz
+1 -1
View File
@@ -17,7 +17,7 @@ A personal blog (andwati.com — "Thoughts and Musings") built with [Zola](https
There is no JS/CSS build tooling beyond Zola itself: `compile_sass = true` in `zola.toml` means Sass under `sass/` is compiled directly by Zola.
Deployment is via Cloudflare (`wrangler.toml`, `npm run deploy` → `zola build && wrangler deploy`), serving `public/` as static assets on `andwati.com`/`www.andwati.com`.
Deployment is self-hosted via Dokploy: `Dockerfile` builds the site with Zola and serves `public/` with `static-web-server`, configured by `sws.toml` (security headers, redirects) at the image's working directory root.
## Content structure
+3 -3
View File
@@ -1,9 +1,8 @@
# Multi-stage build: generate llms.txt with Node, build the site with Zola,
# then serve the static output with static-web-server.
#
# This is the Dokploy/self-hosted deployment path. It's independent of the
# Cloudflare Pages path (`npm run deploy` / wrangler.toml) - that stays in
# place as an option, this doesn't replace it.
# This is the Dokploy/self-hosted deployment path (the only one - the prior
# Cloudflare Pages/Workers path has been retired).
FROM node:22-slim AS llms
WORKDIR /project
@@ -18,4 +17,5 @@ RUN ["/zola", "build"]
FROM ghcr.io/static-web-server/static-web-server:2
COPY --from=zola /project/public /public
COPY sws.toml /sws.toml
EXPOSE 80
+1 -1540
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -4,13 +4,11 @@
"devDependencies": {
"@resvg/resvg-js": "^2.6.2",
"satori": "^0.29.0",
"wawoff2": "^2.0.1",
"wrangler": "^4"
"wawoff2": "^2.0.1"
},
"scripts": {
"generate:llms": "node scripts/generate-llms.mjs",
"generate:og": "node scripts/generate-og-images.mjs",
"build": "npm run generate:llms && npm run generate:og && zola build",
"deploy": "npm run build && wrangler deploy"
"build": "npm run generate:llms && npm run generate:og && zola build"
}
}
-7
View File
@@ -1,7 +0,0 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://utteranc.es https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.google-analytics.com; font-src 'self'; connect-src 'self' https://www.google-analytics.com https://cloudflareinsights.com; frame-src https://www.youtube-nocookie.com https://utteranc.es; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
-2
View File
@@ -1,2 +0,0 @@
/security.txt /.well-known/security.txt 301
/index.xml /rss.xml 301
+7 -1
View File
@@ -58,7 +58,13 @@ document.addEventListener('DOMContentLoaded', () => {
const theme = htmlElement.classList.contains('dark')
? commentsEl.dataset.utterancesThemeDark
: commentsEl.dataset.utterancesThemeLight;
frame.contentWindow.postMessage({ type: 'set-theme', theme }, 'https://utteranc.es');
// The iframe briefly shares our origin before it navigates to utteranc.es,
// so posting with that targetOrigin throws until the navigation completes.
try {
frame.contentWindow.postMessage({ type: 'set-theme', theme }, 'https://utteranc.es');
} catch (e) {
// Ignore: frame hasn't navigated to utteranc.es yet.
}
};
new MutationObserver(syncUtterancesTheme).observe(htmlElement, {
attributes: true,
+21
View File
@@ -0,0 +1,21 @@
[advanced]
[[advanced.headers]]
source = "**"
[advanced.headers.headers]
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://utteranc.es https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.google-analytics.com; font-src 'self'; connect-src 'self' https://www.google-analytics.com https://cloudflareinsights.com; frame-src https://www.youtube-nocookie.com https://utteranc.es; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests"
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Permissions-Policy = "camera=(), microphone=(), geolocation=(), interest-cohort=()"
Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
[[advanced.redirects]]
source = "/security.txt"
destination = "/.well-known/security.txt"
kind = 301
[[advanced.redirects]]
source = "/index.xml"
destination = "/rss.xml"
kind = 301
-29
View File
@@ -1,29 +0,0 @@
name = "andwati-com"
compatibility_date = "2026-07-16"
[assets]
directory = "./public"
not_found_handling = "404-page"
[[routes]]
pattern = "andwati.com"
custom_domain = true
[[routes]]
pattern = "www.andwati.com"
custom_domain = true
[observability]
enabled = false
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1