27022108b4
Path-only @junk matcher on upbeatbytes.com (*.php, /wp-*, /.env, /.git, /phpmyadmin, /vendor, etc.) returns 403 instead of falling through try_files to a 200 SPA shell. Never matches by User-Agent, so real users + Googlebot/Bing are untouched. Applied to the live Caddyfile (validated + reloaded) and mirrored into the repo snapshot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
178 lines
5.8 KiB
Caddyfile
178 lines
5.8 KiB
Caddyfile
# 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
|
|
# <meta robots> 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
|
|
}
|
|
}
|