Download
139 lines 5.4 KiBTextModified 2026-10-28 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
# httpd(8) configuration for a Repo Renderer site, for OpenBSD 8.0 or later
# (which can set response headers).
#
# Copy it to /etc/repo-render/httpd.conf and edit it for your site:
#
#   - change code.example.com in the server line to your host name
#   - if your site has other names, add an alias line for each
#   - if you don't use a private site, delete its section below
#   - if /etc/httpd.conf already has a types block, delete the one here
#
# The paths are inside httpd's chroot, /var/www, so "/htdocs/repo-render"
# is the renderer's htdocs, /var/www/htdocs/repo-render. If you put the
# site somewhere else, change root, errdocs and the private site's root to
# match.
#
# Then add this line to /etc/httpd.conf, and check and reload httpd:
#
#     include "/etc/repo-render/httpd.conf"
#
#     httpd -n && rcctl reload httpd
#
# httpd takes the first location that matches a request, and only that
# one, so a rule you add goes in the existing location for its path. Keep
# the headers: the pages carry no Content Security Policy of their own,
# and the sandbox policy on /raw and /svg stops a repository's own HTML
# and SVG from running as part of the site.

# httpd allows one types block in all: take this out if /etc/httpd.conf
# has one. It gives raw files their real types (.webp, .pdf, …).
types {
	include "/usr/share/misc/mime.types"
}

server "code.example.com" {
	# alias "other.example.com"           # other names for the site
	listen on * port 8080                 # plain HTTP, for relayd(8) to forward to
	log style forwarded                   # log the client relayd passes on
	root "/htdocs/repo-render"
	default type text/plain
	errdocs "/htdocs/repo-render/err"
	gzip-static                           # serve the renderer's .gz twins

	header set "X-Content-Type-Options" "nosniff" always
	header set "Referrer-Policy" "no-referrer" always
	header set "Content-Security-Policy" "default-src 'none'; style-src 'self'; img-src 'self'; script-src 'self'; connect-src 'self'; worker-src 'self'; frame-src 'self'; frame-ancestors 'self'" always

	# acme-client(1)'s challenges, for the certificates relayd serves.
	location "/.well-known/acme-challenge/*" {
		root "/acme"
		request strip 2
	}

	# ---- the private site: delete down to "end of the private site" if
	# you don't set private_htdocs. It is the public rules again under
	# /private/, behind a login from htpasswd(1)'s file, and cached by the
	# browser alone. The last location keeps everything else under
	# /private/ behind the login too.
	location match "^/private/code/(..-)/?$" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		request rewrite "/private/code/%1.html"
	}
	location match "^/private/types/(..-)/?$" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		request rewrite "/private/types/%1.html"
	}
	location "/private/search/*/corpus.json" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		header set "Cache-Control" "private, max-age=31536000, immutable"
	}
	location "/private/raw/*" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		no gzip-static
		header set "Content-Security-Policy" "sandbox" always
	}
	location "/private/svg/*" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		header set "Content-Security-Policy" "sandbox" always
	}
	location "/private/snapshot/*" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		header set "Cache-Control" "private, max-age=31536000, immutable"
	}
	location "/private/git/*/objects/pack/*" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
		header set "Cache-Control" "private, max-age=31536000, immutable"
	}
	location "/private/*" {
		authenticate "Private repositories" with "/conf/repo-render.htpasswd"
		root "/private/repo-render"
	}
	# ---- end of the private site

	# Pages have no .html in their URLs: /code/r/src and /code/r/src/ are
	# code/r/src.html (and /code/r/foo.html is foo.html.html).
	location match "^/code/(..-)/?$" {
		request rewrite "/code/%1.html"
	}
	location match "^/types/(..-)/?$" {
		request rewrite "/types/%1.html"
	}

	# What is named by its contents is cached for good; everything else
	# gets httpd's default, no-cache, so no page outlives a push.
	location "/search/*/corpus.json" {
		header set "Cache-Control" "public, max-age=31536000, immutable"
	}
	location "/static/*" {
		header set "Cache-Control" "public, max-age=31536000, immutable"
	}

	# The repositories' own files, byte for byte, and themed SVGs. With
	# gzip-static off, a request for foo gets foo even when the repository
	# also has a foo.gz.
	location "/raw/*" {
		no gzip-static
		header set "Content-Security-Policy" "sandbox" always
	}
	location "/svg/*" {
		header set "Content-Security-Policy" "sandbox" always
	}

	# A branch's latest tarball is named by its commit, so it is cached for
	# good. A tag can move, so its tarball, in /archive/, keeps the default.
	location "/snapshot/*" {
		header set "Cache-Control" "public, max-age=31536000, immutable"
	}
	# The clone export, for dumb-HTTP clones; packs are named by their
	# contents.
	location "/git/*/objects/pack/*" {
		header set "Cache-Control" "public, max-age=31536000, immutable"
	}
}