mailwolt/scripts/update.sh

597 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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
# Als root ausführen www-data hat keine Git-Credentials für private Repos
LATEST="$(git -C "${APP_DIR}" ls-remote --tags --sort=-v:refname origin 'v*' 2>/dev/null \
| grep -v '\^{}' | head -1 | sed 's|.*refs/tags/||' || 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
_sbin_install "${APP_DIR}/scripts/mailwolt-sandbox-sync" /usr/local/sbin/mailwolt-sandbox-sync
# Sudoers-Regeln: dediziertes File unabhängig von certbot/dkim
local MW_SUDO="/etc/sudoers.d/mailwolt"
if [[ ! -f "$MW_SUDO" ]]; then touch "$MW_SUDO"; chmod 440 "$MW_SUDO"; fi
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-fetch-tags'
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update --check-only'
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-clamav'
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-backup'
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-sandbox-sync *'
_sudoers_add "$MW_SUDO" 'www-data ALL=(root) NOPASSWD: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/*/fullchain.pem'
# Legacy-Files ebenfalls nachrüsten (falls vorhanden)
for f in /etc/sudoers.d/mailwolt-certbot /etc/sudoers.d/mailwolt-dkim; do
[[ -f "$f" ]] || continue
_sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-backup'
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}" <<UNIT
[Unit]
Description=Laravel Reverb WebSocket (MailWolt)
After=network.target
[Service]
WorkingDirectory=${APP_DIR}
ExecStart=${php_bin} artisan reverb:start --host=127.0.0.1 --port=8080 --no-interaction
Restart=always
RestartSec=5
User=www-data
Group=www-data
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable mailwolt-ws
systemctl start mailwolt-ws
echo "[✓] mailwolt-ws Service installiert und gestartet."
elif ! systemctl is-active --quiet mailwolt-ws; then
systemctl start mailwolt-ws
echo "[✓] mailwolt-ws Service gestartet."
fi
# Versionsdateien immer aktualisieren verhindert veraltete /var/lib/mailwolt/version
local cur_tag
cur_tag="$(git -C "${APP_DIR}" describe --tags --abbrev=0 2>/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
# Fail2Ban: Jails + Sudoers sicherstellen (idempotent)
local F2B_SUDO="/etc/sudoers.d/clubird-fail2ban"
local F2B_JAIL="/etc/fail2ban/jail.d/clubird-jails.local"
if command -v fail2ban-client >/dev/null 2>&1; then
# Sudoers für www-data
if [[ ! -f "$F2B_SUDO" ]]; then
cat > "$F2B_SUDO" << 'SUDOEOF'
www-data ALL=(root) NOPASSWD: /usr/bin/tee /etc/fail2ban/jail.d/00-defaults.local
www-data ALL=(root) NOPASSWD: /usr/bin/tee /etc/fail2ban/jail.d/whitelist.local
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client reload
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status *
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client set * banip *
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client set * unbanip *
SUDOEOF
chmod 440 "$F2B_SUDO"
echo "[✓] Fail2Ban sudoers installiert."
fi
# Jail-Konfiguration (dovecot, postfix, nginx)
if [[ ! -f "$F2B_JAIL" ]]; then
cat > "$F2B_JAIL" << 'JAILEOF'
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,submissions,sieve
filter = dovecot
logpath = /var/log/mail.log
maxretry = 5
[postfix]
enabled = true
port = smtp,465,submission,submissions
filter = postfix[mode=more]
logpath = /var/log/mail.log
maxretry = 5
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
/var/log/nginx/mailwolt_error.log
/var/log/nginx/mailwolt_ssl_error.log
maxretry = 5
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
/var/log/nginx/mailwolt_access.log
/var/log/nginx/mailwolt_ssl_access.log
maxretry = 2
JAILEOF
systemctl is-active --quiet fail2ban && fail2ban-client reload >/dev/null 2>&1 || true
echo "[✓] Fail2Ban Jails installiert (dovecot, postfix, nginx)."
fi
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
fix_ownership
# Bei Fehler zusätzlich Cache neu aufbauen
if [[ $rc -ne 0 ]]; then
sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize:clear 2>&1 || true
sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize 2>&1 || true
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 korrigieren (VOR PHP-FPM Restart — kein optimize hier) ------
fix_ownership
# -------- 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
# -------- Cache neu aufbauen (NACH PHP-FPM Restart — OPcache ist frisch) -----
echo "[i] Cache wird neu aufgebaut …"
sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize:clear 2>&1 || true
sudo -u "${APP_USER}" php "${APP_DIR}/artisan" optimize 2>&1 || true
echo "[✓] Cache aufgebaut."
# -------- 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