From eb07184c3e7a2b16f48aba3924985ebccdeba5d1 Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Sat, 13 Jun 2026 09:04:17 +0200 Subject: [PATCH] deploy: VPS + Cloudflare bundle (nginx vhosts, release script, setup guide) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wadamesh.com distribution stack, ready to drop onto a new VPS behind Cloudflare: - deploy/nginx/tiles.wadamesh.com.conf — OSM tile proxy (HTTP origin, double-quoted location regex, resolver + variable proxy_pass, OSM UA, 14d disk cache) - deploy/nginx/firmware.wadamesh.com.conf — bins + the beta_ update-check listing - scripts/release.sh — build both boards tagged, refresh the listing, rsync to the VPS - deploy/README.md — VPS + Cloudflare DNS/SSL/cache setup, publish flow VPS target supplied via $WADAMESH_VPS at publish time — no IP/SSH in the repo (Cloudflare fronts the origin). Web flasher + OTA re-enable are the remaining pre-launch pieces (noted in deploy/README.md). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Kaj Schittecat --- deploy/README.md | 67 +++++++++++++++++++++++++ deploy/nginx/firmware.wadamesh.com.conf | 42 ++++++++++++++++ deploy/nginx/tiles.wadamesh.com.conf | 49 ++++++++++++++++++ scripts/release.sh | 58 +++++++++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/nginx/firmware.wadamesh.com.conf create mode 100644 deploy/nginx/tiles.wadamesh.com.conf create mode 100755 scripts/release.sh diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..037e408 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,67 @@ +# wadamesh.com infrastructure + +Distribution stack for wadamesh: a **VPS nginx origin behind Cloudflare**. + +``` +tiles.wadamesh.com →CF (HTTP, edge-cached) → nginx → tile.openstreetmap.org +firmware.wadamesh.com →CF (cache bins) → nginx → /srv/wadamesh/firmware +flasher.wadamesh.com →CF (HTTPS) → web flasher (TODO — see below) +wadamesh.com →CF (HTTPS) → landing page (later) +``` + +The firmware fetches **tiles + the update-check over plain HTTP** (on-device HTTPS +isn't viable — mbedTLS needs ~30 KB heap, only ~5 KB is free post-Wi-Fi), so the +tile + firmware hosts must stay reachable over HTTP. Cloudflare provides the edge +cache, HTTPS for the flasher, and hides the origin IP (so no IP lives in this repo +or the firmware). + +## 1. VPS (origin) + +```bash +sudo apt install nginx +sudo mkdir -p /srv/wadamesh/firmware/releases/TOUCH /var/cache/nginx/wadamesh-tiles +sudo cp deploy/nginx/tiles.wadamesh.com.conf /etc/nginx/sites-available/ +sudo cp deploy/nginx/firmware.wadamesh.com.conf /etc/nginx/sites-available/ +sudo ln -s /etc/nginx/sites-available/tiles.wadamesh.com.conf /etc/nginx/sites-enabled/ +sudo ln -s /etc/nginx/sites-available/firmware.wadamesh.com.conf /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx +``` + +## 2. Cloudflare + +- **DNS:** `A`/`AAAA` records for `tiles`, `firmware`, `flasher`, `@` → the VPS IP, + all **Proxied** (orange cloud). +- **SSL/TLS:** mode **Flexible** (CF↔origin HTTP) is enough since the origin is + HTTP-only. **Do NOT enable "Always Use HTTPS"** on `tiles.` or `firmware.` — the + firmware needs plain HTTP there. +- **Cache Rules:** + - `tiles.wadamesh.com/*` → Eligible for cache, Edge TTL ~14d. + - `firmware.wadamesh.com/releases/*/*.bin` → cache, Edge TTL ~1d. + - `firmware.wadamesh.com/releases/TOUCH` (the listing) → short TTL (~60s) or + Bypass, so new releases appear promptly. + +## 3. Publishing a release + +From a wadamesh checkout (builds both boards, refreshes the listing, rsyncs up): + +```bash +WADAMESH_VPS=user@your-vps scripts/release.sh beta_2 +``` + +The on-device check GETs `http://firmware.wadamesh.com/releases/TOUCH`, finds the +highest `beta_`, and (once OTA-over-Wi-Fi is re-enabled) pulls +`…/releases/TOUCH/beta_/.bin`. + +## TODO before public launch + +- **Web flasher** (`flasher.wadamesh.com`) — esptool-js / Web Serial install page + with a board picker. Not yet built; the firmware already points "update manually + at flasher.wadamesh.com" here. +- **Re-enable OTA-over-Wi-Fi** in the firmware (currently it version-checks then + defers to manual flashing). +- **Landing page** at `wadamesh.com`. +- Decide tile-proxy sharing: dedicated `tiles.wadamesh.com` (this config) vs + reusing the meshcomod proxy. + +> Never commit the VPS IP, SSH keys, or `WADAMESH_VPS`. Cloudflare fronts the +> origin; the deploy target is supplied via the environment at publish time. diff --git a/deploy/nginx/firmware.wadamesh.com.conf b/deploy/nginx/firmware.wadamesh.com.conf new file mode 100644 index 0000000..7507c55 --- /dev/null +++ b/deploy/nginx/firmware.wadamesh.com.conf @@ -0,0 +1,42 @@ +# firmware.wadamesh.com — wadamesh firmware bins + the update-check feed. +# +# Layout on disk (populated by scripts/release.sh, rsynced to the VPS): +# /srv/wadamesh/firmware/ +# releases/TOUCH/index.json <- listing the firmware scans for beta_ +# releases/TOUCH/beta_1/wadamesh-heltec-v4-tft.bin +# releases/TOUCH/beta_1/wadamesh-heltec-v4-tft-merged.bin +# releases/TOUCH/beta_1/wadamesh-tdeck.bin +# releases/TOUCH/beta_1/wadamesh-tdeck-merged.bin +# +# The on-device version check GETs http://firmware.wadamesh.com/releases/TOUCH and +# scans the body for the highest beta_. We serve index.json at exactly that +# path (no trailing-slash redirect, which the on-device HTTPClient may not follow). +# +# Cloudflare: firmware.wadamesh.com proxied; SSL "Full". Plain HTTP must remain +# reachable (the firmware checks over HTTP) — do NOT force HTTPS on this host. +# Cache Rule: cache .bin aggressively (Edge TTL ~1d); index.json short TTL (~60s) +# or bypass, so a new release shows up promptly. + +server { + listen 80; + listen [::]:80; + server_name firmware.wadamesh.com; + + root /srv/wadamesh/firmware; + + # The update-check feed — served at the no-slash path the firmware requests. + location = /releases/TOUCH { + default_type application/json; + alias /srv/wadamesh/firmware/releases/TOUCH/index.json; + add_header Cache-Control "public, max-age=60"; + } + + # Bins + everything else under /releases/. + location /releases/ { + autoindex off; + types { application/octet-stream bin; } + add_header Cache-Control "public, max-age=86400"; + } + + location / { return 404; } +} diff --git a/deploy/nginx/tiles.wadamesh.com.conf b/deploy/nginx/tiles.wadamesh.com.conf new file mode 100644 index 0000000..c113e44 --- /dev/null +++ b/deploy/nginx/tiles.wadamesh.com.conf @@ -0,0 +1,49 @@ +# tiles.wadamesh.com — OSM tile proxy for the wadamesh map tab. +# +# The firmware fetches plain HTTP (on-device HTTPS isn't workable: mbedTLS wants +# ~30 KB heap for a handshake and only ~5 KB is free after Wi-Fi associates), so +# this vhost is HTTP-only by design. Cloudflare sits in front and caches the +# .png tiles at the edge; this origin keeps a second 14-day disk cache. +# +# Deploy: copy to /etc/nginx/sites-available/, symlink into sites-enabled/, then +# nginx -t && systemctl reload nginx +# Cloudflare: tiles.wadamesh.com proxied (orange cloud), SSL/TLS mode "Flexible" +# (CF<->origin HTTP), and a Cache Rule: Eligible for cache + Edge TTL ~14d for +# path "/*". Do NOT enable "Always Use HTTPS" on this hostname. + +proxy_cache_path /var/cache/nginx/wadamesh-tiles levels=1:2 + keys_zone=wadamesh_tiles:10m max_size=4g inactive=14d use_temp_path=off; + +server { + listen 80; + listen [::]:80; + server_name tiles.wadamesh.com; + + # Re-resolve the upstream at request time. A literal hostname in proxy_pass + # fails with "no resolver defined" on a fresh box; a variable + resolver works. + resolver 1.1.1.1 8.8.8.8 valid=300s; + resolver_timeout 5s; + + # The location regex is DOUBLE-QUOTED on purpose: nginx's lexer otherwise + # treats { and } (quantifiers) as block delimiters and refuses to start. + location ~ "^/(\d{1,2})/(\d{1,7})/(\d{1,7})\.png$" { + set $osm_upstream "tile.openstreetmap.org"; + + proxy_pass https://$osm_upstream/$1/$2/$3.png; + proxy_ssl_server_name on; + proxy_set_header Host tile.openstreetmap.org; + # OSM tile usage policy requires a valid, identifying User-Agent. + proxy_set_header User-Agent "wadamesh-tile-proxy/1.0 (+https://wadamesh.com)"; + proxy_set_header Referer "https://wadamesh.com/"; + + proxy_cache wadamesh_tiles; + proxy_cache_valid 200 302 14d; + proxy_cache_valid 404 1h; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + proxy_cache_lock on; + add_header X-Cache-Status $upstream_cache_status; + expires 14d; + } + + location / { return 404; } +} diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..f2b94f8 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# wadamesh release: build tagged bins for both touch boards, refresh the +# update-check listing, and publish to the firmware VPS (behind Cloudflare). +# +# Usage: +# WADAMESH_VPS=user@your-vps WADAMESH_VPS_PATH=/srv/wadamesh/firmware \ +# scripts/release.sh beta_2 +# +# The VPS target comes from the environment — never commit it (Cloudflare fronts +# the origin, so the IP isn't needed in the firmware anyway). +set -euo pipefail + +TAG="${1:?usage: release.sh e.g. beta_2}" +PIO="${PIO:-$HOME/Library/Python/3.9/bin/pio}" +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +OUT="$ROOT/out/firmware" # local mirror of the VPS firmware root +REL="$OUT/releases/TOUCH" +DEST="${WADAMESH_VPS:-}"; DEST_PATH="${WADAMESH_VPS_PATH:-/srv/wadamesh/firmware}" + +declare -A ENVS=( + [heltec_v4_tft_companion_radio_usb_tcp_touch]=wadamesh-heltec-v4-tft + [LilyGo_TDeck_companion_radio_touch]=wadamesh-tdeck +) + +# 1. Pull the current published tree so the listing stays complete across tags. +if [ -n "$DEST" ]; then + mkdir -p "$OUT" + rsync -a "$DEST:$DEST_PATH/" "$OUT/" 2>/dev/null || echo "note: first publish (nothing to pull yet)" +fi +mkdir -p "$REL/$TAG" + +# 2. Build both boards (app + merged), tag + version embedded. +export PLATFORMIO_BUILD_FLAGS="-DFIRMWARE_RELEASE_TAG='\"${TAG}\"' -DFIRMWARE_VERSION='\"wadamesh ${TAG}\"'" +for env in "${!ENVS[@]}"; do + name="${ENVS[$env]}" + "$PIO" run -t mergebin -e "$env" + cp ".pio/build/$env/firmware.bin" "$REL/$TAG/$name.bin" + cp ".pio/build/$env/firmware-merged.bin" "$REL/$TAG/$name-merged.bin" +done + +# 3. Regenerate the update-check listing (the firmware scans the body for the +# highest beta_). JSON array of dir names — GitHub-contents shape. +python3 - "$REL" > "$REL/index.json" <<'PY' +import os, sys, json +rel = sys.argv[1] +betas = sorted(d for d in os.listdir(rel) + if d.startswith("beta_") and os.path.isdir(os.path.join(rel, d))) +print(json.dumps([{"name": b, "type": "dir"} for b in betas], indent=2)) +PY +echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in json.load(open(sys.argv[1]))))' "$REL/index.json")" + +# 4. Publish to the VPS (Cloudflare caches at the edge). +if [ -n "$DEST" ]; then + rsync -av "$OUT/" "$DEST:$DEST_PATH/" + echo "published $TAG -> $DEST:$DEST_PATH" +else + echo "WADAMESH_VPS not set — built + staged in $REL/$TAG only (no publish)." +fi