clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / deploy / edge / Caddyfile
Caddyfile 3.5K
1# The edge's HTTP front (Fly terminates TLS and sends plain HTTP to :8080).2# Everything goes to the forge through the tailcat forward on :8081. When3# the forge doesn't answer (the tunnel or slate is down, a timeout, or it4# says 502, 503 or 504), GET and HEAD for the forge's own pages are served5# from the pages mirror instead (forge mirror; README.md), marked with6# X-Forge-Offline; a page the copy hasn't got is a 404 that says so. Sites,7# clones, live updates and pushes need the forge.8# FORGE_DOMAIN the forge's hostname, e.g. src.rest; only it falls back9# FORGE_MIRROR_URL the mirror bucket's public URL, e.g. https://mirror.src.rest;10# without it there is no fallback11# FORGE_MIRROR_TOKEN (a Fly secret, optional) sent to the bucket as12# X-Forge-Mirror-Token, so a rule in front of it (a13# Cloudflare WAF rule) can turn away requests without it14{15 admin off16 auto_https off17 persist_config off18}1920:8080 {21 reverse_proxy 127.0.0.1:8081 {22 transport http {23 dial_timeout 3s24 response_header_timeout 15s25 }26 # The forward accepts locally even when the tunnel is dead, so a27 # request would wait out the timeout. Checking every 10s marks the28 # forge down, and then requests go straight to the mirror.29 health_uri /30 health_interval 10s31 health_timeout 5s32 @down status 502 503 50433 handle_response @down {34 error "forge answered {rp.status_code}" 50235 }36 }3738 handle_errors {39 @copy {40 method GET HEAD41 host {$FORGE_DOMAIN:localhost}42 expression `"{$FORGE_MIRROR_URL:}" != ""`43 }44 handle @copy {45 header X-Forge-Offline "mirror"46 header Cache-Control "no-store"47 # Like the forge's file server: /a/b -> /a/b/, /a/b/ -> /a/b/index.html.48 # Release downloads are files whatever their names (greet-linux,49 # SHA256SUMS), and download rather than open, as from the forge.50 # Never a path that starts // or /\ (decoded): redirected, that51 # would be another site's address (//evil.example/x/). Path52 # matchers see the path cleaned (one slash), so this looks at53 # the path itself.54 @bare {55 path_regexp ^(.*/)?[^./]+$56 not path_regexp ^/[^/]+/[^/]+/releases/download/.57 not vars_regexp {path} ^/[/\\]58 }59 redir @bare {path}/ 30860 @download path_regexp ^/[^/]+/[^/]+/releases/download/.61 header @download Content-Disposition attachment62 header @download X-Content-Type-Options nosniff63 # No query strings: on a bucket some (?acl, ?list-type) mean something.64 @dir path */65 rewrite @dir {path}index.html?66 @file not path */67 rewrite @file {path}?68 reverse_proxy {$FORGE_MIRROR_URL:https://mirror.invalid} {69 header_up Host {upstream_hostport}70 header_up X-Forge-Mirror-Token "{$FORGE_MIRROR_TOKEN:}"71 header_down -Set-Cookie72 # Not in the copy: a page that isn't there, or one the copy73 # leaves out (a file's or a folder's; lib/mirror.ts). The74 # forge's own 404, saying so.75 @miss status 403 40476 handle_response @miss {77 header Content-Type "text/html; charset=utf-8"78 respond <<HTML79 <!DOCTYPE html>80 <html lang="en">81 <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Not found</title></head>82 <body><h1>Not found</h1><p>The forge is offline for a moment, and this page isn't in its offline copy, which leaves out the pages of single files and folders. Try again soon.</p><p><a href="/">All repositories</a></p></body>83 </html>84 HTML 40485 }86 }87 }88 respond "The forge is offline for a moment. Try again soon." 50389 }90}