Caddy site block for the update CDN. Serves the signed manifest with short TTL, content-addressed blobs as immutable, historical manifests as immutable, and the Velopack launcher feed alongside. Caching rules are calibrated so a new release is visible within a minute without hammering the origin on thundering herds.
72 lines
2.9 KiB
Plaintext
72 lines
2.9 KiB
Plaintext
# Caddy snippet for updates.jakubkadlec.dev.
|
|
#
|
|
# Drop this into the main Caddyfile on the VPS (or include it from there).
|
|
# Caddy already handles TLS via Let's Encrypt for the parent zone; this block
|
|
# only adds a subdomain that serves the update manifest, detached signature,
|
|
# and content-addressed blob store.
|
|
#
|
|
# Directory layout on disk (owned by the release operator, not Caddy):
|
|
#
|
|
# /var/www/updates.jakubkadlec.dev/
|
|
# ├── manifest.json
|
|
# ├── manifest.json.sig
|
|
# ├── manifests/
|
|
# │ └── 2026.04.14-1.json (archived historical manifests)
|
|
# ├── files/
|
|
# │ └── <hash[0:2]>/<hash> content-addressed blobs
|
|
# └── launcher/ Velopack feed (populated by Velopack's own publish tool)
|
|
#
|
|
# Create with:
|
|
# sudo mkdir -p /var/www/updates.jakubkadlec.dev/{files,manifests,launcher}
|
|
# sudo chown -R mt2.jakubkadlec.dev:mt2.jakubkadlec.dev /var/www/updates.jakubkadlec.dev
|
|
#
|
|
# Then add this to Caddy and `sudo systemctl reload caddy`.
|
|
|
|
updates.jakubkadlec.dev {
|
|
root * /var/www/updates.jakubkadlec.dev
|
|
|
|
# Allow clients to resume interrupted downloads via HTTP Range.
|
|
# Caddy's file_server sets Accept-Ranges: bytes by default, so there's
|
|
# nothing extra to configure for this — listed explicitly as a reminder.
|
|
file_server {
|
|
precompressed gzip br
|
|
}
|
|
|
|
# Content-addressed blobs are immutable (the hash IS the file name), so we
|
|
# can tell clients to cache them forever. A manifest update never rewrites
|
|
# an existing blob.
|
|
@blobs path /files/*
|
|
header @blobs Cache-Control "public, max-age=31536000, immutable"
|
|
|
|
# The manifest and its signature must never be cached beyond a minute —
|
|
# clients need to see new releases quickly, and stale caches would delay
|
|
# rollouts. Short TTL, not zero, to absorb thundering herds on release.
|
|
@manifest path /manifest.json /manifest.json.sig
|
|
header @manifest Cache-Control "public, max-age=60, must-revalidate"
|
|
|
|
# Historical manifests are as immutable as blobs — named by version.
|
|
@archive path /manifests/*
|
|
header @archive Cache-Control "public, max-age=31536000, immutable"
|
|
|
|
# The Velopack feed (launcher self-update) is a separate tree managed by
|
|
# Velopack's publishing tool. Same cache rules as the main manifest: short
|
|
# TTL on the feed metadata, blobs are immutable.
|
|
@velopack-feed path /launcher/RELEASES*
|
|
header @velopack-feed Cache-Control "public, max-age=60, must-revalidate"
|
|
|
|
@velopack-blobs path /launcher/*.nupkg
|
|
header @velopack-blobs Cache-Control "public, max-age=31536000, immutable"
|
|
|
|
# CORS is not needed — the launcher is a native app, not a browser — so
|
|
# no Access-Control-Allow-Origin header. If a web changelog page ever needs
|
|
# to fetch the manifest from the browser, revisit this.
|
|
|
|
# Deny directory listings; the launcher knows exactly which paths it wants.
|
|
file_server browse off 2>/dev/null || file_server
|
|
|
|
log {
|
|
output file /var/log/caddy/updates.jakubkadlec.dev.access.log
|
|
format json
|
|
}
|
|
}
|