mirror of
				https://github.com/KevinMidboe/infra-map.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	proxy through varnish
sets up varnish cache server with: - custom cache per content type - gzip - configure app & hass backends - varnish debug headers & X-Cache verbose cache header also updates docker-compose, varnish/Dockerfile & drone.
This commit is contained in:
		
							
								
								
									
										41
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -48,6 +48,44 @@ steps: | ||||
|         - latest | ||||
|         - ${DRONE_COMMIT_SHA} | ||||
|  | ||||
| trigger: | ||||
|   event: | ||||
|     include: | ||||
|       - push | ||||
|     exclude: | ||||
|       - pull_request | ||||
|   branch: | ||||
|     - main | ||||
|     - update | ||||
|  | ||||
| depends_on: | ||||
|   - Build | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: Publish | ||||
|  | ||||
| platform: | ||||
|   os: linux | ||||
|   arch: amd64 | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: config-check | ||||
|  | ||||
| steps: | ||||
|   - name: check-config | ||||
|     image: alpine/git | ||||
|     commands: | ||||
|       - git fetch --no-tags --depth=2 | ||||
|       - | | ||||
|         if git diff --quiet HEAD^ HEAD -- varnish/default.vcl; then | ||||
|           echo "No changes in varnish config file, skipping..." | ||||
|           exit 78  # exit code 78 = skip in Drone | ||||
|         else | ||||
|           echo "Changes detected in varnish config" | ||||
|         fi | ||||
|  | ||||
|   - name: Publish varnish to ghcr | ||||
|     image: plugins/docker | ||||
|     settings: | ||||
| @@ -72,7 +110,6 @@ trigger: | ||||
|   branch: | ||||
|     - main | ||||
|     - update | ||||
|  | ||||
| depends_on: | ||||
|   - Build | ||||
|  | ||||
| @@ -146,6 +183,6 @@ volumes: | ||||
|     temp: {} | ||||
| --- | ||||
| kind: signature | ||||
| hmac: b3cc991813f340024e65c68d5509cb23025796914a2e2ac72f71657a347e0708 | ||||
| hmac: 01caa41521eac62356f6fc941cdd489dae8e2c4249bdb4e4dc1a32e101c639b7 | ||||
|  | ||||
| ... | ||||
|   | ||||
							
								
								
									
										21
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| version: '3.8' | ||||
|  | ||||
| services: | ||||
|   app: | ||||
|     build: . | ||||
|     container_name: infra-map | ||||
|     ports: | ||||
|       - '3000:3000' # svelte-kit preview HTTP | ||||
|   varnish: | ||||
|     build: varnish | ||||
|     container_name: varnish-cache | ||||
|     ports: | ||||
|       - '6081:6081' # Varnish HTTP | ||||
|     environment: | ||||
|       - VARNISH_LISTEN_PORT=6081 | ||||
|     command: > | ||||
|       varnishd | ||||
|       -F | ||||
|       -f /etc/varnish/default.vcl | ||||
|       -s malloc,256m | ||||
|       -a :6081 | ||||
| @@ -33,7 +33,8 @@ | ||||
| 	function refetchImage() { | ||||
| 		let url; | ||||
| 		try { | ||||
| 			url = new URL(`${IMAGE_PROXY_URL}/image/${imageUrl}`); | ||||
| 			const { protocol, host } = window.location; | ||||
| 			url = new URL(`${protocol}//${host}/image-proxy/${imageUrl}`); | ||||
| 		} catch { | ||||
| 			console.log('url not valid, returning'); | ||||
| 			return; | ||||
|   | ||||
| @@ -21,6 +21,8 @@ RUN git clone https://github.com/varnish/libvmod-digest.git /opt/libvmod-digest | ||||
|     ./configure VARNISHSRC=/usr/include/varnish && \ | ||||
|     make && make install | ||||
|  | ||||
| COPY . /etc/varnish/ | ||||
|  | ||||
| EXPOSE 6081 | ||||
|  | ||||
| CMD ["varnishd", "-F", "-f", "/etc/varnish/default.vcl", "-a", ":6081", "-s", "malloc,512m"] | ||||
|   | ||||
							
								
								
									
										146
									
								
								varnish/default.vcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								varnish/default.vcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| vcl 4.0; | ||||
|  | ||||
| import std; | ||||
| import digest; | ||||
|  | ||||
| # include "handlers/ttl-override-handler.vcl"; | ||||
| include "includes/x-cache-header.vcl"; | ||||
|  | ||||
| include "vcl_deliver.vcl"; | ||||
|  | ||||
| # Define backend pointing to Home Assistant IP | ||||
| backend hass_backend { | ||||
|     .host = "10.0.0.82"; | ||||
|     .port = "8123"; | ||||
| } | ||||
|  | ||||
| backend app_frontend { | ||||
|     .host = "host.docker.internal"; | ||||
|     .port = "5173"; | ||||
| } | ||||
|  | ||||
| sub vcl_recv { | ||||
|     set req.backend_hint = app_frontend; | ||||
|  | ||||
|     # Handle CORS preflight | ||||
|     if (req.method == "OPTIONS") { | ||||
|         return (synth(204, "Preflight")); | ||||
|     } | ||||
|  | ||||
|     # Rewrite image URL | ||||
|     if (req.url ~ "^/image-proxy/?") { | ||||
|         # Extract everything after /image-proxy/ and store it | ||||
|         set req.http.X-Image-URL = regsub(req.url, "^/image-proxy/(.*)", "\1"); | ||||
|  | ||||
|         # Rewrite req.url to match backend expectations | ||||
|         set req.url = regsub(req.http.X-Image-URL, "^http://[^/]+", ""); | ||||
|  | ||||
|         set req.backend_hint = hass_backend; | ||||
|     } | ||||
|  | ||||
|     # Enable debug headers through query param | ||||
|     if (req.url ~ "(?i)debug=(true|yes|1)") { | ||||
|       set req.http.X-debug = true; | ||||
|     } | ||||
|  | ||||
|     # Remove cookies so content is cacheable | ||||
|     unset req.http.Cookie; | ||||
| } | ||||
|  | ||||
| sub vcl_synth { | ||||
|     if (resp.status == 204) { | ||||
|         set resp.http.Access-Control-Allow-Origin = "*"; | ||||
|         set resp.http.Access-Control-Allow-Methods = "GET, OPTIONS"; | ||||
|         set resp.http.Access-Control-Allow-Headers = "Content-Type, X-Cache-ID"; | ||||
|         set resp.http.Content-Length = "0"; | ||||
|         return (deliver); | ||||
|     } | ||||
|  | ||||
|     if (resp.status == 304) { | ||||
|         set resp.http.ETag = req.http.If-None-Match; | ||||
|         set resp.http.Content-Length = "0"; | ||||
|         return (deliver); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub vcl_backend_response { | ||||
|     ################### | ||||
|     #   cache rules   # | ||||
|     ################### | ||||
|     # HTML pages → short cache or no cache | ||||
|     if (bereq.url ~ "\.html$") { | ||||
|         set beresp.ttl = 30s;         # Cache briefly | ||||
|         set beresp.uncacheable = true; # Or disable cache entirely | ||||
|     } | ||||
|  | ||||
|     # JavaScript & CSS → long cache | ||||
|     if (bereq.url ~ "\.(js|css)$") { | ||||
|         set beresp.ttl = 1d; | ||||
|     } | ||||
|  | ||||
|     # Images under /image/ → long cache | ||||
|     if (bereq.url ~ "^/images/.*\.(svg|png|jpe?g)$") { | ||||
|         set beresp.ttl = 1y; | ||||
|     } | ||||
|  | ||||
|     # Favicons → long cache | ||||
|     if (bereq.url ~ "^/favicons/") { | ||||
|         set beresp.ttl = 1y; | ||||
|     } | ||||
|  | ||||
|     # Fallback: ensure some cache | ||||
|     if (beresp.ttl <= 0s) { | ||||
|         set beresp.ttl = 22s; | ||||
|     } | ||||
|  | ||||
|     set beresp.http.X-TTL = beresp.ttl; | ||||
|  | ||||
|     #################### | ||||
|     #   camera proxy   # | ||||
|     #################### | ||||
|     if (bereq.url ~ "^/api/camera_proxy/") { | ||||
|       set beresp.ttl = 0.3s; | ||||
|       set beresp.grace = 60s; | ||||
|       set beresp.keep = 60s; | ||||
|  | ||||
|       # Ensure ETag is passed to client | ||||
|       if (beresp.http.ETag) { | ||||
|           set beresp.http.X-Cache-ETag = beresp.http.ETag; | ||||
|       } else { | ||||
|           # Optional: generate one if not provided | ||||
|           # set beresp.http.ETag = digest.hash_md5(beresp.body); | ||||
|           set beresp.http.ETag = beresp.http.Content-Length; | ||||
|           set beresp.http.X-Cache-ETag = beresp.http.ETag; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ######################## | ||||
|     #   gzip compression   # | ||||
|     ######################## | ||||
|     if (beresp.http.Content-Type ~ "^(text/|application/json|application/javascript|application/x-javascript|application/xml|application/xhtml+xml|image/svg\+xml)") { | ||||
|         set beresp.do_gzip = true; | ||||
|     } | ||||
|  | ||||
|   return (deliver); | ||||
| } | ||||
|  | ||||
| sub vcl_hit { | ||||
|     if (obj.ttl < 0s && std.healthy(req.backend_hint)) { | ||||
|         return (deliver); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub vcl_deliver { | ||||
|     unset resp.http.X-Image-URL; | ||||
|     set resp.http.Access-Control-Allow-Origin = "*"; | ||||
|  | ||||
|     # Handle conditional request with ETag | ||||
|     if ( | ||||
|         req.http.If-None-Match && | ||||
|         req.http.If-None-Match == resp.http.ETag | ||||
|     ) { | ||||
|         return (synth(304)); | ||||
|     } | ||||
|  | ||||
|     return (deliver); | ||||
| } | ||||
							
								
								
									
										43
									
								
								varnish/includes/x-cache-header.vcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								varnish/includes/x-cache-header.vcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| sub vcl_recv { | ||||
|         unset req.http.X-Cache; | ||||
| } | ||||
|  | ||||
| sub vcl_hit { | ||||
|         set req.http.X-Cache = "HIT"; | ||||
| } | ||||
|  | ||||
| sub vcl_miss { | ||||
|         set req.http.X-Cache = "MISS"; | ||||
| } | ||||
|  | ||||
| sub vcl_pass { | ||||
|         set req.http.X-Cache = "PASS"; | ||||
| } | ||||
|  | ||||
| sub vcl_pipe { | ||||
|         set req.http.X-Cache = "PIPE uncacheable"; | ||||
| } | ||||
|  | ||||
| sub vcl_synth { | ||||
|         set resp.http.X-Cache = "SYNTH"; | ||||
| } | ||||
|  | ||||
| sub vcl_deliver { | ||||
|         if (obj.uncacheable) { | ||||
|                 set req.http.X-Cache = req.http.X-Cache + " uncacheable" ; | ||||
|         } else { | ||||
|                 set req.http.X-Cache = req.http.X-Cache + " cached" + " (real age: " + resp.http.Age + ", hits: " + obj.hits + ", ttl: " + resp.http.x-ttl + ")"; | ||||
|         } | ||||
|  | ||||
|         # if we are gracing, make sure the browser doesn't cache things, and set our maxage to 1 | ||||
|         # also log grace delivery | ||||
|         if (req.http.graceineffect) { | ||||
|                 set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=[0-9]*", "max-age=1"); | ||||
|                 set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "channel-maxage=[0-9]*", "channel-maxage=1"); | ||||
|                 set req.http.X-Cache = req.http.X-Cache + " [grace: " + req.http.graceineffect + " " + req.http.grace + ", remaining: " + req.http.graceduration + "]"; | ||||
|         } | ||||
|  | ||||
|         # uncomment the following line to show the information in the response | ||||
|         set resp.http.X-Cache = req.http.X-Cache; | ||||
| } | ||||
|  | ||||
							
								
								
									
										40
									
								
								varnish/vcl_deliver.vcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								varnish/vcl_deliver.vcl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| sub vcl_deliver { | ||||
|     # Happens when we have all the pieces we need, and are about to send the | ||||
|     # response to the client. | ||||
|  | ||||
|     if (resp.status == 503) { | ||||
|         set resp.http.failing-backend = "true"; | ||||
|     } | ||||
|  | ||||
|     # Give some debug | ||||
|     if (req.http.X-debug && req.esi_level == 0) { | ||||
|         set resp.http.X-Backend = req.backend_hint; | ||||
|         set resp.http.X-Backend-Url = req.url; | ||||
|     } else { | ||||
|         # not debug, strip some headers | ||||
|         unset resp.http.X-Cache; | ||||
|         unset resp.http.X-Backend; | ||||
|         unset resp.http.x-upstream; | ||||
|         unset resp.http.x-request-uri; | ||||
|         unset resp.http.Via; | ||||
|         unset resp.http.xkey; | ||||
|         unset resp.http.x-goog-hash; | ||||
|         unset resp.http.x-goog-generation; | ||||
|         unset resp.http.X-GUploader-UploadID; | ||||
|         unset resp.http.x-goog-storage-class; | ||||
|         unset resp.http.x-goog-metageneration; | ||||
|         unset resp.http.x-goog-stored-content-length; | ||||
|         unset resp.http.x-goog-stored-content-encoding; | ||||
|         unset resp.http.x-goog-meta-goog-reserved-file-mtime; | ||||
|         unset resp.http.Server; | ||||
|         unset resp.http.X-Apache-Host; | ||||
|         unset resp.http.X-Varnish-Backend; | ||||
|         unset resp.http.X-Varnish-Host; | ||||
|         unset resp.http.X-Nginx-Host; | ||||
|         unset resp.http.X-Upstream-Age; | ||||
|         unset resp.http.X-Retries; | ||||
|         unset resp.http.X-TTL; | ||||
|         unset resp.http.X-Varnish; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -4,11 +4,6 @@ import { defineConfig } from 'vite'; | ||||
| export default defineConfig({ | ||||
| 	plugins: [sveltekit()], | ||||
| 	server: { | ||||
| 		proxy: { | ||||
| 			'/proxmox': { | ||||
| 				target: 'https://apollo.schleppe:8006/api2/json', | ||||
| 				secure: false | ||||
| 			} | ||||
| 		} | ||||
| 		allowedHosts: ['host.docker.internal'] | ||||
| 	} | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user