eratic / deploy / eratic.caddy
592 words
  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
# eratic.ro — the four sites plus media.
# ---------------------------------------------------------------------------
# Deployed to /opt/web/caddy/conf.d/eratic.caddy. This is the whole public
# configuration of the site: routing, TLS hostnames, headers, caching. It lives
# in this repository and ships with `npm run deploy`, so the config and the
# content it serves are always the same version.
#
# Snippet names are prefixed `eratic-` because conf.d is shared with every
# other site on the box, and two projects both defining `(site)` would be a
# genuinely confusing afternoon.
#
# Certificates are obtained automatically for every hostname named below.
# Nothing to renew, nothing to remember.

(eratic-hardening) {
	header {
		# One year. After the first visit the browser refuses plain HTTP here.
		Strict-Transport-Security "max-age=31536000; includeSubDomains"

		X-Content-Type-Options "nosniff"
		X-Frame-Options "DENY"
		# Do not tell other people's servers where a reader came from.
		Referrer-Policy "no-referrer"
		Cross-Origin-Opener-Policy "same-origin"
		Cross-Origin-Resource-Policy "same-site"

		# The privacy posture, enforced rather than promised.
		#
		# A third-party script, font, pixel or iframe added by accident does not
		# quietly start tracking people — it fails visibly in the browser
		# console. That is the point: the guarantee should not depend on anyone
		# remembering it.
		#
		# 'unsafe-inline' appears for style-src only, because Astro emits
		# component styles inline and per-article theme overrides are inline
		# custom properties. It is deliberately absent from script-src: Astro
		# bundles <script> tags into external files, so real scripts still work
		# and an inline one is a mistake worth catching.
		Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://media.eratic.ro; media-src 'self' https://media.eratic.ro; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'; object-src 'none'; upgrade-insecure-requests"

		Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), interest-cohort=()"

		-Server
		-X-Powered-By
	}
}

(eratic-caching) {
	# Fingerprinted assets: the filename changes whenever the bytes do, so
	# these are safe to cache forever.
	@immutable path /_astro/*
	header @immutable Cache-Control "public, max-age=31536000, immutable"

	# HTML is always revalidated, or a deploy would not be visible until
	# someone's cache happened to expire.
	@html path *.html /
	header @html Cache-Control "public, max-age=0, must-revalidate"

	# Feeds sit in between. Ten minutes feels live without making a reader who
	# polls every minute free traffic.
	@feeds path /rss.xml /feed.json /sitemap*.xml
	header @feeds Cache-Control "public, max-age=600"
}

(eratic-site) {
	encode zstd gzip
	import eratic-hardening
	import eratic-caching
	file_server

	handle_errors {
		# Astro builds a real 404 page; serve it instead of Caddy's plain text.
		rewrite * /404.html
		file_server
	}

	# Deliberately no access log. Not recording IP addresses is the only way to
	# be certain they are never leaked, backed up or subpoenaed. See
	# docs/06-privacy.md for how to get traffic numbers without keeping a
	# record of individuals.
	log {
		output discard
	}
}

# ---------------------------------------------------------------------------
# The four sites
# ---------------------------------------------------------------------------

eratic.ro {
	root * /srv/eratic/current/home
	import eratic-site
}

blog.eratic.ro {
	root * /srv/eratic/current/blog
	import eratic-site
}

fav.eratic.ro {
	root * /srv/eratic/current/fav
	import eratic-site
}

io.eratic.ro {
	root * /srv/eratic/current/io
	import eratic-site
}

# ---------------------------------------------------------------------------
# One canonical hostname. 308 keeps the method and body, and browsers remember
# it.
# ---------------------------------------------------------------------------

www.eratic.ro {
	redir https://eratic.ro{uri} 308
}

# ---------------------------------------------------------------------------
# media.eratic.ro — video and anything else too large for git.
# ---------------------------------------------------------------------------

media.eratic.ro {
	root * /srv/eratic/media
	encode zstd gzip
	import eratic-hardening

	header {
		# So a <video> on blog.eratic.ro can range-request and seek properly.
		Access-Control-Allow-Origin "https://blog.eratic.ro"
		Access-Control-Allow-Methods "GET, HEAD, OPTIONS"
		Cache-Control "public, max-age=2592000"
	}

	# No directory listings — media is linked explicitly or not at all.
	file_server {
		index ""
	}

	log {
		output discard
	}
}

static mirror of HEAD · about · clone: git clone https://git.makefuture.ro/eratic.git