#!/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, 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"