orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import (
4 "net/http"
5 "strings"
6
7 "auth.bythewood.me/web"
8)
9
10// reqContext is where a request came from, as far as the edge can tell.
11//
12// The country and city headers are trustworthy here only because the tunnel is
13// the only way in: no container in this repo publishes a host port, so nothing
14// reaches this process without passing cloudflared and Caddy first. The day one
15// of them is directly reachable, a client can set CF-IPCountry to whatever it
16// likes and the session list starts lying.
17//
18// CF-IPCountry arrives by default. The rest need the "Add visitor location
19// headers" managed transform turned on for the zone, and stay empty without it,
20// which is why nothing below treats an empty one as an error.
21type reqContext struct {
22 IP string
23 Country string
24 City string
25 UA string
26 Ray string
27}
28
29func requestContext(r *http.Request) reqContext {
30 return reqContext{
31 IP: web.ClientIP(r),
32 Country: strings.TrimSpace(r.Header.Get("CF-IPCountry")),
33 City: strings.TrimSpace(r.Header.Get("CF-IPCity")),
34 // Truncated: a user agent is display-only here and some are enormous.
35 UA: truncate(r.UserAgent(), 300),
36 Ray: r.Header.Get("CF-Ray"),
37 }
38}
39
40// Where reads as a place a person recognises, or as the address when the
41// transform is off and there is nothing better to say.
42func (c reqContext) Where() string {
43 switch {
44 case c.City != "" && c.Country != "":
45 return c.City + ", " + c.Country
46 case c.Country != "":
47 return c.Country
48 default:
49 return c.IP
50 }
51}
52
53func truncate(s string, n int) string {
54 if len(s) <= n {
55 return s
56 }
57 return s[:n]
58}