spa/deploy: dynamische site-url via __SITE_URL__-platzhalter, staging + prod als deploy-targets

app.html nutzt __SITE_URL__ als platzhalter in og:url und canonical.
deploy-svelte.sh ersetzt ihn nach dem build pro ziel via sed:
  - svelte  → https://svelte.joerg-lohrer.de  (default, bisheriger SVELTE_FTP_-pfad)
  - staging → https://staging.joerg-lohrer.de (STAGING_FTP_-pfad, webroot joerglohrer26)
  - prod    → https://joerg-lohrer.de        (STAGING_FTP_-pfad, cutover-ziel)

env-auslese aus .env.local nicht mehr via `source` (bricht bei
sonderzeichen im passwort), sondern via awk pro schlüssel. build wird
jetzt vom deploy-skript angestoßen, damit immer gegen den frischen
html-stand gebaut wird.

app/.env.example dokumentiert PUBLIC_SITE_URL (derzeit ungenutzt, da
der platzhalter-ansatz zuverlässiger ist als runtime-env für prerender).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jörg Lohrer 2026-04-18 10:01:08 +02:00
parent 75ad8b87fa
commit 34c62cb944
3 changed files with 94 additions and 20 deletions

9
app/.env.example Normal file
View File

@ -0,0 +1,9 @@
# Öffentliche Site-URL für Canonical-Link und og:url-Meta-Tags.
# Zur Build-Zeit fest; gilt domain-übergreifend (svelte./staging./haupt-
# domain). Für jeden Deploy-Zweck kann eine andere URL gesetzt werden.
#
# Beispiele:
# PUBLIC_SITE_URL=https://svelte.joerg-lohrer.de
# PUBLIC_SITE_URL=https://staging.joerg-lohrer.de
# PUBLIC_SITE_URL=https://joerg-lohrer.de
PUBLIC_SITE_URL=https://joerg-lohrer.de

View File

@ -6,8 +6,9 @@
<meta name="description" content="Jörg Lohrer Blog (Nostr-basiert)" />
<meta property="og:title" content="Jörg Lohrer Blog" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://svelte.joerg-lohrer.de/" />
<meta property="og:url" content="__SITE_URL__/" />
<meta property="og:description" content="Jörg Lohrer Blog (Nostr-basiert)" />
<link rel="canonical" href="__SITE_URL__/" />
<meta name="robots" content="index, follow" />
<title>Jörg Lohrer</title>
<style>

View File

