feat: expand portfolios, drop wedding crowd photos, offer studio on site

- 28 more of the operator's own public images (42 -> 67), alts from his captions
- remove group-feier, group-outdoor, group-posiert (wedding guests visible)
- studio section now offers Studio Neumuenster and Mobiles Studio
- deploy: --force-recreate on release and rollback, plus in-container content probe
- verify: DESFOTO_BASE dry-run, redirect, asset and privacy checks
- tests: 57 OK (3 new content guards, 2 deploy/verify regression guards)
This commit is contained in:
desfoto automation
2026-09-19 17:37:49 +02:00
parent 447b909d05
commit 8ffeea6731
141 changed files with 476 additions and 72 deletions

View File

@@ -12,7 +12,13 @@ cd "$(dirname "$0")/.."
root="$(pwd)"
release_sha="${1:-unknown}"
remote="${DESFOTO_REMOTE:-prod-main}"
base="https://desfoto.de"
# DESFOTO_BASE lets the hook be dry-run against a local container
# (DESFOTO_BASE=http://127.0.0.1:18430 .ocauto/verify unknown); the
# domain-specific sections (deployed tree, canonical host, neighbours, TLS)
# only run against the real production origin.
base="${DESFOTO_BASE:-https://desfoto.de}"
live=0
if [ "$base" = "https://desfoto.de" ]; then live=1; fi
failures=0
pass() { printf ' ok %s\n' "$1"; }
@@ -44,8 +50,29 @@ check_header() {
fi
}
check_redirect() {
local url="$1" want="$2" got target
got="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 "$url" || echo 000)"
target="$(curl -sS -o /dev/null -w '%{redirect_url}' --max-time 30 "$url" || true)"
if [ "$got" = "301" ] && [ "$target" = "$want" ]; then
pass "$url -> 301 $want"
else
fail "$url -> $got ${target:-<no Location>} (want 301 $want)"
fi
}
check_not_contains() {
local url="$1" needle="$2" body
body="$(curl -sS --max-time 30 "$url" || true)"
if printf '%s' "$body" | grep -qF -- "$needle"; then
fail "$url still contains '$needle'"
else
pass "$url no longer contains '$needle'"
fi
}
printf '\n== release identity\n'
if [ "$release_sha" != "unknown" ] && git -C "$root" rev-parse --verify --quiet "$release_sha" >/dev/null; then
if [ "$live" = 1 ] && [ "$release_sha" != "unknown" ] && git -C "$root" rev-parse --verify --quiet "$release_sha" >/dev/null; then
local_hash="$(cd "$root/site" && find . -type f ! -name sitemap.xml ! -path './.well-known/*' -print0 |
sort -z | xargs -0 sha256sum | sha256sum | cut -d' ' -f1)"
remote_hash="$(ssh "$remote" "sudo sh -c 'cd /home/denny/stacks/desfoto/site && find . -type f ! -name sitemap.xml ! -path ./.well-known/\* -print0 | sort -z | xargs -0 sha256sum | sha256sum'" | cut -d' ' -f1)"
@@ -63,12 +90,18 @@ if [ "$release_sha" != "unknown" ] && git -C "$root" rev-parse --verify --quiet
fi
printf '\n== routes\n'
for route in / /fotografie/ /businessfotografie/ /portrait-und-model/ /familien-und-paare/ \
for route in / /shootings/ /businessfotografie/ /portrait-und-model/ /familien-und-paare/ \
/minishootings/ /video/ /social-media/ /projekte/ /ueber/ /kontakt/ /impressum/ /datenschutz/; do
check_status "$base$route" 200
done
# The retired photo URL must keep working: it is the umbrella page now.
check_redirect "$base/fotografie" "$base/shootings/"
check_redirect "$base/fotografie/" "$base/shootings/"
check_redirect "$base/fotografie.html" "$base/shootings/"
printf '\n== canonical host and redirects\n'
if [ "$live" = 1 ]; then
www_status="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 https://www.desfoto.de/ || echo 000)"
www_target="$(curl -sS -o /dev/null -w '%{redirect_url}' --max-time 30 https://www.desfoto.de/ || true)"
if [ "$www_status" = "301" ] && [ "$www_target" = "https://desfoto.de/" ]; then
@@ -81,15 +114,25 @@ case "$http_target" in
https://desfoto.de/*) pass "http://desfoto.de/ -> $http_target" ;;
*) fail "http://desfoto.de/ redirects to '$http_target'" ;;
esac
fi
printf '\n== content\n'
check_contains "$base/" "Bilder und Filme, die nicht beliebig aussehen"
check_contains "$base/" 'href="/shootings/"'
check_contains "$base/" 'href="/video/"'
check_contains "$base/" "Mobiles Studio"
check_contains "$base/" "Studio Neumünster"
check_not_contains "$base/" 'href="/fotografie/'
check_contains "$base/shootings/" "Autarke Blitzanlage"
check_contains "$base/shootings/" "Minishootings"
check_contains "$base/impressum/" "§ 5 DDG"
check_contains "$base/impressum/" "DE462149560"
check_contains "$base/datenschutz/" "keine Zugriffsprotokolle"
check_contains "$base/datenschutz/" "youtube-nocookie.com"
check_contains "$base/video/" "NWWFTf7l8g0"
check_contains "$base/sitemap.xml" "<loc>https://desfoto.de/video/</loc>"
check_contains "$base/" "https://www.dennyschulz.de/"
check_contains "$base/" "https://dennyapp.de/"
check_contains "$base/sitemap.xml" "<loc>https://desfoto.de/shootings/</loc>"
check_not_contains "$base/sitemap.xml" "<loc>https://desfoto.de/fotografie/</loc>"
printf '\n== error handling and metadata\n'
check_status "$base/diese-seite-gibt-es-nicht/" 404
@@ -98,11 +141,22 @@ check_status "$base/.well-known/security.txt" 200
check_status "$base/robots.txt" 200
printf '\n== assets\n'
check_status "$base/assets/img/hero-studio-1200.webp" 200
check_status "$base/assets/img/logo-160.png" 200
check_status "$base/assets/img/favicon-32.png" 200
check_status "$base/assets/img/og-shootings.jpg" 200
check_status "$base/assets/site.css" 200
check_status "$base/assets/fonts/fraunces-latin.woff2" 200
check_status "$base/assets/img/og-desfoto.jpg" 200
# The portfolio expansion ships the operator's own public work...
check_status "$base/assets/img/music-dreadlocks-1200.webp" 200
check_status "$base/assets/img/business-hotel-window-1200.webp" 200
check_status "$base/assets/img/couple-park-1200.webp" 200
check_status "$base/assets/img/editorial-leap-1200.webp" 200
# ...and the wedding pictures showing the guests stay removed.
check_status "$base/assets/img/group-feier-1200.webp" 404
check_status "$base/assets/img/group-outdoor-1200.webp" 404
check_status "$base/assets/img/group-posiert-1200.webp" 404
check_contains "$base/projekte/" "music-dreadlocks-1200.webp"
check_contains "$base/familien-und-paare/" "couple-park-1200.webp"
printf '\n== privacy promises and headers\n'
check_header "$base/" "Strict-Transport-Security"
@@ -116,12 +170,14 @@ else
fi
printf '\n== neighbouring sites untouched\n'
if [ "$live" = 1 ]; then
check_status "https://www.dennyschulz.de/" 200
check_contains "https://www.dennyschulz.de/impressum" "Denny Schulz"
check_status "https://dennyapp.de/" 200
fi
printf '\n== TLS\n'
if command -v openssl >/dev/null 2>&1; then
if [ "$live" = 1 ] && command -v openssl >/dev/null 2>&1; then
if echo | openssl s_client -servername desfoto.de -connect desfoto.de:443 2>/dev/null |
openssl x509 -noout -checkend 604800 -subject 2>/dev/null; then
pass "certificate for desfoto.de is valid for at least 7 more days"