diff --git a/deploy/caddy-site.snippet b/deploy/caddy-site.snippet new file mode 100644 index 0000000..550c8c4 --- /dev/null +++ b/deploy/caddy-site.snippet @@ -0,0 +1,99 @@ +# deploy/caddy-site.snippet +# Caddy-motsvarigheten till nginx.conf för imagesbyolofsson.se (statisk dist/ från `astro build`, +# build.format "file": /about -> about.html, 404 -> 404.html). +# +# Användning i Caddyfile (Caddy >= 2.7): +# +# import /etc/caddy/snippets/caddy-site.snippet +# +# www.imagesbyolofsson.se { +# import ibo_www_redirect +# } +# imagesbyolofsson.se { +# # Befintliga specialregler (t.ex. handle /download/original* -> Immich-proxyn) +# # ska ligga FÖRE importen, så att de vinner över try_files/file_server. +# import ibo_site /srv/imagesbyolofsson/dist +# } +# +# Argumentet är sökvägen till dist-katalogen på servern (justera till den riktiga). + +(ibo_www_redirect) { + redir https://imagesbyolofsson.se{uri} permanent +} + +(ibo_site) { + root * {args[0]} + encode zstd gzip + + # --- Säkerhetsheaders --- + # CSP: tillåter Meta Pixel (laddas först efter samtycke), anonym statistik via sendBeacon + # till customer.imagesbyolofsson.se och Cloudflares egna skript (/cdn-cgi, ev. Web Analytics). + # 'unsafe-inline' behövs för samtyckesskriptet och Astros inlinade skript/stilar. + header { + Strict-Transport-Security "max-age=31536000" + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()" + Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://connect.facebook.net https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://www.facebook.com https://*.facebook.com; font-src 'self' data:; connect-src 'self' https://customer.imagesbyolofsson.se https://www.facebook.com https://*.facebook.com https://connect.facebook.net https://cloudflareinsights.com; frame-src https://www.facebook.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests" + -Server + } + + # --- Cache-Control (ömsesidigt uteslutande matchers) --- + @ibo_immutable path /_astro/* + header @ibo_immutable Cache-Control "public, max-age=31536000, immutable" + + @ibo_media { + path *.jpg *.jpeg *.png *.webp *.avif *.gif *.svg *.ico *.woff *.woff2 *.ttf *.otf + not path /_astro/* + } + header @ibo_media Cache-Control "public, max-age=2592000" + + @ibo_textfiles path *.xml *.txt + header @ibo_textfiles Cache-Control "public, max-age=3600" + + @ibo_pages { + not path /_astro/* + not path *.jpg *.jpeg *.png *.webp *.avif *.gif *.svg *.ico *.woff *.woff2 *.ttf *.otf *.xml *.txt + } + header @ibo_pages Cache-Control "public, max-age=0, must-revalidate" + + # --- 301:or --- + # Gamla indexerade adresser -> portfolion. + @ibo_old path /my-people /my-people/ /animales-and-nature /animales-and-nature/ /celebrations-with-family /celebrations-with-family/ /northen-lights /northen-lights/ + redir @ibo_old /portfolio permanent + + # /index.html -> / + redir /index.html / permanent + + # /about.html -> /about (404-sidan hanteras separat nedan) + @ibo_htmlext { + path_regexp ibo_htmlext ^(/.+)\.html$ + not path /404.html + } + redir @ibo_htmlext {re.ibo_htmlext.1}{?query} permanent + + # Avslutande snedstreck: /about/ -> /about + @ibo_trailing path_regexp ibo_trailing ^(/.+)/$ + redir @ibo_trailing {re.ibo_trailing.1}{?query} permanent + + # 404-sidan ska bara synas som svar på okända adresser, inte som egen URL med 200. + @ibo_404page path /404 /404.html + error @ibo_404page 404 + + # --- Statiska filer --- + try_files {path} {path}.html + file_server + + # Riktig 404: 404.html med status 404 (inte startsidan med 200). + handle_errors { + @ibo_notfound expression {err.status_code} == 404 + handle @ibo_notfound { + root * {args[0]} + rewrite * /404.html + file_server { + status 404 + } + } + } +} diff --git a/nginx.conf b/nginx.conf index 242f4a1..58fd60e 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,11 +1,20 @@ -map $sent_http_content_type $cache_control_strategy { - default "public, max-age=2592000"; - ~^text/html "no-store, must-revalidate"; - ~^text/css "public, max-age=31536000, immutable"; - ~^application/javascript "public, max-age=31536000, immutable"; - ~^application/json "no-store, must-revalidate"; - ~^image/ "public, max-age=2592000"; - ~^font/ "public, max-age=31536000, immutable"; +# nginx för imagesbyolofsson.se (Docker-image: dist/ i /usr/share/nginx/html). +# Bygget använder build.format "file": /about -> about.html, 404 -> 404.html. +# Samma regler som Caddy-varianten i deploy/caddy-site.snippet. + +# Cache-Control per sökväg (inte per Content-Type, så att /_astro/*.jpg också blir immutable). +map $uri $ibo_cache_control { + default "public, max-age=0, must-revalidate"; + ~^/_astro/ "public, max-age=31536000, immutable"; + ~*\.(?:jpe?g|png|webp|avif|gif|svg|ico|woff2?|ttf|otf)$ "public, max-age=2592000"; + ~*\.(?:xml|txt)$ "public, max-age=3600"; +} + +# www -> apex (om www-trafik når den här containern). +server { + listen 80; + server_name www.imagesbyolofsson.se; + return 301 https://imagesbyolofsson.se$request_uri; } server { @@ -14,33 +23,68 @@ server { root /usr/share/nginx/html; index index.html; + # Relativa Location-headers: bakom Cloudflare/proxy blir det aldrig http://host:80/... + absolute_redirect off; + server_tokens off; + + # Säkerhetsheaders. OBS: inga add_header i location-blocken nedan, annars ärvs inte dessa. + # CSP: tillåter Meta Pixel (connect.facebook.net/facebook.com, laddas först efter samtycke), + # den anonyma statistiken (sendBeacon till customer.imagesbyolofsson.se) och Cloudflares + # egna skript (/cdn-cgi, ev. Web Analytics). 'unsafe-inline' behövs för samtyckesskriptet + # och Astros inlinade skript/stilar; byt mot hashar (Astro experimental.csp) i ett senare steg. + add_header Strict-Transport-Security "max-age=31536000" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; - add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; - add_header Cache-Control $cache_control_strategy always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://connect.facebook.net https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://www.facebook.com https://*.facebook.com; font-src 'self' data:; connect-src 'self' https://customer.imagesbyolofsson.se https://www.facebook.com https://*.facebook.com https://connect.facebook.net https://cloudflareinsights.com; frame-src https://www.facebook.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests" always; + add_header Cache-Control $ibo_cache_control always; + + # Gamla indexerade adresser -> närmaste nya sida. + location ~ ^/(?:my-people|animales-and-nature|celebrations-with-family|northen-lights)/?$ { + return 301 /portfolio; + } + + # /index.html och /about.html -> rena URL:er (bara externa anrop; try_files nedan + # serverar filerna internt utan att gå hit). + location = /index.html { + return 301 /; + } + location ~ ^(?/.+)\.html$ { + return 301 $ibo_noext$is_args$args; + } + + # Inga dubbletter med avslutande snedstreck: /about/ -> /about. + location ~ ^(?/.+)/$ { + return 301 $ibo_noslash$is_args$args; + } + + # Startsidan serveras direkt (ingen intern omdirigering till /index.html). + location = / { + try_files /index.html =404; + } + + # Hashade Astro-filer: cachas ett år (immutable sätts via map ovan). + location ^~ /_astro/ { + access_log off; + try_files $uri =404; + } + + # Originalnedladdning (/download/original) proxas till Immich någon annanstans + # (Cloudflare/Caddy). Finns den regeln i den här nginx: lägg den här ovanför. location / { - try_files $uri $uri/ $uri.html $uri/index.html =404; + try_files $uri $uri.html =404; } - location /_astro/ { - access_log off; - expires 1y; - } - - location ~* \.(webp|avif|jpg|jpeg|png|gif|svg|ico|woff2?|ttf|otf|eot)$ { - access_log off; - expires 30d; + # Riktig 404: 404.html med status 404 (inte startsidan med 200). + error_page 404 /404.html; + location = /404.html { + internal; } gzip on; gzip_vary on; gzip_min_length 1024; - gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss image/svg+xml; - - error_page 404 /404.html; - location = /404.html { - internal; - } + gzip_types text/plain text/css text/xml application/xml application/json application/javascript application/xml+rss image/svg+xml; }