#!/usr/bin/env bash set -euo pipefail # -------- Konfiguration ------------------------------------------------------- APP_USER="${APP_USER:-www-data}" APP_GROUP="${APP_GROUP:-www-data}" APP_DIR="${APP_DIR:-/var/www/mailwolt}" BRANCH="${BRANCH:-main}" MODE="${UPDATE_MODE:-tags}" ALLOW_DIRTY="${ALLOW_DIRTY:-0}" # -------- --check-only: nur Remote-Tags holen, Version schreiben, sicher beenden --- # (wird von CheckUpdates via sudo aufgerufen — kein Update, nur Versionsprüfung) if [[ "${1:-}" == "--check-only" ]]; then LATEST="$(sudo -u "$APP_USER" bash -c \ "git -C '${APP_DIR}' ls-remote --tags --sort=-v:refname origin 'v*' 2>/dev/null \ | grep -v '\^{}' | head -1 | sed 's|.*refs/tags/||'" 2>/dev/null || true)" if [[ -n "$LATEST" ]]; then mkdir -p /var/lib/mailwolt printf '%s\n' "$LATEST" > /var/lib/mailwolt/version_remote fi exit 0 fi STATE_DIR="/var/lib/mailwolt/update" LOCK_FILE="/var/run/mailwolt-update.lock" LOG_FILE="/var/log/mailwolt-update.log" # npm / CI Defaults export CI=1 export NPM_CONFIG_FUND=false export NPM_CONFIG_AUDIT=false export npm_config_loglevel=warn # -------- Helper -------------------------------------------------------------- as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; } restart_if_exists(){ local u="$1" systemctl list-unit-files | grep -q "^${u}\.service" && systemctl restart "$u" || true } reload_if_active(){ local u="$1" systemctl is-active --quiet "$u" && systemctl reload "$u" || true } restart_php_fpm(){ for u in php8.3-fpm php8.2-fpm php8.1-fpm php-fpm; do if systemctl list-unit-files | grep -q "^${u}\.service"; then # reload = graceful: laufende Requests fertig, dann Workers tauschen systemctl reload "$u" 2>/dev/null || systemctl restart "$u" return 0 fi done } artisan_down(){ as_app "cd ${APP_DIR} && php artisan down --render=errors.503 --retry=10" 2>/dev/null || true echo "[i] Wartungsmodus aktiviert" } artisan_up(){ as_app "cd ${APP_DIR} && php artisan up" 2>/dev/null || true echo "[i] Wartungsmodus beendet" } git_safe(){ # safe.directory system-weit (root → /etc/gitconfig → gilt für alle User) git config --system --add safe.directory "${APP_DIR}" >/dev/null 2>&1 || true # Gesamte App dem APP_USER zuweisen — root kann immer schreiben, # aber PHP-FPM (www-data) braucht Lese-/Schreibrecht auf bootstrap/cache + storage chown -R "${APP_USER}":"${APP_GROUP}" "${APP_DIR}" 2>/dev/null || true } git_dirty_check(){ # Als root ausführen: kein Permission-Problem unabhängig vom Dateieigentümer # Nur tracked Änderungen prüfen – untracked Dateien (??) ignorieren local dirty dirty="$(git -C "${APP_DIR}" status --porcelain 2>/dev/null | grep -v '^??' || true)" if [[ -n "$dirty" ]]; then echo "[!] Uncommitted Änderungen — tracked Dateien werden zurückgesetzt:" echo "$dirty" git -C "${APP_DIR}" reset --hard HEAD 2>/dev/null || true git -C "${APP_DIR}" clean -fd \ --exclude='.env' --exclude='storage/' --exclude='node_modules/' 2>/dev/null || true fi } get_version(){ git -C "${APP_DIR}" describe --tags --always 2>/dev/null \ || git -C "${APP_DIR}" rev-parse --short=7 HEAD 2>/dev/null \ || echo "unknown" } write_version_files(){ local ver="$1" rev="$2" install -d -m 0755 /etc/mailwolt /var/lib/mailwolt || true printf "version=%s\nrev=%s\nupdated=%s\n" "$ver" "$rev" "$(date -Is)" > /etc/mailwolt/build.info || true printf '%s\n' "${ver#v}" > /var/lib/mailwolt/version || true printf '%s\n' "$ver" > /var/lib/mailwolt/version_raw || true } write_build_info(){ write_version_files "$@"; } # --- Frontend build (quiet & robust) ------------------------------------------ frontend_build_quiet() { local LOG="/var/log/mailwolt-frontend-build.log" local NPM_ENV="CI=1 NPM_CONFIG_FUND=false NPM_CONFIG_AUDIT=false npm_config_loglevel=warn" # Logfile vorbereiten install -d -m 0755 "$(dirname "$LOG")" : > "$LOG" || true chmod 0644 "$LOG" echo "[i] Frontend: vorbereiten …" as_app "mkdir -p '${APP_DIR}/public/build' '${APP_DIR}/node_modules' '${APP_DIR}/.vite' '${APP_DIR}/.npm-cache'" chown -R "$APP_USER":"$APP_GROUP" "${APP_DIR}/node_modules" "${APP_DIR}/.vite" "${APP_DIR}/.npm-cache" 2>/dev/null || true # npm-Cache explizit im App-Verzeichnis setzen — verhindert Schreibfehler ins HOME local NPM_CACHE_FLAG="--cache '${APP_DIR}/.npm-cache'" # Altes Build-Verzeichnis sichern — wird erst nach erfolgreichem Build ersetzt local BUILD_DIR="${APP_DIR}/public/build" local BUILD_BAK="${APP_DIR}/public/build.bak" rm -rf "${BUILD_BAK}" 2>/dev/null || true [[ -d "${BUILD_DIR}" ]] && cp -a "${BUILD_DIR}" "${BUILD_BAK}" 2>/dev/null || true rm -rf "${APP_DIR}/node_modules/.vite" "${BUILD_DIR}/"* 2>/dev/null || true chown -R "$APP_USER":"$APP_GROUP" "${BUILD_DIR}" 2>/dev/null || true as_app "printf 'fund=false\naudit=false\ncache=${APP_DIR}/.npm-cache\n' > ~/.npmrc" >>"$LOG" 2>&1 || true echo "[i] Frontend: Dependencies … (Details: $LOG)" if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm ci --no-audit --no-fund --no-progress ${NPM_CACHE_FLAG}" >>"$LOG" 2>&1; then if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm install --no-audit --no-fund --no-progress ${NPM_CACHE_FLAG}" >>"$LOG" 2>&1; then echo "[!] npm install fehlgeschlagen — altes Build wird wiederhergestellt." [[ -d "${BUILD_BAK}" ]] && cp -a "${BUILD_BAK}/." "${BUILD_DIR}/" 2>/dev/null || true tail -n 60 "$LOG" || true return 1 fi fi echo "[i] Frontend: Build … (Details: $LOG)" if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm run build --silent --loglevel=warn ${NPM_CACHE_FLAG}" >>"$LOG" 2>&1; then echo "[!] Build fehlgeschlagen — altes Build wird wiederhergestellt." [[ -d "${BUILD_BAK}" ]] && cp -a "${BUILD_BAK}/." "${BUILD_DIR}/" 2>/dev/null || true tail -n 80 "$LOG" || true return 1 fi rm -rf "${BUILD_BAK}" 2>/dev/null || true chown -R "$APP_USER":"$APP_GROUP" "${BUILD_DIR}" || true find "${BUILD_DIR}" -type d -exec chmod 2775 {} \; 2>/dev/null || true find "${BUILD_DIR}" -type f -exec chmod 0664 {} \; 2>/dev/null || true echo "[✓] Frontend gebaut." } # -------- Systemzustand sicherstellen (läuft bei JEDER Ausführung) ----------- # Neue Scripts/Sudoers-Regeln werden so auch ohne echtes Update eingespielt. _sbin_install(){ local src="$1" dst="$2" [[ -f "$src" ]] && install -m 755 "$src" "$dst" && return 0 || return 0 } _sudoers_add(){ local file="$1" rule="$2" [[ -f "$file" ]] || return 0 grep -qF "$rule" "$file" && return 0 chmod 640 "$file" printf '%s\n' "$rule" >> "$file" chmod 440 "$file" } fix_ownership(){ # Alle App-Dateien dem APP_USER zuweisen (git läuft als root → neue Dateien wären sonst root-owned) # node_modules + vendor ausklammern (zu groß, werden separat behandelt) for d in app bootstrap config database public resources routes scripts storage; do [[ -e "${APP_DIR}/${d}" ]] && chown -R "${APP_USER}:${APP_GROUP}" "${APP_DIR}/${d}" 2>/dev/null || true done for f in artisan composer.json composer.lock package.json package-lock.json .env vite.config.js vite.config.ts; do [[ -e "${APP_DIR}/${f}" ]] && chown "${APP_USER}:${APP_GROUP}" "${APP_DIR}/${f}" 2>/dev/null || true done chmod -R ug+rwX "${APP_DIR}/bootstrap/cache" "${APP_DIR}/storage" 2>/dev/null || true find "${APP_DIR}/bootstrap/cache" "${APP_DIR}/storage" \ -type d -exec chmod g+s {} \; 2>/dev/null || true } fix_permissions(){ fix_ownership # Frontend fehlt → rebuild if [[ ! -f "${APP_DIR}/public/build/manifest.json" ]]; then echo "[!] manifest.json fehlt — Frontend wird neu gebaut …" frontend_build_quiet || true fi # Erst leeren, dann neu aufbauen — Permissions sind oben bereits korrekt gesetzt sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize:clear --quiet 2>/dev/null || true sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize --quiet 2>/dev/null || true } ensure_system(){ # Cache + Rechte immer korrigieren — auch wenn das sbin veraltet ist fix_permissions # Scripts nach sbin _sbin_install "${APP_DIR}/scripts/mailwolt-apply-domains" /usr/local/sbin/mailwolt-apply-domains _sbin_install "${APP_DIR}/scripts/mailwolt-fetch-tags" /usr/local/sbin/mailwolt-fetch-tags _sbin_install "${APP_DIR}/scripts/update.sh" /usr/local/sbin/mailwolt-update _sbin_install "${APP_DIR}/scripts/mailwolt-clamav" /usr/local/sbin/mailwolt-clamav _sbin_install "${APP_DIR}/scripts/mailwolt-backup" /usr/local/sbin/mailwolt-backup # Sudoers-Regeln nachrüsten for f in /etc/sudoers.d/mailwolt-certbot /etc/sudoers.d/mailwolt-dkim; do _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-fetch-tags' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update --check-only' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-clamav' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-backup' _sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/*/fullchain.pem' done # Laravel Scheduler cron (schedule:run muss jede Minute laufen) local cron_file="/etc/cron.d/mailwolt" local cron_line="* * * * * www-data php ${APP_DIR}/artisan schedule:run >> /dev/null 2>&1" if ! grep -qF "artisan schedule:run" "${cron_file}" 2>/dev/null; then printf '%s\n' "${cron_line}" > "${cron_file}" chmod 644 "${cron_file}" echo "[✓] Laravel Scheduler cron installiert." fi # Reverb WebSocket Service local ws_service="/etc/systemd/system/mailwolt-ws.service" if [[ ! -f "${ws_service}" ]]; then local php_bin php_bin=$(command -v php || echo "/usr/bin/php") cat > "${ws_service}" </dev/null || true)" if [[ -n "$cur_tag" ]]; then local cur_rev cur_rev="$(git -C "${APP_DIR}" rev-parse --short HEAD 2>/dev/null || true)" write_version_files "$cur_tag" "$cur_rev" fi # ClamAV installieren falls nicht vorhanden (läuft deaktiviert, User schaltet ein/aus) if ! dpkg -l clamav-daemon 2>/dev/null | grep -q '^ii'; then echo "[…] ClamAV wird installiert…" DEBIAN_FRONTEND=noninteractive apt-get install -y clamav clamav-daemon clamav-freshclam 2>&1 | sed 's/^/ /' systemctl enable clamav-freshclam 2>/dev/null || true systemctl start clamav-freshclam 2>/dev/null || true systemctl disable clamav-daemon 2>/dev/null || true systemctl stop clamav-daemon 2>/dev/null || true echo "[✓] ClamAV installiert (deaktiviert – User kann es einschalten)." fi } # -------- Guards -------------------------------------------------------------- [[ "$(id -u)" -eq 0 ]] || { echo "[!] Bitte als root ausführen"; exit 1; } [[ -d "$APP_DIR/.git" ]] || { echo "[!] $APP_DIR scheint kein Git-Repo zu sein"; exit 1; } # -------- Lock ---------------------------------------------------------------- mkdir -p "$STATE_DIR" if [[ -f "$LOCK_FILE" ]]; then LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null || true) if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then echo "[!] Update läuft bereits (PID $LOCK_PID) — Abbruch." exit 1 fi rm -f "$LOCK_FILE" fi echo $$ > "$LOCK_FILE" # -------- State & Cleanup-Trap ----------------------------------------------- MAINTENANCE_ACTIVE=0 _cleanup() { local rc=$? [[ "$MAINTENANCE_ACTIVE" -eq 1 ]] && artisan_up # Ownership immer korrigieren; Cache nur bei Fehler neu aufbauen # (bei Erfolg hat fix_permissions im Hauptfluss bereits optimize gebaut) if [[ $rc -ne 0 ]]; then fix_permissions else fix_ownership fi mkdir -p "$STATE_DIR" echo "done" > "$STATE_DIR/state" echo "$rc" > "$STATE_DIR/rc" rm -f "$LOCK_FILE" } trap '_cleanup' EXIT INT TERM # Update als laufend markieren echo "running" > "$STATE_DIR/state" rm -f "$STATE_DIR/rc" : > "$LOG_FILE" # Alle echo-Ausgaben in Log-Datei schreiben (und stdout — für CLI-Nutzung) exec > >(tee -a "$LOG_FILE") 2>&1 git_safe git_dirty_check # Sbin + Sudoers immer aktuell halten — unabhängig vom Update-Pfad ensure_system # -------- Git: neuen Stand holen --------------------------------------------- echo "[i] Prüfe Repository …" OLD_REV="$(git -C "${APP_DIR}" rev-parse HEAD)" OLD_VER="$(get_version)" NEW_REV="$OLD_REV" if [[ "$MODE" = "tags" ]]; then # Tags vom Remote holen (als root — hat Zugriff auf gespeicherte Credentials) git -C "${APP_DIR}" fetch --quiet origin 2>/dev/null || true git -C "${APP_DIR}" fetch --tags --quiet origin 2>/dev/null || true LATEST_TAG="$(git -C "${APP_DIR}" describe --tags --abbrev=0 \ "$(git -C "${APP_DIR}" rev-list --tags --max-count=1 2>/dev/null)" 2>/dev/null || echo '')" # Neuesten Remote-Tag cachen [[ -n "$LATEST_TAG" ]] && { mkdir -p /var/lib/mailwolt; printf '%s\n' "$LATEST_TAG" > /var/lib/mailwolt/version_remote; } || true if [[ -z "$LATEST_TAG" ]]; then echo "[!] Keine Tags gefunden – falle auf origin/${BRANCH} zurück" git -C "${APP_DIR}" checkout -q "${BRANCH}" git -C "${APP_DIR}" pull --ff-only origin "${BRANCH}" else TARGET_REV="$(git -C "${APP_DIR}" rev-list -n1 "${LATEST_TAG}")" if [[ "$TARGET_REV" = "$OLD_REV" ]]; then echo "[✓] Bereits auf neuestem Release (${LATEST_TAG}) – nichts zu tun." write_build_info "$(get_version)" "$OLD_REV" ensure_system exit 0 fi echo "[i] Checkout auf Release ${LATEST_TAG} (${TARGET_REV:0:7}) …" git -C "${APP_DIR}" checkout -q "${LATEST_TAG}" fi NEW_REV="$(git -C "${APP_DIR}" rev-parse HEAD)" # Nach Checkout Eigentümer korrigieren chown -R "${APP_USER}":"${APP_GROUP}" "${APP_DIR}" 2>/dev/null || true else # Rolling: branch folgen git -C "${APP_DIR}" fetch --quiet origin "${BRANCH}" 2>/dev/null || true BEHIND="$(git -C "${APP_DIR}" rev-list --count "HEAD..origin/${BRANCH}" 2>/dev/null || echo 0)" if [[ "$BEHIND" -eq 0 ]]; then echo "[✓] Branch origin/${BRANCH} ist bereits aktuell – nichts zu tun." write_build_info "$(get_version)" "$OLD_REV" ensure_system exit 0 fi echo "[i] Es gibt ${BEHIND} neue Commit(s) – ziehe Änderungen …" git -C "${APP_DIR}" checkout -q "${BRANCH}" git -C "${APP_DIR}" pull --ff-only origin "${BRANCH}" NEW_REV="$(git -C "${APP_DIR}" rev-parse HEAD)" chown -R "${APP_USER}":"${APP_GROUP}" "${APP_DIR}" 2>/dev/null || true fi # -------- Änderungstypen ermitteln ------------------------------------------- CHANGED_FILES="$(as_app "git -C ${APP_DIR} diff --name-only ${OLD_REV}..${NEW_REV}")" NEED_COMPOSER=0 NEED_MIGRATIONS=0 NEED_FRONTEND=0 NEED_PHP_RESTART=0 NEED_SBIN_UPDATE=0 echo "$CHANGED_FILES" | grep -qE '(^|/)composer\.(json|lock)$' && NEED_COMPOSER=1 echo "$CHANGED_FILES" | grep -qE '^database/migrations/' && NEED_MIGRATIONS=1 echo "$CHANGED_FILES" | grep -qE '^(package(-lock)?\.json|vite\.config(\.ts|\.js)?|resources/|public/.*\.(js|css))' && NEED_FRONTEND=1 echo "$CHANGED_FILES" | grep -qE '^(app/|routes/|config/|resources/views/)' && NEED_PHP_RESTART=1 echo "$CHANGED_FILES" | grep -qE '^scripts/' && NEED_SBIN_UPDATE=1 echo "[i] Zusammenfassung:" echo " Composer : $([[ $NEED_COMPOSER -eq 1 ]] && echo JA || echo nein)" echo " Migrations : $([[ $NEED_MIGRATIONS -eq 1 ]] && echo JA || echo nein)" echo " Frontend : $([[ $NEED_FRONTEND -eq 1 ]] && echo JA || echo nein)" echo " PHP restart : $([[ $NEED_PHP_RESTART -eq 1 ]] && echo JA || echo nein)" echo " Sbin-Scripts : $([[ $NEED_SBIN_UPDATE -eq 1 ]] && echo JA || echo nein)" # System-Scripts aus scripts/ sofort nach dem Pull einspielen (kein Downtime nötig) # ensure_system() am Ende des Scripts übernimmt das vollständig. # .env immer migrieren (unabhängig vom Update-Pfad) as_app "cd ${APP_DIR} && php artisan mailwolt:migrate-env-reverb" 2>/dev/null || migrate_env_reverb # Wenn gar nichts relevantes geändert wurde → sauber beenden if [[ $NEED_COMPOSER -eq 0 && $NEED_MIGRATIONS -eq 0 && $NEED_FRONTEND -eq 0 && $NEED_PHP_RESTART -eq 0 ]]; then echo "[✓] Code-Stand aktualisiert, aber keine Build/Runtime-Änderungen – keine Neustarts nötig." NEW_VER="$(get_version)" write_build_info "$NEW_VER" "$NEW_REV" echo "[i] Version: ${OLD_VER} → ${NEW_VER}" fix_permissions ensure_system exit 0 fi # -------- Composer (ohne Downtime) ------------------------------------------- if [[ $NEED_COMPOSER -eq 1 ]]; then echo "[i] Composer …" as_app "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev" fi # -------- Frontend (ohne Downtime — Assets sind statisch) -------------------- if [[ $NEED_FRONTEND -eq 1 ]]; then echo "[i] Frontend-Änderungen erkannt – baue Assets …" if ! frontend_build_quiet; then echo "[!] Frontend-Build ist fehlgeschlagen (siehe /var/log/mailwolt-frontend-build.log)." exit 1 fi fi # -------- Wartungsmodus immer aktivieren (Cache-Rebuild darf keine Requests sehen) --- artisan_down MAINTENANCE_ACTIVE=1 if [[ $NEED_MIGRATIONS -eq 1 ]]; then echo "[i] DB-Migrationen …" as_app "cd ${APP_DIR} && php artisan migrate --force" fi if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then as_app "cd ${APP_DIR} && php artisan queue:restart || true" fi # -------- Version + Build-Info ablegen ---------------------------------------- NEW_VER="$(get_version)" write_version_files "$NEW_VER" "$NEW_REV" # -------- Rechte + Cache aufbauen (VOR PHP-FPM Restart) ---------------------- fix_permissions # -------- Dienste neu laden (gezielt) ---------------------------------------- echo "[i] Dienste neu laden …" if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then restart_php_fpm restart_if_exists "${APP_USER}-queue" restart_if_exists "${APP_USER}-schedule" restart_if_exists "${APP_USER}-ws" fi if [[ $NEED_FRONTEND -eq 1 || $NEED_PHP_RESTART -eq 1 ]]; then reload_if_active nginx fi # -------- Wartungsmodus beenden ---------------------------------------------- artisan_up MAINTENANCE_ACTIVE=0 echo "[✓] Update abgeschlossen: ${OLD_VER} → ${NEW_VER} (${OLD_REV:0:7} → ${NEW_REV:0:7})" # -------- .env: REVERB-Werte auf Domain-Basis normalisieren ------------------- migrate_env_reverb() { local env_file="${APP_DIR}/.env" [ -f "${env_file}" ] || return 0 # APP_HOST auslesen — Fallback auf APP_URL wenn nicht gesetzt local app_host app_host=$(grep '^APP_HOST=' "${env_file}" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'") if [ -z "${app_host}" ]; then local app_url app_url=$(grep '^APP_URL=' "${env_file}" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'") # https://domain.at → domain.at oder http://1.2.3.4 → 1.2.3.4 app_host=$(echo "${app_url}" | sed 's|https\?://||' | cut -d/ -f1 | cut -d: -f1) fi [ -z "${app_host}" ] && return 0 # IP-Adresse → nichts tun (kein sinnvoller Hostname für TLS) echo "${app_host}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' && return 0 # Nur migrieren wenn VITE_REVERB_HOST nicht auf app_host zeigt local current_vite_host current_vite_host=$(grep '^VITE_REVERB_HOST=' "${env_file}" | head -1 | cut -d= -f2-) if [ "${current_vite_host}" = "\${REVERB_HOST}" ] || [ "${current_vite_host}" = "${app_host}" ]; then return 0 # bereits korrekt fi echo "[i] .env: REVERB-Werte werden auf Domain '${app_host}' aktualisiert …" local scheme="http" port="80" [ -f "/etc/letsencrypt/live/${app_host}/fullchain.pem" ] && scheme="https" && port="443" _uenv() { local k="$1" v="$2" if grep -q "^${k}=" "${env_file}"; then sed -i "s|^${k}=.*|${k}=${v}|" "${env_file}" else echo "${k}=${v}" >> "${env_file}" fi } _uenv "REVERB_HOST" "\${APP_HOST}" _uenv "REVERB_PORT" "${port}" _uenv "REVERB_SCHEME" "${scheme}" _uenv "REVERB_PATH" "/ws" _uenv "REVERB_SERVER_HOST" "127.0.0.1" _uenv "REVERB_SERVER_PORT" "8080" _uenv "REVERB_SERVER_SCHEME" "http" _uenv "REVERB_SERVER_PATH" "" _uenv "VITE_REVERB_HOST" "\${REVERB_HOST}" _uenv "VITE_REVERB_PORT" "\${REVERB_PORT}" _uenv "VITE_REVERB_SCHEME" "\${REVERB_SCHEME}" _uenv "VITE_REVERB_PATH" "\${REVERB_PATH}" # Assets neu bauen mit den korrekten Werten frontend_build_quiet || true as_app "cd ${APP_DIR} && php artisan optimize:clear || true" echo "[✓] REVERB .env migriert + Assets neu gebaut." } migrate_env_reverb ensure_system