- dev/betrieb/deploy.sh: Deploy nur per Tag, mit --seed (Seeder im Wartungsmodus) und --initial (Vorschau/Live einmalig an Ort und Stelle auf Git umstellen) - dev/betrieb/vorschau-auffrischen.sh: Live-DB und Live-Medien in die Vorschau (Generalprobe) - InteriorReleaseSeeder: alle Inhaltsschritte des Relaunchs in einem Lauf, idempotent; Displays, Playlists und Mediathek bleiben unberührt - DisplayInteriorBrandingSeeder überschreibt keinen inzwischen gepflegten Claim mehr - Monitoring: GlitchTip (Server + Browser), /health mit Token, Scheduler-Herzschlag, Alarme per Mail und ntfy, Version in about und /health - public/build versioniert; .gitignore: macOS-Muster Icon traf resources/views/flux/icon/ - Runbook: dev/betrieb/README.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
175 lines
6.2 KiB
Bash
Executable file
175 lines
6.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
#
|
||
# Vorschau mit den aktuellen Live-Daten auffrischen (B2in). Ablauf: dev/betrieb/README.md.
|
||
#
|
||
# Kopiert die komplette Live-Datenbank und die hochgeladenen Medien (storage/app/public:
|
||
# Display-Mediathek, CMS-Mediathek, Videos, QR-Codes) in die Vorschau. Danach steht auf der
|
||
# Vorschau genau das, was der Kunde auf Live gepflegt hat – Displays, Playlists, Mediathek –,
|
||
# und der Release-Seeder kann darauf zur Probe laufen.
|
||
#
|
||
# Live wird nur gelesen (mysqldump --single-transaction, rsync als Quelle). Die Vorschau-
|
||
# Datenbank wird vollständig ersetzt.
|
||
#
|
||
# Aufruf (auf dem Server, als App-Benutzer):
|
||
#
|
||
# bash dev/betrieb/vorschau-auffrischen.sh <live-verzeichnis> <vorschau-verzeichnis> [--seed=<Klasse>]
|
||
#
|
||
# --seed=<Klasse> nach dem Import (und den Migrationen) diesen Seeder auf der Vorschau ausführen,
|
||
# z. B. --seed=InteriorReleaseSeeder für eine erneute Generalprobe.
|
||
#
|
||
# Die Live-Sicherung bleibt als ~/b2in-live-<zeit>.sql.gz liegen.
|
||
|
||
set -euo pipefail
|
||
|
||
live="${1:-}"
|
||
preview="${2:-}"
|
||
shift 2 || true
|
||
seeders=()
|
||
|
||
for option in "$@"; do
|
||
case "$option" in
|
||
--seed=*)
|
||
seeder="${option#--seed=}"
|
||
if [[ ! "$seeder" =~ ^[A-Za-z][A-Za-z0-9]*$ ]]; then
|
||
echo "Ungültiger Seeder: $seeder" >&2; exit 2
|
||
fi
|
||
seeders+=("$seeder") ;;
|
||
*) echo "Unbekannte Option: $option" >&2; exit 2 ;;
|
||
esac
|
||
done
|
||
|
||
if [[ -z "$live" || -z "$preview" || ! -f "$live/artisan" || ! -f "$preview/artisan" ]]; then
|
||
echo "Aufruf: bash $0 <live-verzeichnis> <vorschau-verzeichnis> [--seed=<Klasse>]" >&2
|
||
exit 2
|
||
fi
|
||
|
||
live="$(cd "$live" && pwd)"
|
||
preview="$(cd "$preview" && pwd)"
|
||
|
||
if [[ "$live" == "$preview" ]]; then
|
||
echo "Live- und Vorschau-Verzeichnis sind dasselbe." >&2
|
||
exit 2
|
||
fi
|
||
|
||
step() { printf '\n==> %s\n' "$*"; }
|
||
|
||
# Wert aus einer .env lesen (ohne Anführungszeichen, ohne Kommentar am Zeilenende bei unquotierten Werten)
|
||
env_value() {
|
||
local file="$1" key="$2" line value
|
||
line="$(grep -E "^${key}=" "$file" | tail -n 1 || true)"
|
||
value="${line#*=}"
|
||
if [[ "$value" =~ ^\"(.*)\"$ || "$value" =~ ^\'(.*)\'$ ]]; then
|
||
value="${BASH_REMATCH[1]}"
|
||
else
|
||
value="${value%% #*}"
|
||
fi
|
||
printf '%s' "$value"
|
||
}
|
||
|
||
# MySQL-Zugangsdaten als Datei (chmod 600), damit das Passwort nicht in der Prozessliste steht
|
||
credentials() {
|
||
local env="$1/.env" file
|
||
file="$(mktemp)"
|
||
chmod 600 "$file"
|
||
{
|
||
echo "[client]"
|
||
echo "host=$(env_value "$env" DB_HOST)"
|
||
echo "port=$(env_value "$env" DB_PORT)"
|
||
echo "user=$(env_value "$env" DB_USERNAME)"
|
||
echo "password=\"$(env_value "$env" DB_PASSWORD)\""
|
||
} > "$file"
|
||
printf '%s' "$file"
|
||
}
|
||
|
||
live_db="$(env_value "$live/.env" DB_DATABASE)"
|
||
preview_db="$(env_value "$preview/.env" DB_DATABASE)"
|
||
live_host="$(env_value "$live/.env" DB_HOST)"
|
||
preview_host="$(env_value "$preview/.env" DB_HOST)"
|
||
|
||
if [[ -z "$live_db" || -z "$preview_db" ]]; then
|
||
echo "DB_DATABASE fehlt in einer der beiden .env-Dateien." >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [[ "$live_db" == "$preview_db" && "$live_host" == "$preview_host" ]]; then
|
||
echo "STOPP: Vorschau und Live zeigen auf dieselbe Datenbank ($live_db). Nichts wurde verändert." >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [[ "$(env_value "$preview/.env" APP_ENV)" == "production" ]]; then
|
||
echo "STOPP: Die Vorschau läuft mit APP_ENV=production – ist das wirklich die Vorschau? Nichts wurde verändert." >&2
|
||
exit 1
|
||
fi
|
||
|
||
mailer="$(env_value "$preview/.env" MAIL_MAILER)"
|
||
if [[ "$mailer" != "log" && "$mailer" != "array" ]]; then
|
||
echo "Hinweis: MAIL_MAILER der Vorschau ist „$mailer“. Mit den Live-Daten könnte die Vorschau echte Mails verschicken."
|
||
echo "Empfohlen: MAIL_MAILER=log in $preview/.env."
|
||
fi
|
||
|
||
echo "Live: $live (Datenbank $live_db)"
|
||
echo "Vorschau: $preview (Datenbank $preview_db – wird vollständig ersetzt)"
|
||
echo "Medien: $live/storage/app/public/ → $preview/storage/app/public/ (Vorschau-eigene Dateien werden entfernt)"
|
||
read -r -p "Weiter? [ja/nein] " answer
|
||
if [[ "$answer" != "ja" ]]; then
|
||
echo "Abgebrochen, nichts verändert."
|
||
exit 1
|
||
fi
|
||
|
||
live_credentials="$(credentials "$live")"
|
||
preview_credentials="$(credentials "$preview")"
|
||
trap 'rm -f "$live_credentials" "$preview_credentials"' EXIT
|
||
|
||
dump="$HOME/b2in-live-$(date +%Y-%m-%d-%H%M).sql.gz"
|
||
|
||
step "Live-Datenbank sichern nach $dump"
|
||
mysqldump --defaults-extra-file="$live_credentials" --single-transaction --quick --no-tablespaces \
|
||
--routines --triggers --default-character-set=utf8mb4 "$live_db" | gzip > "$dump"
|
||
ls -lh "$dump"
|
||
|
||
step "Vorschau in den Wartungsmodus"
|
||
(cd "$preview" && php artisan down --retry=60)
|
||
|
||
trap 'echo; echo "!! Abgebrochen. Die Vorschau bleibt im Wartungsmodus; Live ist unverändert." >&2; rm -f "$live_credentials" "$preview_credentials"' ERR
|
||
|
||
step "Vorschau-Datenbank leeren und Live-Stand einspielen"
|
||
(cd "$preview" && php artisan db:wipe --force)
|
||
gunzip -c "$dump" | mysql --defaults-extra-file="$preview_credentials" --default-character-set=utf8mb4 "$preview_db"
|
||
|
||
# Sitzungen und Cache von Live gehören nicht in die Vorschau
|
||
for table in sessions cache cache_locks jobs; do
|
||
if mysql --defaults-extra-file="$preview_credentials" -N -e "SHOW TABLES LIKE '$table'" "$preview_db" | grep -q .; then
|
||
mysql --defaults-extra-file="$preview_credentials" -e "TRUNCATE TABLE \`$table\`" "$preview_db"
|
||
fi
|
||
done
|
||
|
||
step "Medien übernehmen"
|
||
mkdir -p "$preview/storage/app/public"
|
||
if [[ "$(realpath "$live/storage/app/public")" == "$(realpath "$preview/storage/app/public")" ]]; then
|
||
# Vorschau verlinkt auf die Live-Medien (preview-deployment.md, Schritt 3) – nichts zu kopieren
|
||
echo "Die Vorschau nutzt die Live-Medien per Link – übersprungen."
|
||
else
|
||
rsync -a --delete "$live/storage/app/public/" "$preview/storage/app/public/"
|
||
du -sh "$preview/storage/app/public"
|
||
fi
|
||
|
||
step "Migrationen und Seeder"
|
||
cd "$preview"
|
||
php artisan migrate --force
|
||
for seeder in "${seeders[@]}"; do
|
||
php artisan db:seed --class="$seeder" --force
|
||
done
|
||
|
||
step "Caches"
|
||
[[ -e public/storage ]] || php artisan storage:link
|
||
php artisan optimize:clear
|
||
php artisan config:cache
|
||
php artisan route:cache
|
||
php artisan view:cache
|
||
|
||
step "Vorschau wieder online"
|
||
php artisan up
|
||
|
||
trap - ERR
|
||
echo
|
||
echo "Vorschau aufgefrischt: Daten und Medien von Live, Stand $(date '+%d.%m.%Y %H:%M')."
|