From 649ed74317a2db50967decf704161e8ee4da48fa Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sun, 7 Jun 2026 21:29:06 -0700 Subject: [PATCH] lith: fix stale-code-after-deploy (short TTL for code modules + CDN purge) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runtime .mjs (kidlisp/disk/graph/…) are STATIC sub-imports of disk.mjs, so they don't inherit the ?v=Date.now() cache-bust boot.mjs applies to top-level modules — they were served from a 1h Cloudflare edge cache, pinning clients to pre-deploy code. - Caddyfile: split code modules (.mjs/.js/.css/.lisp/.lua) into a short-TTL bucket (max-age=60, swr=300); static assets (fonts/images/media/json) keep the 1h/24h cache. - deploy.fish: purge the Cloudflare cache (purge_everything) after restart, so a deploy is visible immediately. Token read from env or vault service env; skips cleanly if absent. Co-Authored-By: Claude Opus 4.8 (1M context) --- lith/Caddyfile | 13 +++++++++++-- lith/deploy.fish | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lith/Caddyfile b/lith/Caddyfile index 4944a4f42e..2b519e8195 100644 --- a/lith/Caddyfile +++ b/lith/Caddyfile @@ -15,8 +15,17 @@ level INFO } # --- Global performance headers --- - # Cache static assets (1h fresh, serve stale for 24h while revalidating) - @cacheable path *.mjs *.js *.css *.woff2 *.woff *.ttf *.png *.jpg *.jpeg *.svg *.gif *.webp *.ico *.mp3 *.wav *.mp4 *.json + # Code modules change every deploy — short TTL so rollouts are visible fast. + # (lib/disk .mjs are STATIC sub-imports without the ?v= cache-bust boot.mjs + # applies to top-level modules, so a long TTL pins clients to stale runtime + # code. The deploy script also purges the CDN; this bounds browser-side + # staleness to ~1min.) + @code path *.mjs *.js *.css *.lisp *.lua + header @code Cache-Control "public, max-age=60, stale-while-revalidate=300" + header @code Access-Control-Allow-Origin * + + # Static assets: long cache (1h fresh, serve stale for 24h while revalidating) + @cacheable path *.woff2 *.woff *.ttf *.png *.jpg *.jpeg *.svg *.gif *.webp *.ico *.mp3 *.wav *.mp4 *.json header @cacheable Cache-Control "public, max-age=3600, stale-while-revalidate=86400" header @cacheable Access-Control-Allow-Origin * diff --git a/lith/deploy.fish b/lith/deploy.fish index f4e0a52c16..2c28946bff 100644 --- a/lith/deploy.fish +++ b/lith/deploy.fish @@ -261,6 +261,47 @@ systemctl reload caddy" echo -e "$GREEN-> Restarting lith...$NC" ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "systemctl restart lith" +# Purge the Cloudflare cache so new code is served immediately. Runtime .mjs +# (kidlisp, disk, graph, …) are STATIC sub-imports without the ?v= cache-bust +# boot.mjs puts on top-level modules, so the edge would otherwise serve stale +# code until the TTL. Token comes from the env or the vault service env; if +# absent we skip (the short Caddy TTL still bounds staleness). +function get_cf_token + if set -q CLOUDFLARE_API_TOKEN + echo $CLOUDFLARE_API_TOKEN + return 0 + end + for token_file in $SERVICE_ENV \ + "$VAULT_DIR/help/deploy.env" "$VAULT_DIR/oven/deploy.env" "$VAULT_DIR/at/deploy.env" + test -f $token_file; or continue + set line (rg -m1 '^CLOUDFLARE_API_TOKEN=' $token_file) + if test -n "$line" + string replace -r '^CLOUDFLARE_API_TOKEN=' '' -- $line + return 0 + end + end + return 1 +end + +echo -e "$GREEN-> Purging Cloudflare cache...$NC" +set CF_TOKEN (get_cf_token) +if test -z "$CF_TOKEN" + echo -e "$YELLOW No CLOUDFLARE_API_TOKEN found — skipping purge (Caddy short TTL still applies).$NC" +else + set CF_ZONE (curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=aesthetic.computer" \ + -H "Authorization: Bearer $CF_TOKEN" -H "content-type: application/json" \ + | python3 -c "import json,sys; r=json.load(sys.stdin).get('result') or []; print(r[0]['id'] if r else '')" 2>/dev/null) + if test -z "$CF_ZONE" + echo -e "$YELLOW Could not resolve zone id — skipping purge.$NC" + else + set CF_RESULT (curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE/purge_cache" \ + -H "Authorization: Bearer $CF_TOKEN" -H "content-type: application/json" \ + --data '{"purge_everything":true}' \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if d.get('success') else 'failed: '+str(d.get('errors')))" 2>/dev/null) + echo -e "$GREEN purge: $CF_RESULT$NC" + end +end + echo -e "$GREEN-> Done. lith deployed to $TARGET_HOST$NC" # Mirror slab/menuband/ to its standalone GitHub repo. Runs after a -- 2.51.2