Repository navigation
Expand file tree
/
Copy pathCaddyfile
More file actions
232 lines (192 loc) · 8.51 KB
/
Copy pathCaddyfile
File metadata and controls
232 lines (192 loc) · 8.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# Redirect rules and site config, shipped inside the release so they roll back with the content.
# See OPERATIONS.md for the bundle layout and the redirect design.
{
admin off
auto_https off
# Trusting a range means believing X-Forwarded-For from anything inside it.
# The value is per-host, because docker subnets differ.
# An empty value trusts nothing, which is correct where Caddy is reachable without a proxy.
# The default keeps a host that omits the variable working.
servers {
trusted_proxies static {$TRUSTED_PROXIES:172.16.0.0/12 10.0.0.0/8 192.168.0.0/16}
}
}
:8080 {
root * /srv/blog/current/site
# Only the container distinguishes environments, which all serve this bundle on one port.
# A proxy rule aimed at the wrong one answers healthily under the right hostname.
# The default matters, since an unset variable yields an empty header and still validates.
header X-Blog-Env "{$SITE_ENV:unset}"
# Keeps a non-public copy of the site out of search indexes.
# The default is the value that is harmless on production.
# An unset staging container is still behind its auth gate.
# Production inheriting noindex would deindex the site silently.
header X-Robots-Tag "{$SITE_ROBOTS:index, follow}"
# Names the release whose rules are answering, substituted at install time.
# Content follows the current symlink per request, while rules wait on a config reload.
# Without this a stale config passes the URL contract against rules never shipped.
header X-Blog-Release "@@RELEASE@@"
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-5ilQRTfItUB+Etsk1onTDCqBA1OpUBIKfJhdMlZ61Co=' 'sha256-QOwTHff6BrH8cmnR7hf10tIuyQ09hKSkEevZzo+G8WE=' 'sha256-UBD9lfFHKxagKGHh9S8B2hytN1cmDsFK1Tl1uedsZ0Q=' 'sha256-eyUVosQAviXm2qeOTG819D1n0kSst2gY8VmfgVsZlag='; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'"
}
# One map serves four classes because their key spaces are disjoint.
# Merging them keeps those four classes to a single lookup.
map {path} {mapped} {
import /srv/blog/current/maps/blogger.map
import /srv/blog/current/maps/slugs.map
import /srv/blog/current/maps/terms.map
default ""
}
# A legacy link that drops the trailing slash still deserves its mapped destination.
# A real page gets the slash from file_server, and these keys name no file.
map {path}/ {mapped_slash} {
import /srv/blog/current/maps/slugs.map
import /srv/blog/current/maps/terms.map
default ""
}
# WordPress shortlinks, keyed on the query string rather than the path.
map {query.p} {by_id} {
import /srv/blog/current/maps/p-ids.map
default ""
}
@post_id expression {by_id} != ""
@mapped expression {mapped} != ""
# A dotted path is left out, or /robots.txt would redirect to itself through the /robots.txt/ key.
@mapped_slash {
path_regexp ^/[^.]*[^./]$
expression {mapped_slash} != ""
}
# Blogger label archives, defaulting to a real destination.
# A label that never became a term still deserves a landing page rather than a 404.
map {re.label.1} {by_label} {
import /srv/blog/current/maps/labels.map
default /all/
}
route {
# Explicit lifetimes replace whatever heuristic a browser or an edge cache would apply, so a deploy shows at once.
# The baseline runs first so a redirect carries it too, and the file classes below override it.
header Cache-Control "no-cache"
redir @post_id {by_id} 301
# WordPress served every feed under a type suffix too, so dropping it hands the address to the bare feed's rule.
# The prefix admits only shapes that had a feed, so a probe of any other shape still 404s.
@feed_type path_regexp ft ^((?:/\d{4}/\d{2}/\d{2}/[^/]+(?:/[^/]+)?|/(?:tag|category|author)/[^/]+|/comments|/about)?)/feed/(?:atom|rss|rss2|rdf)/?$
redir @feed_type {re.ft.1}/feed/ 301
# A per-post comment feed and an attachment page share a shape under a post date.
# The longer pattern is tested first, or the broader rule claims both.
@post_child_feed path_regexp pcf ^(/\d{4}/\d{2}/\d{2}/[^/]+)/[^/]+/feed/?$
redir @post_child_feed {re.pcf.1}/ 301
@post_child path_regexp pc ^(/\d{4}/\d{2}/\d{2}/[^/]+)/[^/]+/?$
redir @post_child {re.pc.1}/ 301
@term_feed path_regexp tf ^(/(?:tag|category)/[^/]+)/feed/?$
redir @term_feed {re.tf.1}/ 301
# Hugo emits /feed.xml, named by the RSS outputFormat baseName.
@site_feed path_regexp sf ^/(?:feed|comments/feed|about/feed)/?$
redir @site_feed /feed.xml 301
@blogger_feed path /feeds/posts/default /feeds/posts/default/ /atom.xml
redir @blogger_feed /feed.xml 301
@news_sitemap path /news-sitemap.xml
redir @news_sitemap /sitemap.xml 301
# Every monthly archive path answers, including dates the blog never covered.
# The archive index is where a visitor can actually find the month.
@blogger_archive path_regexp ^/\d{4}_\d{2}_01_archive\.html$
redir @blogger_archive /all/ 301
@blogger_page path_regexp bp ^/p/([^/]+)\.html$
redir @blogger_page /{re.bp.1}/ 301
# Hugo builds no year or month archive.
# The archive index beats the home page for someone following a dated link.
@date_archive path_regexp ^/\d{4}(/\d{2})?(/page/\d+)?/?$
redir @date_archive /all/ 301
# Single-author blog, so every author URL duplicates the home page.
# The author feed belongs to this class and 404s if the pattern omits it.
@author path_regexp ^/author/[^/]+(/(page/\d+|feed))?/?$
redir @author / 301
@label path_regexp label ^/search/label/([^/]+)/?$
redir @label {by_label} 301
# The media tree lives under a different prefix than the one the old site served.
# This rule keeps every legacy image URL alive for hotlinks and search results.
@uploads path_regexp up ^/wp-content/uploads/(.*)$
redir @uploads /media/{re.up.1} 301
redir @mapped {mapped} 301
redir @mapped_slash {mapped_slash} 301
@fingerprinted path /assets/*
header @fingerprinted Cache-Control "public, max-age=31536000, immutable"
# An hour bounds how long a replaced image or font is still served from any copy.
@media path /media/* /fonts/* /*.png /*.ico
header @media Cache-Control "public, max-age=3600"
# The proxy forwards Accept-Encoding unchanged and adds no compression of its own.
# Serving the precompressed siblings here is what reaches the client.
file_server {
precompressed br gzip
}
}
handle_errors {
# A missing file in a cached class would otherwise keep that class's lifetime.
header Cache-Control "no-cache"
rewrite * /404.html
file_server {
precompressed br gzip
}
}
log {
output stdout
format console
}
}
# The family genealogy site shares this listener, and the Host header the proxy forwards selects it.
# Its staging name sits under the wildcard that already covers the blog's staging host.
# A local mirror adds its own name as a whole address, since an empty host inside a written address matches every host.
http://viljoen.family:8080, http://viljoen.vps.insanegenius.net:8080 {$FAMILY_SITE_ADDRESS} {
root * /srv/blog/current/family
header {
X-Blog-Env "{$SITE_ENV:unset}"
X-Robots-Tag "{$FAMILY_SITE_ROBOTS:index, follow}"
X-Blog-Release "@@RELEASE@@"
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self'; script-src 'self'; img-src 'self' data:"
}
@image path *.svg *.png *.ico *.woff2
header @image Cache-Control "public, max-age=3600"
@code path *.css *.js
header @code Cache-Control "no-cache"
@page not path *.svg *.png *.ico *.woff2 *.css *.js
header @page Cache-Control "no-cache"
# The root follows the browser's first language, and /en/ and /af/ never redirect, so the language switch always holds.
@af_first {
path /
header_regexp Accept-Language `(?i)^\s*af(-[a-z0-9]+)*\s*(,|$|;\s*q\s*=\s*(1|0?\.0*[1-9]))`
}
header / +Vary Accept-Language
redir @af_first /af/{?query} 302
redir /en /en/ 308
redir /af /af/ 308
rewrite /en/ /index.html
file_server {
precompressed br gzip
}
# A single-page site has nowhere better to send a stray path than the page itself.
handle_errors {
header Cache-Control "no-cache"
@af_page path /af/*
handle @af_page {
rewrite * /af/index.html
file_server {
precompressed br gzip
}
}
handle {
rewrite * /index.html
file_server {
precompressed br gzip
}
}
}
log {
output stdout
format console
}
}