D35/R-1228: publish-site-cloudflare.sh (dry run, refusals); the contact form posts to api.felhom.eu
gates / gates (push) Successful in 3m15s

This commit is contained in:
admin committed 2026-10-30 10:54:29 +01:00
1 parent bd74a613bf
commit 53107f1b48
5 files changed
+88 -3

No files matched your search

+7
View File
@@ -1,3 +1,10 @@
## 2026-10-30 — `publish-site-cloudflare.sh`: the website and the installer to Cloudflare Pages (D35 / R-1228)
- Stages `website/` from `origin/main` and `scripts/` from the installer tag named in `manifests/webpage.yaml`; REFUSES
when the staged installer is not the tag's byte for byte, when a must-have page is missing, when the manifest names
more than one tag, or when a file is over Pages' 25 MiB; `--dry-run` uploads nothing. The upload is wrangler 3.80.0 in
`node:20.17.0-alpine`; the token (`sec run CF_PAGES_TOKEN`) reaches it as an environment variable name only.
## 2026-10-28 (midday) — the watch asks Docker Hub gently: spaced, retried on 429, tag lists cached 24 h (R-1334)
- **`security_watch.py`:** Docker Hub requests (`hub.docker.com`, `registry-1.docker.io`, `auth.docker.io`) go through
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# publish-site-cloudflare.sh [--dry-run] [--project NAME] — D35 / R-1228 (2026-10-30): the public website and the
# installer, uploaded to Cloudflare Pages. It replaces „push to main and DooPlex's git-sync serves it" for the copy on
# Cloudflare; DooPlex keeps serving its own copy until the DNS switch, and for 7 days after it as the way back.
#
# WHAT IT UPLOADS — the same files DooPlex serves today, from the same refs:
# / website/ from origin/main (DooPlex: git-sync --ref=main, nginx root website/)
# /scripts/ scripts/ from the installer TAG (DooPlex: git-sync --ref=installer-v<X>, nginx location /scripts/)
# The tag is read from manifests/webpage.yaml, so the two copies can never name different installers. Clean URLs
# (/kapcsolat → kapcsolat.html), 404.html and compression are Pages' own behaviour — nothing to configure.
#
# Run: sec run CF_PAGES_TOKEN -- scripts/publish-site-cloudflare.sh [--dry-run]
# --dry-run stage and check everything, print what WOULD be uploaded, upload nothing (no token needed).
# The token reaches wrangler as an environment variable NAME (docker -e NAME) — never on a command line.
set -euo pipefail
PROJECT=felhom-site
DRY=0
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY=1 ;;
--project) PROJECT="$2"; shift ;;
*) echo "usage: $0 [--dry-run] [--project NAME]" >&2; exit 2 ;;
esac
shift
done
ACCOUNT_ID="${CF_ACCOUNT_ID:-9e432dea3b6befc7c87a41316049f162}" # an account identifier, not a secret
WRANGLER="wrangler@3.80.0"
NODE_IMAGE="node:20.17.0-alpine"
REPO=$(git -C "$(dirname "$0")" rev-parse --show-toplevel)
git -C "$REPO" fetch -q origin main --tags
TAG=$(grep -oE -- '--ref=installer-v[0-9]+\.[0-9]+\.[0-9]+' "$REPO/manifests/webpage.yaml" | sort -u | sed 's/--ref=//')
if [ "$(printf '%s\n' "$TAG" | wc -l)" != 1 ] || [ -z "$TAG" ]; then
echo "REFUSED: manifests/webpage.yaml must name exactly ONE installer tag (found: ${TAG:-none})" >&2; exit 1
fi
git -C "$REPO" rev-parse -q --verify "refs/tags/$TAG" >/dev/null || { echo "REFUSED: tag $TAG does not exist" >&2; exit 1; }
SITE_REV=$(git -C "$REPO" rev-parse --short origin/main)
STAGE=$(mktemp -d)
trap 'rm -rf "$STAGE"' EXIT
mkdir -p "$STAGE/site"
git -C "$REPO" archive origin/main website | tar -x -C "$STAGE"
cp -a "$STAGE/website/." "$STAGE/site/"
git -C "$REPO" archive "$TAG" scripts | tar -x -C "$STAGE/site"
WANT=$(git -C "$REPO" show "$TAG:scripts/felhom-host-install.sh" | sha256sum | cut -d' ' -f1)
GOT=$(sha256sum "$STAGE/site/scripts/felhom-host-install.sh" | cut -d' ' -f1)
[ "$WANT" = "$GOT" ] || { echo "REFUSED: the staged installer is not the tag's ($GOT != $WANT)" >&2; exit 1; }
for must in index.html 404.html kapcsolat.html en/index.html sitemap.xml robots.txt; do
[ -f "$STAGE/site/$must" ] || { echo "REFUSED: $must is missing from the staged site" >&2; exit 1; }
done
FILES=$(find "$STAGE/site" -type f | wc -l)
BIG=$(find "$STAGE/site" -type f -size +25M | wc -l)
[ "$BIG" = 0 ] || { echo "REFUSED: $BIG file(s) over Pages' 25 MiB limit" >&2; exit 1; }
echo "project : $PROJECT (account $ACCOUNT_ID)"
echo "website : origin/main $SITE_REV"
echo "installer : $TAG — scripts/felhom-host-install.sh sha256 $GOT"
echo "files : $FILES ($(du -sh "$STAGE/site" | cut -f1))"
if [ "$DRY" = 1 ]; then
echo "DRY RUN — nothing uploaded."
exit 0
fi
: "${CF_PAGES_TOKEN:?run under: sec run CF_PAGES_TOKEN -- $0}"
export CLOUDFLARE_API_TOKEN="$CF_PAGES_TOKEN" CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID"
docker run --rm -e CLOUDFLARE_API_TOKEN -e CLOUDFLARE_ACCOUNT_ID -v "$STAGE/site":/site "$NODE_IMAGE" \
npx -y "$WRANGLER" pages deploy /site --project-name "$PROJECT" --branch main \
--commit-hash "$(git -C "$REPO" rev-parse origin/main)" --commit-message "website $SITE_REV + $TAG" --commit-dirty=true
echo "UPLOADED — check it from outside: the pages answer, and the installer's sha256 is $GOT"
+9 -1
View File
@@ -1,4 +1,12 @@
## 2026-10-23 — the privacy wording says what is true; the minimum machine is written down (D3 / R-985, D5 / R-991; operator rulings 2026-10-22 15:47) and D36's two sentences (R-1225, R-1226) — published on the operator's yes (day brief)
## 2026-10-30 — the site moves to Cloudflare Pages (D35 / R-1228); the contact form posts to api.felhom.eu
- **D35 (operator yes 2026-10-23; the switch approved 2026-10-30).** The site is uploaded to the Cloudflare Pages project
`felhom-site` by `scripts/publish-site-cloudflare.sh` (website from `main`, `/scripts/` from the installer tag). After
the DNS switch a push to `main` alone no longer changes the public site — run the script too.
- `kapcsolat` / `en/contact`: `formEndpoint` is `https://api.felhom.eu/api/contact` (the mailer stays on DooPlex, its
CORS already allows felhom.eu and www).
## 2026-10-23 — the privacy wording says what is true; the minimum machine is written down (D3 / R-985, D5 / R-991; operator rulings 2026-10-22 15:47) and D36's two sentences (R-1225, R-1226) — published on the operator's yes (day brief)
- **D36 (operator yes 2026-10-23, R-1225, R-1226).** `letoltes` / `en/download`: „A 8 GB az alapokat futtatja; a fotóalkalmazáshoz 16 GB kell.” / „8 GB runs the basics; the photo app needs 16 GB.” `gyik` / `en/faq` („Ti hozzáfértek az adataimhoz?”, the visible answer and its JSON-LD twin): „…csak titkosítva hagyják el az otthonodat (a távoli mentés)” became „Amit az otthonodon kívülről nyitsz meg, az a Cloudflare hálózatán halad át, és ott olvasható; a mentések csak titkosítva hagyják el az otthonodat.” / „What you open from outside your home passes through Cloudflare's network and is readable there; the backups leave your home only encrypted.” (`13-threat-model.md` §6, R-904).
+1 -1
View File
@@ -225,7 +225,7 @@
// 3. Cloudflare Workers: Deploy a worker that forwards to email
//
// For now, falls back to mailto: if no endpoint is configured.
formEndpoint: '/api/contact',
formEndpoint: 'https://api.felhom.eu/api/contact', // D35/R-1228: the site is on Cloudflare; the mailer stays on DooPlex
maxTotalFileSize: 20 * 1024 * 1024, // 20 MB
maxSingleFileSize: 10 * 1024 * 1024, // 10 MB per file
+1 -1
View File
@@ -225,7 +225,7 @@
// 3. Cloudflare Workers: Deploy a worker that forwards to email
//
// For now, falls back to mailto: if no endpoint is configured.
formEndpoint: '/api/contact',
formEndpoint: 'https://api.felhom.eu/api/contact', // D35/R-1228: the site is on Cloudflare; the mailer stays on DooPlex
maxTotalFileSize: 20 * 1024 * 1024, // 20 MB
maxSingleFileSize: 10 * 1024 * 1024, // 10 MB per file