# generated:machine-discovery:start # Generated by utilities/generate-machine-manifests.mjs. Exact public hosts # only: unknown hosts, client sites, robots rules and API auth are untouched. (machine_discovery) { map {host} {machine_readme_file} { default "" ~(?i)^aesel[.]app$ machine/aesel.app.txt ~(?i)^aesthetic[.]computer$ llms.txt ~(?i)^api[.]aesthetic[.]computer$ machine/api.aesthetic.computer.txt ~(?i)^api[.]prompt[.]ac$ machine/api.aesthetic.computer.txt ~(?i)^bills[.]aesthetic[.]computer$ machine/bills.aesthetic.computer.txt ~(?i)^buy[.]kidlisp[.]com$ machine/buy.kidlisp.com.txt ~(?i)^calm[.]kidlisp[.]com$ machine/calm.kidlisp.com.txt ~(?i)^data[.]aesthetic[.]computer$ machine/data.aesthetic.computer.txt ~(?i)^device[.]kidlisp[.]com$ machine/device.kidlisp.com.txt ~(?i)^feed[.]aesthetic[.]computer$ machine/feed.aesthetic.computer.txt ~(?i)^give[.]aesthetic[.]computer$ machine/give.aesthetic.computer.txt ~(?i)^jas[.]life$ machine/jas.life.txt ~(?i)^justanothersystem[.]org$ machine/justanothersystem.org.txt ~(?i)^keep[.]kidlisp[.]com$ machine/keep.kidlisp.com.txt ~(?i)^keeps[.]kidlisp[.]com$ machine/keep.kidlisp.com.txt ~(?i)^kidlisp[.]com$ machine/kidlisp.com.txt ~(?i)^l5[.]aesthetic[.]computer$ machine/l5.aesthetic.computer.txt ~(?i)^l5[.]prompt[.]ac$ machine/l5.aesthetic.computer.txt ~(?i)^laklok[.]com$ machine/laklok.com.txt ~(?i)^learn[.]kidlisp[.]com$ machine/learn.kidlisp.com.txt ~(?i)^menuband[.]app$ machine/menuband.app.txt ~(?i)^midi[.]oskiewar[.]com$ machine/oskiewar.com.txt ~(?i)^mime[.]ac$ machine/mime.ac.txt ~(?i)^nela[.]aesthetic[.]computer$ machine/nela.aesthetic.computer.txt ~(?i)^news[.]aesthetic[.]computer$ machine/news.aesthetic.computer.txt ~(?i)^nft[.]aesthetic[.]computer$ machine/nft.aesthetic.computer.txt ~(?i)^nopaint[.]art$ machine/nopaint.art.txt ~(?i)^notepat[.]com$ machine/notepat.com.txt ~(?i)^oskiewar[.]com$ machine/oskiewar.com.txt ~(?i)^p5[.]aesthetic[.]computer$ machine/p5.aesthetic.computer.txt ~(?i)^p5[.]prompt[.]ac$ machine/p5.aesthetic.computer.txt ~(?i)^pals[.]aesthetic[.]computer$ machine/pals.aesthetic.computer.txt ~(?i)^papers[.]aesthetic[.]computer$ machine/papers.aesthetic.computer.txt ~(?i)^papers[.]prompt[.]ac$ machine/papers.aesthetic.computer.txt ~(?i)^pj[.]kidlisp[.]com$ machine/pj.kidlisp.com.txt ~(?i)^pop[.]aesthetic[.]computer$ machine/pop.aesthetic.computer.txt ~(?i)^processing[.]aesthetic[.]computer$ machine/processing.aesthetic.computer.txt ~(?i)^processing[.]prompt[.]ac$ machine/processing.aesthetic.computer.txt ~(?i)^prompt[.]ac$ machine/prompt.ac.txt ~(?i)^quiltnet[.]org$ machine/quiltnet.org.txt ~(?i)^rdp[.]aesthetic[.]computer$ machine/rdp.aesthetic.computer.txt ~(?i)^rdp[.]jas[.]life$ machine/rdp.jas.life.txt ~(?i)^sitemap[.]aesthetic[.]computer$ machine/sitemap.aesthetic.computer.txt ~(?i)^sitemap[.]prompt[.]ac$ machine/sitemap.aesthetic.computer.txt ~(?i)^sotce[.]net$ machine/sotce.net.txt ~(?i)^top[.]kidlisp[.]com$ machine/top.kidlisp.com.txt ~(?i)^tv[.]whistlegraph[.]org$ machine/tv.whistlegraph.org.txt ~(?i)^whistlegraph[.]org$ whistlegraph.org/llms.txt ~(?i)^www[.]aesel[.]app$ machine/aesel.app.txt ~(?i)^www[.]aesthetic[.]computer$ llms.txt ~(?i)^www[.]jas[.]life$ machine/jas.life.txt ~(?i)^www[.]justanothersystem[.]org$ machine/justanothersystem.org.txt ~(?i)^www[.]kidlisp[.]com$ machine/kidlisp.com.txt ~(?i)^www[.]laklok[.]com$ machine/laklok.com.txt ~(?i)^www[.]menuband[.]app$ machine/menuband.app.txt ~(?i)^www[.]mime[.]ac$ machine/mime.ac.txt ~(?i)^www[.]nopaint[.]art$ machine/nopaint.art.txt ~(?i)^www[.]notepat[.]com$ machine/notepat.com.txt ~(?i)^www[.]oskiewar[.]com$ machine/oskiewar.com.txt ~(?i)^www[.]prompt[.]ac$ machine/prompt.ac.txt ~(?i)^www[.]quiltnet[.]org$ machine/quiltnet.org.txt ~(?i)^www[.]sotce[.]net$ machine/sotce.net.txt ~(?i)^www[.]whistlegraph[.]org$ whistlegraph.org/llms.txt } @machine_host host aesel.app aesthetic.computer api.aesthetic.computer api.prompt.ac bills.aesthetic.computer buy.kidlisp.com calm.kidlisp.com data.aesthetic.computer device.kidlisp.com feed.aesthetic.computer give.aesthetic.computer jas.life justanothersystem.org keep.kidlisp.com keeps.kidlisp.com kidlisp.com l5.aesthetic.computer l5.prompt.ac laklok.com learn.kidlisp.com menuband.app midi.oskiewar.com mime.ac nela.aesthetic.computer news.aesthetic.computer nft.aesthetic.computer nopaint.art notepat.com oskiewar.com p5.aesthetic.computer p5.prompt.ac pals.aesthetic.computer papers.aesthetic.computer papers.prompt.ac pj.kidlisp.com pop.aesthetic.computer processing.aesthetic.computer processing.prompt.ac prompt.ac quiltnet.org rdp.aesthetic.computer rdp.jas.life sitemap.aesthetic.computer sitemap.prompt.ac sotce.net top.kidlisp.com tv.whistlegraph.org whistlegraph.org www.aesel.app www.aesthetic.computer www.jas.life www.justanothersystem.org www.kidlisp.com www.laklok.com www.menuband.app www.mime.ac www.nopaint.art www.notepat.com www.oskiewar.com www.prompt.ac www.quiltnet.org www.sotce.net www.whistlegraph.org header @machine_host +Link "; rel=\"describedby\"; type=\"text/plain\"" @machine_readme { host aesel.app aesthetic.computer api.aesthetic.computer api.prompt.ac bills.aesthetic.computer buy.kidlisp.com calm.kidlisp.com data.aesthetic.computer device.kidlisp.com feed.aesthetic.computer give.aesthetic.computer jas.life justanothersystem.org keep.kidlisp.com keeps.kidlisp.com kidlisp.com l5.aesthetic.computer l5.prompt.ac laklok.com learn.kidlisp.com menuband.app midi.oskiewar.com mime.ac nela.aesthetic.computer news.aesthetic.computer nft.aesthetic.computer nopaint.art notepat.com oskiewar.com p5.aesthetic.computer p5.prompt.ac pals.aesthetic.computer papers.aesthetic.computer papers.prompt.ac pj.kidlisp.com pop.aesthetic.computer processing.aesthetic.computer processing.prompt.ac prompt.ac quiltnet.org rdp.aesthetic.computer rdp.jas.life sitemap.aesthetic.computer sitemap.prompt.ac sotce.net top.kidlisp.com tv.whistlegraph.org whistlegraph.org www.aesel.app www.aesthetic.computer www.jas.life www.justanothersystem.org www.kidlisp.com www.laklok.com www.menuband.app www.mime.ac www.nopaint.art www.notepat.com www.oskiewar.com www.prompt.ac www.quiltnet.org www.sotce.net www.whistlegraph.org path /llms.txt } handle @machine_readme { root * /opt/ac/system/public rewrite * /{machine_readme_file} header Content-Type "text/plain; charset=utf-8" header Cache-Control "public, max-age=300" file_server } } # generated:machine-discovery:end # --- zero-downtime deploys --- # lith restarts leave :8888 dark for a few seconds (161 function imports # before listen). Instead of failing the first dial (502 at Cloudflare), # hold and retry for up to 30s so requests queue through the restart. (lith_proxy) { reverse_proxy localhost:8888 { lb_try_duration 30s lb_try_interval 250ms } } # lith production Caddyfile — full subdomain routing # Cloudflare handles TLS. Caddy serves HTTP on :80. # --- papers.aesthetic.computer --- # (touched 2026-04-29 to re-fire webhook after the install-Caddyfile-before-reload fix) :443 { import machine_discovery tls /etc/caddy/origin-cert.pem /etc/caddy/origin-key.pem log { output file /var/log/caddy/access.log { roll_size 50MiB roll_keep 3 roll_keep_for 72h } format json level INFO } # --- Global performance headers --- # 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" # `?` = set only if absent. The upstream Express app (server.mjs) already # sends Access-Control-Allow-Origin on proxied responses; adding a second # copy here produced "*, *", which browsers reject (the same bug the IPFS # block below hit). Set-if-absent yields exactly one header for both proxied # and Caddy-served responses. header @code ?Access-Control-Allow-Origin * # Content-hashed worker bundles are the exception to the short TTL: the # hash in the filename IS the version, and bios.mjs reads the current one # from disk-worker-manifest.json (fetched no-cache, so always revalidated). # A given filename never changes content, so browsers and the service # worker may keep it for a year — a new build means a new name, never a # stale hit. Must come after the @code rule so it wins for these paths. @immutable path_regexp ^/aesthetic\.computer/lib/disk\.worker\.[a-f0-9]+\.mjs$ header @immutable Cache-Control "public, max-age=31536000, immutable" # The two modules that must ship updates within seconds. `no-cache` means # store-but-always-revalidate: the browser (and the service worker's # network-first fetch) sends If-None-Match and gets a 304 when nothing # changed, instead of the 60s/300s stale window above or — what boot.mjs # used to do to get around that window — a `?v=` bust that # re-downloaded bios.mjs on every boot. Must come after @code to win. @core path /aesthetic.computer/bios.mjs /aesthetic.computer/lib/disk.mjs header @core Cache-Control "no-cache" # 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 * # set-if-absent — see @code note # These are database-backed resolvers, not static object URLs. In # particular, do not cache the brief 404 window while a new #code and its # object-store upload converge. The redirect target remains cacheable. @mediaLookup path /media/paintings/* /media/tapes/* header @mediaLookup Cache-Control "no-cache, no-store, must-revalidate" # Service workers must always revalidate — cached sw.js delays rollout of # bumped CACHE_NAME, pinning clients to stale module caches. @serviceworker path /sw.js /firebase-messaging-sw.js header @serviceworker Cache-Control "no-cache, no-store, must-revalidate" # HTML: no-cache (always revalidate, but use ETag for 304) # `?` = set only if absent — proxied apps that send their own # Cache-Control (e.g. the sotce-net shell) were getting both headers. @html path / *.html header @html ?Cache-Control "no-cache" # Encode everything (Caddy does this by default, but be explicit) encode zstd gzip @nft host nft.aesthetic.computer handle @nft { root * /opt/ac/system/public/nft.aesthetic.computer file_server } @rdpmeta host rdp.aesthetic.computer handle @rdpmeta { root * /opt/ac/system/public/rdp.aesthetic.computer file_server } @papers host papers.aesthetic.computer papers.prompt.ac handle @papers { handle /api/mediascholar-status { import lith_proxy } handle /en { rewrite * /index.html?lang=en root * /opt/ac/system/public/papers.aesthetic.computer file_server } handle /da { rewrite * /index.html?lang=da root * /opt/ac/system/public/papers.aesthetic.computer file_server } handle /es { rewrite * /index.html?lang=es root * /opt/ac/system/public/papers.aesthetic.computer file_server } handle /cn { rewrite * /index.html?lang=zh root * /opt/ac/system/public/papers.aesthetic.computer file_server } root * /opt/ac/system/public/papers.aesthetic.computer # /platter is the existing white research-records page (platter.html). # We briefly created /platter/ as a directory with our own index — that # was the wrong call; restored below by deleting the dir's index and # reversing the prior 301 with a 302 to invalidate cached redirects. # /platter/jeffrey/ remains a real directory (the dashboard); its # explicit canonical redirect stays so the page's relative # `./manifest.json` fetch resolves correctly. # (See: 2026-04-29 platter restoration.) redir /platter/ /platter 302 redir /platter/jeffrey /platter/jeffrey/ permanent # SPA fallback for unknown paths: if none of {path}, {path}.html, # {path}/index.html, or {path}index.html exists on disk, serve # /index.html — but mark it no-cache. Without this, a request for a # not-yet-deployed PDF gets the SPA HTML, and Cloudflare pins that # HTML to the PDF URL for 4h via its default static-asset cache. # (See: 2026-04-26 latency-paper deploy gap.) The two index.html # terms cover both /platter (no slash → {path}/index.html) and # /platter/ (slash → {path}index.html). (See: 2026-04-29 # jeffrey-platter URL move.) @missing not file { try_files {path} {path}.html {path}/index.html {path}index.html } handle @missing { header Cache-Control "no-cache, must-revalidate, max-age=0" rewrite * /index.html file_server } # NOTE: {path} is intentionally absent from the top-level try_files. # Caddy's try_files matches directories as well as files, so including # {path} would silently match a request for /platter (the directory), # short-circuit before {path}/index.html, then file_server would serve # the wrong content rather than canonicalizing to /platter/. Without # {path} here, file_server handles existing regular files itself and # issues a 301 redirect for directory-without-slash to the canonical # // form. (See: 2026-04-29 platter URL move.) try_files {path}.html {path}/index.html {path}index.html file_server } # --- pop.aesthetic.computer --- @pop host pop.aesthetic.computer handle @pop { root * /opt/ac/system/public/pop.aesthetic.computer @release path_regexp release ^/releases/([a-z0-9-]+)$ handle @release { rewrite * /releases/{re.release.1}.json file_server } handle { try_files {path} {path}.html {path}/index.html /index.html file_server } } # --- bills.aesthetic.computer --- @bills host bills.aesthetic.computer handle @bills { root * /opt/ac/system/public/bills.aesthetic.computer try_files {path} {path}.html /index.html file_server } # --- nela.aesthetic.computer (NELA Computer Club donate page) --- @nela host nela.aesthetic.computer handle @nela { handle /api/* { import lith_proxy } # Static fallback wrapped in `handle {}` — bare try_files reorders # ahead of `handle` and eats /api/* (see the give block above). handle { root * /opt/ac/system/public/nela.aesthetic.computer try_files {path} {path}.html /index.html file_server } } # --- give.aesthetic.computer --- @give host give.aesthetic.computer handle @give { handle /api/* { import lith_proxy } handle /da { rewrite * /index.html?lang=da¤cy=dkk root * /opt/ac/system/public/give.aesthetic.computer file_server } handle /es { rewrite * /index.html?lang=es¤cy=usd root * /opt/ac/system/public/give.aesthetic.computer file_server } handle /de { rewrite * /index.html?lang=de¤cy=eur root * /opt/ac/system/public/give.aesthetic.computer file_server } handle /cn { rewrite * /index.html?lang=zh¤cy=usd root * /opt/ac/system/public/give.aesthetic.computer file_server } # Static fallback MUST be wrapped in `handle {}` so it's mutually # exclusive with `handle /api/*`. Bare `try_files`/`file_server` is # reordered ahead of `handle` by Caddy's default directive order, which # rewrites /api/* POSTs to /index.html → file_server 405s them (this # broke the give-portal cancel button). See news/feed blocks below. handle { root * /opt/ac/system/public/give.aesthetic.computer try_files {path} {path}.html /index.html file_server } } # --- whistlegraph.org (the index of the artform) --- # Live index of the artform; / (e.g. /imab) falls back to index.html, # whose client router deep-links that piece. commands.json is the canonical # prompt feed consumed by AC, prompt.ac, and ac-native. @whistlegraph host whistlegraph.org www.whistlegraph.org handle @whistlegraph { root * /opt/ac/system/public/whistlegraph.org # Auth0-gated Whistlegraph Desk API. Keep the rest of aesthetic.com's API # namespace off this host; only this narrowly-scoped function crosses # from the static Whistlegraph site into lith. handle /api/whistlegraph-admin* { import lith_proxy } # Public, indexed read projection used by the Desk's paginated index. handle /api/whistlegraph-query* { import lith_proxy } # Free license verification. It comes first and keeps the original query # instead of rebuilding it, because the receipt being checked travels in # that query — the rewrite below would throw it away. @wgverify path /api/wg/verify /api/wg/verify/ handle @wgverify { rewrite * /api/whistlegraph-llm?resource=verify&{query} import lith_proxy } # Paid machine access, metered with x402. /api/wg/[/] # flattens onto the one function; an unpaid GET returns 402 with the # terms, so asking always works. See whistlegraph-llm.mjs. @wgapi path_regexp wgapi ^/api/wg/([a-z]+)(?:/([A-Za-z0-9]+))?/?$ handle @wgapi { rewrite * /api/whistlegraph-llm?resource={re.wgapi.1}&code={re.wgapi.2} import lith_proxy } # The machine-readable index. llms.txt is the map an agent reads first; # index.md is the whole archive as Markdown. Both are generated by # toolchain/whistlegraph/gen-llms.mjs and are deliberately free — the # JSON they summarize is already public, so gating them would be theater. @wgllms path /llms.txt /robots.txt handle @wgllms { header Content-Type "text/plain; charset=utf-8" header Cache-Control "public, max-age=300, stale-while-revalidate=3600" header Access-Control-Allow-Origin "*" file_server } # /llms-full.txt is the same document under the name crawlers look for. @wgindexmd path /index.md /llms-full.txt handle @wgindexmd { rewrite * /index.md header Content-Type "text/markdown; charset=utf-8" header Cache-Control "public, max-age=300, stale-while-revalidate=3600" header Access-Control-Allow-Origin "*" file_server } # Auth0 returns to the bare origin. Only a root request carrying its # transaction state is served by the Desk; ordinary / remains the site. @wgcallback { path / query state=* } handle @wgcallback { rewrite * /admin.html header Cache-Control "no-store" file_server } # /desk is canonical. Keep the old /admin path as a quiet compatibility # alias for sessions begun before the callback moved to the bare origin. @wgdesk path /desk /desk/ /admin /admin/ /admin.html handle @wgdesk { rewrite * /admin.html header Cache-Control "no-store" file_server } @wgcommands path /api/commands /api/commands.json handle @wgcommands { rewrite * /commands.json header Cache-Control "public, max-age=60, stale-while-revalidate=300" header Access-Control-Allow-Origin "*" file_server } # Stable /post/ links get the same record-specific unfurl treatment. @wgpost path_regexp wgpost ^/post/([0-9]+)$ handle @wgpost { rewrite * /api/whistlegraph-og?id={re.wgpost.1} import lith_proxy } # /stars is the ORGSTARS sky. It has to claim its path here, above the # bare-code matcher, or that matcher reads "stars" as a whistlegraph code # and hands the request to the unfurl function instead of the page. @wgstars path /stars /stars/ handle @wgstars { rewrite * /stars.html header Cache-Control "public, max-age=60, stale-while-revalidate=300" file_server } # /comments is the back-of-house comment room — TikTok comment # screenshots getting sorted. Claimed here for the same reason as # /stars: the bare-code matcher would read "comments" as a code. @wgcomments path /comments /comments/ handle @wgcomments { rewrite * /comments.html header Cache-Control "public, max-age=60, stale-while-revalidate=300" file_server } # /wild is the field guide of third-party sightings — whistlegraph # content found loose on the open web. @wgwild path /wild /wild/ handle @wgwild { rewrite * /wild.html header Cache-Control "public, max-age=60, stale-while-revalidate=300" file_server } # Bare / deep links (e.g. /imab, /clth) go to lith, which returns # index.html with THAT work's og:title / og:image / og:video injected — # so iMessage, Slack, Twitter, etc. unfurl the specific whistlegraph # instead of one generic card. Unknown codes fall back to the plain page. # Everything with a dot (index.html, *.json, *.jpg, favicon) skips this # matcher and is served straight off disk. @wgcode path_regexp wgcode ^/([A-Za-z0-9]+)$ handle @wgcode { rewrite * /api/whistlegraph-og?code={re.wgcode.1} import lith_proxy } handle { try_files {path} {path}.html /index.html file_server } } # --- tv.whistlegraph.org (the archive as broadcast) --- # Fullscreen back-to-back playback of the same curated index whistlegraph.org # serves: tv.html reads graphs.json / posts.json straight off this root and # the live curation overlay through the one narrowly-scoped function below. # Nothing else of the API namespace crosses onto this host. @wgtv host tv.whistlegraph.org handle @wgtv { root * /opt/ac/system/public/whistlegraph.org handle /api/whistlegraph-admin* { import lith_proxy } handle { try_files {path} /tv.html file_server } } # --- data.aesthetic.computer (Linked Open Data / CIDOC CRM) --- # Most paths go to lith, which rewrites them to the `crm` function (landing, # /@handle, /painting, /piece, /mood, /.well-known/void). /sparql is the one # exception: it goes straight to the Oxigraph SPARQL store (Stage 2). @data host data.aesthetic.computer handle @data { # Read-only SPARQL: rewrite to Oxigraph's /query endpoint. Its /update # and /store write endpoints are NEVER proxied — writes happen only # locally via the ETL (crm/build-graph.mjs). handle /sparql* { rewrite * /query reverse_proxy localhost:7878 } handle { import lith_proxy } } # --- news.aesthetic.computer --- @news host news.aesthetic.computer handle @news { handle /api/* { import lith_proxy } handle { rewrite * /api/news{uri} import lith_proxy } } # --- api.aesthetic.computer --- @apidomain host api.aesthetic.computer api.prompt.ac handle @apidomain { import lith_proxy } # --- feed.aesthetic.computer (DP-1 Feed V2 — Go + Postgres) --- @feed host feed.aesthetic.computer handle @feed { header Access-Control-Allow-Origin * handle /api/* { reverse_proxy localhost:8787 } handle /health { reverse_proxy localhost:8787 } handle { root * /opt/dp1-feed rewrite * /landing-page.html file_server } } # --- ipfs.aesthetic.computer (self-hosted IPFS gateway) --- # Kubo emits Access-Control-Allow-Origin: * on its own. An earlier Caddy # `header` directive was stacking a second copy, producing "*, *" that # browsers reject. Let Kubo own the header, Caddy just proxies. @ipfs host ipfs.aesthetic.computer handle @ipfs { reverse_proxy localhost:8090 } # --- justanothersystem.org --- @wwwjas2 host www.justanothersystem.org handle @wwwjas2 { redir https://justanothersystem.org{uri} 301 } @jas host justanothersystem.org handle @jas { handle /api/* { import lith_proxy } redir /bio /cv 301 redir /bio/ /cv 301 # Static fallback wrapped in handle{} so handle /api/* stays terminal — # bare try_files is reordered ahead of handle and 405s /api/* POSTs. handle { root * /opt/ac/system/public/justanothersystem.org try_files {path} {path}.html /index.html file_server } } # --- builds.false.work --- @builds_api { host builds.false.work path /api/* /.netlify/functions/* } handle @builds_api { import lith_proxy } @builds host builds.false.work handle @builds { root * /opt/ac/system/public/builds.false.work try_files {path} {path}.html /index.html file_server } # --- sotce.net --- # www → apex, 308 so a stray POST keeps its method. One canonical origin: # Auth0's callback/logout allowlists, the push service worker, and # localStorage all key on it. (Netlify used to do this; lith must.) @sotcewww host www.sotce.net handle @sotcewww { redir https://sotce.net{uri} 308 } @sotce host sotce.net handle @sotce { handle /api/* { import lith_proxy } handle /user { import lith_proxy } handle /handle { import lith_proxy } handle /authorized { import lith_proxy } handle /aesthetic.computer/* { uri strip_prefix /aesthetic.computer root * /opt/ac/system/public/aesthetic.computer file_server } # Everything else → sotce-net function handle { rewrite * /api/sotce-net{uri} import lith_proxy } } # --- kidlisp.com subdomains --- @keep host keep.kidlisp.com handle @keep { handle /api/* { import lith_proxy } handle /technology { root * /opt/ac/system/public/kidlisp.com rewrite * /keeps-tech.html file_server } handle /wallet { root * /opt/ac/system/public/kidlisp.com rewrite * /wallet/index.html file_server } handle { root * /opt/ac/system/public/kidlisp.com rewrite * /keeps.html file_server } } @buy host buy.kidlisp.com handle @buy { root * /opt/ac/system/public/kidlisp.com rewrite * /buy.html file_server } @pj host pj.kidlisp.com handle @pj { root * /opt/ac/system/public/kidlisp.com rewrite * /pj.html file_server } @device host device.kidlisp.com handle @device { handle /api/* { import lith_proxy } handle /js/* { root * /opt/ac/system/public/kidlisp.com file_server } handle /qr/* { root * /opt/ac/system/public/kidlisp.com rewrite * /qr.html file_server } handle { root * /opt/ac/system/public/kidlisp.com rewrite * /device.html file_server } } @topcalm host top.kidlisp.com calm.kidlisp.com handle @topcalm { handle /js/* { root * /opt/ac/system/public/kidlisp.com file_server } import lith_proxy } @learn host learn.kidlisp.com handle @learn { handle /api/* { import lith_proxy } handle /decree* { root * /opt/ac/system/public/kidlisp.com rewrite * /decree.html file_server } handle { root * /opt/ac/system/public/kidlisp.com rewrite * /learn.html file_server } } @keepsredirect host keeps.kidlisp.com handle @keepsredirect { redir https://keep.kidlisp.com{uri} 301 } @keepsac host keeps.aesthetic.computer handle @keepsac { import lith_proxy } # --- jas.life --- @jaslife host jas.life handle @jaslife { root * /opt/ac/system/public/jas.life try_files {path} {path}.html /index.html file_server } # --- www.jas.life redirect --- @wwwjas host www.jas.life handle @wwwjas { redir https://jas.life{uri} 301 } # --- pals.aesthetic.computer --- # pals.aesthetic.computer → /api/logo (dynamic logo generation) @pals host pals.aesthetic.computer handle @pals { rewrite * /api/logo{uri} import lith_proxy } @l5prompt host l5.prompt.ac handle @l5prompt { redir https://l5.aesthetic.computer{uri} 301 } @p5prompt host p5.prompt.ac handle @p5prompt { redir https://p5.aesthetic.computer{uri} 301 } @procprompt host processing.prompt.ac handle @procprompt { redir https://processing.aesthetic.computer{uri} 301 } @siteprompt host sitemap.prompt.ac handle @siteprompt { redir https://sitemap.aesthetic.computer{uri} 301 } @apiprompt host api.prompt.ac handle @apiprompt { redir https://api.aesthetic.computer{uri} 301 } # --- legacy duckweedtri hosts --- @duckweedaesthetic host duckweedtri.aesthetic.computer handle @duckweedaesthetic { redir https://aesthetic.computer{uri} 301 } @duckweedprompt host duckweedtri.prompt.ac handle @duckweedprompt { redir https://aesthetic.computer{uri} 301 } # --- www.prompt.ac redirect --- # (needs a proxied `www` A record in the prompt.ac zone — without it the # `*.prompt.ac → 100::` Worker wildcard swallows www and 522s.) @wwwpromptac host www.prompt.ac handle @wwwpromptac { redir https://prompt.ac{uri} 301 } # --- prompt.ac/menuband → menuband.app (Menu Band's own domain) --- # Menu Band now lives at menuband.app; this legacy path keeps working by # forwarding into it (subpath preserved: /menuband/privacy.html → # menuband.app/privacy.html). More specific than the host-only @promptac # below, so Caddy evaluates it first. @promptac_menuband { host prompt.ac path /menuband /menuband/* } handle @promptac_menuband { # route{} preserves written order so strip_prefix runs before redir # captures {uri} — otherwise the target keeps the /menuband prefix. route { uri strip_prefix /menuband redir https://menuband.app{uri} 301 } } # --- prompt.ac (apex root only) → HTML prompt shell (its own product) --- # Naked prompt.ac serves the fast HTML prompt that hosts the AC runtime in # an iframe. Every other prompt.ac/ still 301s to aesthetic.computer # (below), so deep links keep working. Evaluated before the redirect since # it appears first in source order. @promptac_root { host prompt.ac path / } handle @promptac_root { root * /opt/ac/system/public/prompt.ac rewrite * /index.html file_server } # --- prompt.ac/2018 → Jeffrey's prehistory-year media memorial --- @promptac_2018 { host prompt.ac path /2018 /2018/* } handle @promptac_2018 { root * /opt/ac/system/public/prompt.ac uri strip_prefix /2018 try_files {path} {path}/ /2018/index.html file_server } # --- prompt.ac/~ → a live Aesthetic Code session --- # The address a phone gets when it scans the prompt rock. It exists to be # short: every byte of it is a module of QR, and the long form it expands # to (`/prompt~channel%20~!autorun`) costs an extra version of the # symbol — smaller modules, on the surface hardest to scan. Twenty-seven # bytes with the scheme keeps it at version 2. # # A redirect rather than a rewrite: the shell reads the channel out of its # own location client-side, so it has to actually arrive at the long URL. # Must sit above @promptac, which otherwise claims every non-root path. @promptac_channel { host prompt.ac path_regexp promptacchannel ^/~([A-Za-z0-9_-]{4,16})$ } handle @promptac_channel { redir https://prompt.ac/prompt~channel%20{re.promptacchannel.1}~!autorun 302 } # The installer and its tarball, served as files from both hosts. Must sit # above @promptac, which otherwise hands every non-root path to the resolver # and would turn `curl prompt.ac/aesel.sh` into a redirect to a piece. # The easel.* names are the same files under the name they had first; # installed copies poll easel.json, so both stay served. # # prompt.ac because it is nine bytes shorter than aesthetic.computer and an # install line is read aloud and typed by hand more often than it is copied. @aesel_install { host prompt.ac aesthetic.computer path /aesel.sh /aesel.tar.gz /aesel.json /easel.sh /easel.tar.gz /easel.json } handle @aesel_install { root * /opt/ac/system/public header /aesel.sh Content-Type "text/plain; charset=utf-8" header /aesel.tar.gz Content-Type "application/gzip" header /aesel.json Content-Type "application/json" header /easel.sh Content-Type "text/plain; charset=utf-8" header /easel.tar.gz Content-Type "application/gzip" header /easel.json Content-Type "application/json" # A stale installer is worse than a slow one: it points at a tarball # that may no longer be there. header Cache-Control "public, max-age=300" file_server } # --- prompt.ac routes --- # Non-root paths pass through lith's conflict-aware Whistlegraph resolver. # It sends live codes to whistlegraph.org and preserves the historical # aesthetic.computer redirect for pieces, commands, and unknown paths. @promptac { host prompt.ac not path / } handle @promptac { import lith_proxy } @tryshared { host l5.aesthetic.computer processing.aesthetic.computer path /aesthetic.computer/* } handle @tryshared { root * /opt/ac/system/public file_server } # --- l5.aesthetic.computer --- @l5 host l5.aesthetic.computer handle @l5 { handle /api/* { import lith_proxy } handle /docs* { import lith_proxy } # Static fallback wrapped in handle{} so handle /api/* stays terminal — # bare try_files is reordered ahead of handle and 405s /api/* POSTs. handle { root * /opt/ac/system/public/l5.aesthetic.computer try_files {path} {path}.html /index.html file_server } } # --- processing.aesthetic.computer --- @processing host processing.aesthetic.computer handle @processing { handle /api/* { import lith_proxy } handle /docs* { import lith_proxy } # Static fallback wrapped in handle{} so handle /api/* stays terminal — # bare try_files is reordered ahead of handle and 405s /api/* POSTs. handle { root * /opt/ac/system/public/processing.aesthetic.computer try_files {path} {path}.html /index.html file_server } } # --- rdp.jas.life --- @rdp host rdp.jas.life handle @rdp { root * /opt/ac/system/public/rdp.jas.life try_files {path} {path}.html /index.html file_server } # --- quiltnet.org --- @quiltnet host quiltnet.org www.quiltnet.org handle @quiltnet { root * /opt/ac/system/public/quiltnet.org try_files {path} {path}.html /index.html file_server } # --- kidlisp.com root domain --- @kidlisproot host kidlisp.com www.kidlisp.com handle @kidlisproot { handle /api/* { import lith_proxy } handle /.netlify/functions/* { import lith_proxy } handle /keeps { redir https://keep.kidlisp.com/ 301 } # Serve AC runtime assets for iframe embedding handle /aesthetic.computer/* { root * /opt/ac/system/public file_server } # kidlisp.com SPA handle { root * /opt/ac/system/public/kidlisp.com try_files {path} {path}.html /index.html file_server } } # --- notepat.com --- @notepatroot host notepat.com www.notepat.com handle @notepatroot { @notepatindex path / handle @notepatindex { rewrite * /notepat } # Permalink: notepat.com/amxd → current notepat.com.amxd as a # direct download. Clients get the Content-Disposition so it # lands in ~/Downloads instead of opening in-browser. handle /amxd { rewrite * /m4l/notepat.com.amxd header Content-Disposition "attachment; filename=\"notepat.com.amxd\"" root * /opt/ac/system/public file_server } import lith_proxy } # --- wipppps.world --- @wippppsroot host wipppps.world www.wipppps.world handle @wippppsroot { @wippppsindex path / handle @wippppsindex { rewrite * /wipppps } import lith_proxy } # --- danzballet.studio --- @danzballetroot host danzballet.studio www.danzballet.studio handle @danzballetroot { root * /opt/ac/system/public/danzballet.studio try_files {path} /index.html file_server } # --- sitemap.aesthetic.computer --- # The sitemap is a static network index, not the generic AC prompt shell. @sitemaproot { host sitemap.aesthetic.computer path / } handle @sitemaproot { root * /opt/ac/system/public rewrite * /sitemap.html file_server } @mainspa host aesthetic.computer www.aesthetic.computer lith.aesthetic.computer notepat.com www.notepat.com wipppps.world www.wipppps.world p5.aesthetic.computer sitemap.aesthetic.computer handle @mainspa { # Assets → DO Spaces CDN. The bucket root maps directly to # assets.aesthetic.computer (no /assets/ prefix exists in the # bucket), so we use handle_path to strip the matched /assets/ # prefix before substituting {path} into the redirect target. # The previous form (`handle /assets/*` + `{uri}`) doubled the # prefix and produced 403s on assets.aesthetic.computer/assets/... # (See: 2026-04-29 platter readings 403 fix.) handle_path /assets/* { redir https://assets.aesthetic.computer{path} 302 } # API → lith handle /api/* { import lith_proxy } handle /.netlify/functions/* { import lith_proxy } # Media → lith handle /media/* { import lith_proxy } # Static rewrite shortcuts handle /disks/* { uri strip_prefix /disks root * /opt/ac/system/public/aesthetic.computer/disks file_server } handle /lib/* { uri strip_prefix /lib root * /opt/ac/system/public/aesthetic.computer/lib file_server } # Static files, then SPA fallback handle { root * /opt/ac/system/public @static file handle @static { file_server } handle { import lith_proxy } } } # --- false.work --- @falseroot host false.work handle @falseroot { root * /opt/ac/system/public/false.work try_files {path} {path}.html /index.html file_server } @wwwfalse host www.false.work handle @wwwfalse { redir https://false.work{uri} 301 } # --- menuband.app (Menu Band — Mac App Store landing + privacy page) --- # The landing page uses absolute asset paths (/menuband/icon.png, # /aesthetic.computer/cursors/…), so those two prefixes are served from the # full public tree; everything else (apex → index.html, /privacy → privacy) # is served from the scoped menuband bundle. Each branch is wrapped in its # own handle{} — mixing bare try_files/file_server with nested handles gets # reordered by Caddy and breaks the reload (see the l5/kidlisp blocks). @menubandapp host menuband.app handle @menubandapp { # Backend API — the landing page fetches /api/version (recent-changes # feed + live-reload), and /advanced fetches /api/menuband-downloads # (the DMG counter, which moved off the landing page when the App # Store became the front door). Without this, those fall through to # the catch-all and get index.html instead of JSON, so "Recent # changes" never populates. Kept terminal (own handle{}) and ahead of # the static catch-all. handle /api/* { import lith_proxy } handle /menuband/* { root * /opt/ac/system/public file_server } handle /aesthetic.computer/* { root * /opt/ac/system/public file_server } # Apex → Mac App Store product page (the store listing is the # converting front door; @jeffrey, 2026-08-31). Only the bare root # forwards — /privacy.html, /support.html, /redeem.html, /advanced.html # must stay served here because App Review points at them. 302, not # 301, so the front door can move back without fighting browser caches. handle / { redir https://apps.apple.com/app/id6767311903 302 } handle { root * /opt/ac/system/public/menuband try_files {path} {path}.html /index.html file_server } } @wwwmenubandapp host www.menuband.app handle @wwwmenubandapp { redir https://menuband.app{uri} 301 } # --- Fallback for any unmatched host --- handle { import lith_proxy } } # --- oskiewar.com --- # Porkbun DNS points directly at lith, so this host needs a publicly trusted # certificate rather than the Cloudflare origin certificate on the :443 block. # midi.oskiewar.com is the same site: the shell reads the host name and turns # the MIDI bridge on, so scoring from a DAW needs no query string. It needs its # own A record at Porkbun — there is no wildcard on this zone. oskiewar.com, midi.oskiewar.com { import machine_discovery tls { issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } protocols tls1.2 tls1.3 } encode zstd gzip # Meet-style durable round URLs: live while a publisher is present, demo # playback after the round ends. Redirect old QR routes to the raw ID. @oskiewarwatchmatch path_regexp oskiewarwatchmatch ^/watch/([^/]+)/?$ redir @oskiewarwatchmatch https://oskiewar.com/{re.oskiewarwatchmatch.1} 308 @oskiewarwatchroot path /watch /watch/ redir @oskiewarwatchroot https://oskiewar.com/ 308 @oskiewarround path_regexp oskiewarround ^/(([a-z]{4,7}[0-9]{1,3})|([bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou]-[bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou]-[bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou][bdfgklmnprstvz][aeiou]))/?$ handle @oskiewarround { root * /opt/ac/xbox/live rewrite * /mac-test.html header Cache-Control no-cache file_server { precompressed br zstd } } # The game source and modules share the /oskiewar prefix with the live API # route below, so serve them before the proxy matcher claims them. # `precompressed` answers from the max-level .br/.zst sidecars that # xbox/tools/precompress-live.sh writes after every checkout — a fifth # smaller than encoding on the fly — and falls back to `encode` when a # sidecar is missing. # `no-cache` here means revalidate, not never-cache: an unchanged file # answers 304 against its ETag and loads from disk. Without the header the # browser's heuristic kept whole modules stale across deploys — a returning # visitor once ran a months-old round-room.mjs against the current relay. @oskiewarmodules path /oskiewar.js /oskiewar-sfx.mjs /oskiewar-voice.mjs /oskiewar-midi.mjs /oskiewar-wizard.mjs /oskiewar-fighter.mjs /oskiewar-map.mjs /oskiewar-workshop.mjs handle @oskiewarmodules { root * /opt/ac/xbox/live header Cache-Control no-cache file_server { precompressed br zstd } } handle /oskiewar* { import lith_proxy } # AC runtime services used by the spectator route. handle /api/* { import lith_proxy } handle /.netlify/functions/* { import lith_proxy } handle /media/* { import lith_proxy } handle /manifest.json { import lith_proxy } handle /aesthetic.computer/* { root * /opt/ac/system/public header Cache-Control no-cache file_server } handle /type/* { root * /opt/ac/system/public header Cache-Control no-cache file_server } # The one asset that genuinely never moves between deploys can skip the # revalidation round-trip for a week. woff2 is the same face at a third # of the bytes; the TTF stays for whatever cannot read it. handle /ComicRelief-Regular.woff2 { root * /opt/ac/system/public/papers.aesthetic.computer/foundry/fonts header Cache-Control "public, max-age=604800" file_server } handle /ComicRelief-Regular.ttf { root * /opt/ac/system/public/papers.aesthetic.computer/foundry/fonts header Cache-Control "public, max-age=604800" file_server } handle /sw.js { root * /opt/ac/system/public header Cache-Control no-cache file_server } # Add your coach: the page the title screen prints, and (through the # catch-all below) the one file it hands to a player's own agent. handle /coach { root * /opt/ac/xbox/live rewrite * /coach.html header Cache-Control no-cache file_server } @jevdemo path /jev-vs-jev /jev-vs-jev/ handle @jevdemo { root * /opt/ac/xbox/live rewrite * /jev-vs-jev/index.html header Cache-Control no-cache file_server } # Playable Canvas/Web Audio build shared with the desktop test harness. # Classic: v168, the side-view monowheel park, frozen whole in # xbox/classic with its own paths under /classic/. It is the front door # for now (@jeffrey, 2026-09-29: "hold that, recall that as a mode"); # today's 3D game is at /3d. Room links still open the current game. @oskiewarfront path / redir @oskiewarfront /classic{uri} 302 @oskiewarclassic path /classic /classic/ handle @oskiewarclassic { root * /opt/ac/xbox/classic rewrite * /mac-test.html header Cache-Control no-cache file_server } handle /classic/* { root * /opt/ac/xbox header Cache-Control no-cache file_server } @oskiewargame path /3d /3d/ /workshop /workshop/ handle @oskiewargame { root * /opt/ac/xbox/live rewrite * /mac-test.html header Cache-Control no-cache file_server { precompressed br zstd } } handle { root * /opt/ac/xbox/live header Cache-Control no-cache file_server { precompressed br zstd } } } www.oskiewar.com { tls { protocols tls1.2 tls1.3 } redir https://oskiewar.com{uri} 301 } # --- nopaint.art --- # (touched 2026-07-19 x2 to force a reload after the DNS flip so ACME retries promptly) # This host's DNS points straight at lith (DigitalOcean zone, no Cloudflare # proxy), so it cannot ride the origin cert the :443 block uses — Caddy # manages a real ACME cert for it instead. The native remake landed, so the # root now serves the new No Paint on AC without leaving nopaint.art. # /classic stays the permanent home of the preserved 2021 Construct build, # and /gallery + the per-painting archive record pages keep serving here. nopaint.art { import machine_discovery # Pin issuance to LE production (Caddy demoted this host to LE staging # after early post-DNS-flip failures; staging's resolvers held the stale # Vercel IP long after production had the new one). # # The `protocols` line is NOT a hardening tweak — it is what makes this # site's certificate get SERVED. The :443 catch-all above pins its # certificate_selection to the Cloudflare origin cert (any_tag cert0), # and without a distinct TLS setting here the Caddyfile adapter emits # only that catch-all connection policy, so every SNI — including this # one — gets the origin cert. A site-specific setting forces an # sni-matched policy with default (managed-cert) selection, ordered # before the catch-all. Verify with: # caddy adapt --config lith/Caddyfile | jq '.apps.http.servers.srv0.tls_connection_policies' tls { issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } protocols tls1.2 tls1.3 } root * /opt/ac/system/public/nopaint.art encode gzip @nopaint_root path / handle @nopaint_root { rewrite * /nopaint import lith_proxy } # The conductor rewrites / to /nopaint:. Reloads and shared # sessions must boot AC again instead of matching an archive record. @nopaint_session path /nopaint /nopaint/ /nopaint:* /nopaint~* handle @nopaint_session { import lith_proxy } # AC runtime routes used by the native remake at the branded root. handle /presigned-upload-url/* { import lith_proxy } handle /api/* { import lith_proxy } handle /.netlify/functions/* { import lith_proxy } handle /media/* { import lith_proxy } handle /disks/* { uri strip_prefix /disks root * /opt/ac/system/public/aesthetic.computer/disks file_server } handle /lib/* { uri strip_prefix /lib root * /opt/ac/system/public/aesthetic.computer/lib file_server } handle /aesthetic.computer/* { root * /opt/ac/system/public file_server } handle /type/* { root * /opt/ac/system/public file_server } handle /sw.js { root * /opt/ac/system/public file_server } handle /manifest.json { root * /opt/ac/system/public file_server } handle_path /nopaint.art/* { root * /opt/ac/system/public/nopaint.art file_server } handle_path /classic* { file_server } handle /gallery* { file_server } # A single archive id gets a human-readable record page. Existing files # (runtime assets, icons, manifests) continue through the final handler. @nopaint_archive_record { not { file } path_regexp archiveRecord ^/[^/]+/?$ } handle @nopaint_archive_record { rewrite * /painting/index.html file_server } handle { file_server } } www.nopaint.art { # Same serving requirement as the apex block above: without a distinct # TLS setting, the catch-all's pinned origin cert answers this SNI too. tls { protocols tls1.2 tls1.3 } redir https://nopaint.art{uri} 301 } # --- gym.anthonyzollo.com --- # Anthony's zone CNAMEs this host to lith.aesthetic.computer. Unlike AC's # Cloudflare-origin-cert catch-all, this external hostname needs its own public # ACME certificate and SNI policy. The HTML is updated through the authenticated # Express endpoint and stored outside the git checkout. gym.anthonyzollo.com { tls { issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } protocols tls1.2 tls1.3 } encode zstd gzip @gymapi path /api/publish-gym /api/history-gym /api/rewind-gym handle @gymapi { import lith_proxy } handle { root * /var/lib/aesthetic-computer/gym.anthonyzollo.com try_files {path} /index.html header Cache-Control "no-cache" file_server } } # --- inbound.aesthetic.computer --- # Not a website. This is the SMTP door for Amail (lith/mail-inbound.mjs, # lith-mail.service on :25) — Google Workspace routes every unknown # @aesthetic.computer address here. The DNS record is DNS-only (grey cloud): # Cloudflare does not proxy port 25. # # Its STARTTLS certificate can't come from Caddy's automation: the Cloudflare # origin cert loaded above is a self-issued *.aesthetic.computer wildcard, and # Caddy skips ACME for any name a loaded cert already covers. So certbot # fetches it by HTTP-01 through the webroot served below (:80), and the door # reads /etc/letsencrypt/live/inbound.aesthetic.computer/. There is no site # block for the host on purpose: one would answer the challenge itself. :80 { # certbot's HTTP-01 challenge files, before the redirect swallows them. # Both are `handle`s on purpose: a bare `redir` sorts ahead of `handle` # in Caddy's directive order and would answer first. handle /.well-known/acme-challenge/* { root * /var/lib/amail-acme file_server } handle { redir https://{host}{uri} 301 } } # --- mime.ac --- # DNS-only on Cloudflare; serve MIME at the apex with its own public TLS. mime.ac { import machine_discovery tls { issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } protocols tls1.2 tls1.3 } encode zstd gzip handle / { root * /opt/ac/system/public/mime header Cache-Control "no-cache" file_server } @mime_brand path /favicon.svg /favicon-32.png /apple-touch-icon.png /social/mime.jpg handle @mime_brand { root * /opt/ac/system/public/mime header Cache-Control "public, max-age=86400" file_server } handle /mime { redir / 302 } handle /mime/ { redir / 302 } handle /prompt { redir https://aesthetic.computer/prompt 302 } handle { import lith_proxy } } www.mime.ac { tls { issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } } redir https://mime.ac{uri} 308 } # Aesel uses Cloudflare DNS-only records and its own public certificate. aesel.app { import machine_discovery tls { protocols tls1.2 tls1.3 issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } } encode zstd gzip root * /opt/ac/system/public/aesel header X-Content-Type-Options nosniff header Referrer-Policy strict-origin-when-cross-origin file_server } www.aesel.app { tls { protocols tls1.2 tls1.3 issuer acme { dir https://acme-v02.api.letsencrypt.org/directory } } redir https://aesel.app{uri} 301 }