@ -1,6 +1,15 @@
#!/usr/bin/env bash
# Deploy: SvelteKit-Build nach svelte.joerg-lohrer.de per FTPS.
# Credentials kommen aus ./.env.local (gitignored), Variablen-Prefix SVELTE_FTP_.
# Deploy: SvelteKit-Build per FTPS nach einem der All-Inkl-Webroots.
# Credentials kommen aus ./.env.local (gitignored), Variablen-Prefix je Ziel.
#
# Zielauswahl via DEPLOY_TARGET-Env-Variable:
# - DEPLOY_TARGET=svelte (default) → svelte.joerg-lohrer.de via SVELTE_FTP_*
# - DEPLOY_TARGET=staging → staging.joerg-lohrer.de via STAGING_FTP_*
#
# Beispiele:
# ./scripts/deploy-svelte.sh # default: svelte
# DEPLOY_TARGET=staging ./scripts/deploy-svelte.sh # staging-probelauf
# DEPLOY_TARGET=svelte ./scripts/deploy-svelte.sh # explizit
set -euo pipefail
@ -12,44 +21,99 @@ if [ ! -f .env.local ]; then
exit 1
fi
# nur SVELTE_FTP_* exportieren (via Tempfile — process substitution ist nicht
# überall verfügbar, je nach Shell/Sandbox).
_ENV_TMP="$(mktemp)"
trap 'rm -f "$_ENV_TMP"' EXIT
grep -E '^SVELTE_FTP_' .env.local > "$_ENV_TMP" || true
set -a
# shellcheck disable=SC1090
. "$_ENV_TMP"
set +a
TARGET="${DEPLOY_TARGET:-svelte}"
case "$TARGET" in
svelte)
PREFIX="SVELTE_FTP_"
PUBLIC_URL="https://svelte.joerg-lohrer.de/"
SITE_URL="https://svelte.joerg-lohrer.de"
;;
staging)
PREFIX="STAGING_FTP_"
PUBLIC_URL="https://staging.joerg-lohrer.de/"
SITE_URL="https://staging.joerg-lohrer.de"
;;
prod)
# Deploy auf staging-ftp (joerglohrer26 = aktueller cutover-webroot),
# aber mit og:url auf der hauptdomain.
PREFIX="STAGING_FTP_"
PUBLIC_URL="https://joerg-lohrer.de/"
SITE_URL="https://joerg-lohrer.de"
;;
*)
echo "FEHLER: unbekanntes DEPLOY_TARGET='$TARGET' (erlaubt: svelte, staging, prod)." >&2
exit 2
;;
esac
for v in SVELTE_FTP_HOST SVELTE_FTP_USER SVELTE_FTP_PASS SVELTE_FTP_REMOTE_PATH; do
if [ -z "${!v:-}" ]; then
echo "FEHLER: $v fehlt in .env.local." >&2
# Werte direkt aus .env.local lesen (nicht via `source`, weil
# password-shell-metazeichen wie ( ) & kein sourcing überstehen).
read_env() {
local key="$1"
# nimmt die erste zeile, die exakt mit KEY= beginnt, schneidet alles nach
# dem ersten = ab, gibt den rest 1:1 zurück (auch mit sonderzeichen).
awk -F= -v k="$key" 'BEGIN{found=0} $1==k && !found { sub("^" k "=",""); print; found=1 }' .env.local
}
FTP_HOST_KEY="${PREFIX}HOST"
FTP_USER_KEY="${PREFIX}USER"
FTP_PASS_KEY="${PREFIX}PASS"
FTP_PATH_KEY="${PREFIX}REMOTE_PATH"
FTP_HOST="$(read_env "$FTP_HOST_KEY")"
FTP_USER="$(read_env "$FTP_USER_KEY")"
FTP_PASS="$(read_env "$FTP_PASS_KEY")"
FTP_REMOTE_PATH="$(read_env "$FTP_PATH_KEY")"
for pair in "$FTP_HOST_KEY:$FTP_HOST" "$FTP_USER_KEY:$FTP_USER" \
"$FTP_PASS_KEY:$FTP_PASS" "$FTP_PATH_KEY:$FTP_REMOTE_PATH"; do
key="${pair%%:*}"
val="${pair#*:}"
if [ -z "$val" ]; then
echo "FEHLER: $key fehlt in .env.local." >&2
exit 1
fi
done
BUILD_DIR="$ROOT/app/build"
echo "Baue SvelteKit …"
(cd "$ROOT/app" && npm run build >/dev/null 2>&1) || {
echo "FEHLER: Build fehlgeschlagen. 'cd app && npm run build' manuell ausführen zum Debuggen." >&2
exit 1
}
if [ ! -d "$BUILD_DIR" ]; then
echo "FEHLER: app/build nicht vorhanden. Bitte vorher 'npm run build' in app/ ausführen." >&2
echo "FEHLER: app/build nicht vorhanden nach build." >&2
exit 1
fi
echo "Lade Build von $BUILD_DIR nach ftp://$SVELTE_FTP_HOST$SVELTE_FTP_REMOTE_PATH"
# __SITE_URL__-Platzhalter in allen HTML-Dateien durch die ziel-spezifische
# SITE_URL ersetzen (für og:url / canonical). Nicht im quellcode hart
# setzen, damit ein builder einmal baut und mehrere domains damit bedienen
# kann.
echo "Patche __SITE_URL__ → $SITE_URL in HTML-Dateien …"
find "$BUILD_DIR" -type f -name "*.html" -print0 | while IFS= read -r -d '' html_file; do
# sed -i '' für macOS-kompatibilität (bsd sed braucht leeres backup-arg)
sed -i '' "s|__SITE_URL__|$SITE_URL|g" "$html_file"
done
echo "Ziel: $TARGET ($PUBLIC_URL)"
echo "Lade Build von $BUILD_DIR nach ftp://$FTP_HOST$FTP_REMOTE_PATH"
# pro Datei ein curl-Upload (zuverlässig auf macOS ohne lftp)
find "$BUILD_DIR" -type f -print0 | while IFS= read -r -d '' local_file; do
rel="${local_file#$BUILD_DIR/}"
remote="ftp://$SVELTE_FTP_HOST${SVELTE_FTP_REMOTE_PATH%/}/$rel"
remote="ftp://$FTP_HOST${FTP_REMOTE_PATH%/}/$rel"
echo "$rel"
# --tls-max 1.2: All-Inkl/Kasserver FTPS schließt bei TLS 1.3 die Data-
# Connection mit "426 Transfer aborted" — mit 1.2 läuft es sauber durch.
curl -sSf --ssl-reqd --tls-max 1.2 --ftp-create-dirs \
--retry 3 --retry-delay 2 --retry-all-errors \
--connect-timeout 15 \
--user "$SVELTE_FTP_USER:$SVELTE_FTP_PASS" \
--user "$FTP_USER:$FTP_PASS" \
-T "$local_file" "$remote"
done
echo "Upload fertig. Live-Check:"
curl -sIL "https://svelte.joerg-lohrer.de/" | head -5
curl -sIL "$PUBLIC_URL" | head -5