# SNAPSHOT (read-only) of the live Caddy config. # Live source of truth: /home/jay/srv/caddy/caddy-config/Caddyfile (mounted into the 'caddy' container). # Captured so the upbeatbytes try_files {path} {path}.html change is tracked. Do not edit here expecting it to deploy. { email thejayman77@gmail.com } tjm77.com, www.tjm77.com { tls { dns cloudflare {env.CF_API_TOKEN} } root * /srv/sites/tjm77 file_server encode gzip zstd log { output file /data/access-tjm77.log } } jsj-designs.com, www.jsj-designs.com { tls { dns cloudflare {env.CF_API_TOKEN} } root * /srv/sites/jsj file_server encode gzip zstd log { output file /data/access-jsj.log } } # Canonical host = apex. www redirects to it BEFORE the app, so OAuth always # starts from the same host its callback uses (the ub_oauth cookie is host-only; # starting from www then bouncing to the apex callback loses it → error=google). www.upbeatbytes.com { tls { dns cloudflare {env.CF_API_TOKEN} } redir https://upbeatbytes.com{uri} permanent } upbeatbytes.com { tls { dns cloudflare {env.CF_API_TOKEN} } encode gzip zstd # Drop vuln-scanner probes for stacks we don't run. We're a SvelteKit SPA + FastAPI: # zero PHP, no WordPress, no exposed dotfiles — so these paths can NEVER be a real # user or a wanted search crawler (matching is path-only, never by User-Agent, so # Googlebot/Bing are untouched). Without this they fall through try_files to the SPA # shell and get a 200; now they get a clean 403 (still logged, so probes stay visible). @junk path *.php /wp-admin* /wp-login* /wp-includes* /wp-content* /wp-json* /xmlrpc.php /.env /.env.* /.git /.git/* /phpmyadmin* /pma* /myadmin* /dbadmin* /vendor/* /.aws/* /.ssh/* /cgi-bin/* /administrator/* handle @junk { respond 403 } # Retired prototype routes (promoted/removed at the news relaunch) → the hub. @oldhome path /home2 /home2.html /home3 /home3.html handle @oldhome { redir https://upbeatbytes.com/ permanent } # Dynamic API + server-rendered pages (share, digest, sitemap) → FastAPI. @api path /api/* /healthz /docs /docs/* /openapi.json /a/* /today /sitemap.xml handle @api { reverse_proxy upbeatbytes-api:8000 } # Everything else → the static SvelteKit SPA. try_files falls back to # index.html so deep client routes (e.g. /auth/verify) boot the app # instead of 404ing. handle { root * /srv/sites/upbeatbytes # Hidden in-progress prototypes — keep crawlers out at the HTTP level (the JS # isn't seen by non-JS bots since the static shell is generic). # Only admin stays out of the index now — news, art, play, and the joy pages are public. @hidden path /admin /admin.html header @hidden X-Robots-Tag "noindex, nofollow" # Content-hashed assets never change for a given URL — cache them forever. @immutable path /_app/immutable/* header @immutable Cache-Control "public, max-age=31536000, immutable" # Static texture + font assets — large/unchanging. Cache them forever like # immutable assets; rename the file if one ever changes. @assets path /textures/* /fonts/* header @assets Cache-Control "public, max-age=31536000, immutable" # The SPA shell: "/" and extensionless client routes (try_files → index.html). # Briefly cacheable at the CDN edge (s-maxage) so a first paint never depends # on this origin's uplink; browsers still revalidate every visit (max-age=0). # A deploy propagates within ≤2min and old immutable chunks are kept for a # 14-day grace window, so a briefly-stale shell still boots cleanly. # (Requires a Cloudflare Cache Rule marking these paths eligible — CF does # not cache HTML by default.) @shell { not path /_app/immutable/* not path *.* } header @shell Cache-Control "public, max-age=0, s-maxage=120, stale-while-revalidate=600" # Mutable FILES (service worker, version manifest, webmanifest, word lists, # icons) must revalidate every time — a pinned stale service worker is the # classic blank-screen cause behind a CDN. @revalidate { not path /_app/immutable/* not path /textures/* not path /fonts/* path *.* } header @revalidate Cache-Control "no-cache" # Serve a route's own prerendered HTML when it exists (e.g. /play -> play.html, # which carries its own canonical/OG metadata), else fall back to the SPA shell. # Cache-Control matchers above run on the ORIGINAL extensionless path, so /play # still gets the @shell header before this rewrite. try_files {path} {path}.html /index.html file_server } log { output file /data/access-upbeatbytes.log } } git.tjm77.com { tls { dns cloudflare {env.CF_API_TOKEN} } reverse_proxy gitea:3000 encode gzip zstd log { output file /data/access-gitea.log } } api.tjm77.com { tls { dns cloudflare {env.CF_API_TOKEN} } encode gzip zstd # Recipe finder (What can I make? web tier) — must precede the arbiter catch-all. @finder path /v1/find-recipes /v1/find-recipes/* handle @finder { reverse_proxy finder:8090 } # Per-device token registration → auth service. @register path /v1/register handle @register { reverse_proxy auth:8070 } # In-app feedback relay → auth service (validates the device token + SMTP-sends). # Keeps the arbiter a pure LLM gateway. Must precede the arbiter catch-all. @feedback path /v1/feedback handle @feedback { reverse_proxy auth:8070 } # App update policy — public static JSON (no secrets). Edit ~/srv/sites/api/app-version.json # to change what testers are prompted to update to; no redeploy needed. Precedes the catch-all. @appversion path /v1/app-version handle @appversion { root * /srv/sites rewrite * /api/app-version.json header Content-Type application/json file_server } # LLM Arbiter (everything else); Bearer auth enforced by the arbiter. handle { reverse_proxy arbiter:8080 } log { output file /data/access-arbiter.log } }