# 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/ # │ └── / 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 } }