342 lines
14 KiB
Bash
342 lines
14 KiB
Bash
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
source ./lib.sh
|
||
|
||
# --- Helper: sicherer Frontend-Build als APP_USER ---------------------------
|
||
safe_frontend_build() {
|
||
echo "[i] Frontend build …"
|
||
|
||
# Verzeichnisse & Rechte vorbereiten (Gruppen-sticky & ACL)
|
||
install -d -m 2775 -o "$APP_USER" -g "$APP_GROUP" \
|
||
"${APP_DIR}/public/build" "${APP_DIR}/node_modules" "${APP_DIR}/.npm-cache"
|
||
|
||
chown -R "$APP_USER":"$APP_GROUP" "${APP_DIR}"
|
||
find "${APP_DIR}" -type d -exec chmod 2775 {} \;
|
||
find "${APP_DIR}" -type f -exec chmod 664 {} \;
|
||
setfacl -R -m g:"$APP_GROUP":rwX -m d:g:"$APP_GROUP":rwX "${APP_DIR}" || true
|
||
|
||
# Vite-/Build-Reste bereinigen (falls mal root dort gebaut hat)
|
||
rm -rf "${APP_DIR}/node_modules/.vite" "${APP_DIR}/public/build/"* 2>/dev/null || true
|
||
|
||
# npm auf projektlokales Cache konfigurieren
|
||
sudo -u "$APP_USER" -H bash -lc "cat > ~/.npmrc <<'RC'
|
||
fund=false
|
||
audit=false
|
||
prefer-offline=true
|
||
cache=${APP_DIR}/.npm-cache
|
||
RC"
|
||
|
||
# Node ggf. installieren
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||
apt-get install -y nodejs
|
||
fi
|
||
|
||
# Dependencies + Build (als App-User)
|
||
if sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && (npm ci --no-audit --no-fund || npm install --no-audit --no-fund) && npm run build"; then
|
||
return 0
|
||
fi
|
||
|
||
echo "[!] Build fehlgeschlagen – Rechtefix + Clean + Retry …"
|
||
rm -rf "${APP_DIR}/node_modules/.vite" "${APP_DIR}/public/build/"* 2>/dev/null || true
|
||
chown -R "$APP_USER":"$APP_GROUP" "${APP_DIR}"
|
||
find "${APP_DIR}" -type d -exec chmod 2775 {} \;
|
||
find "${APP_DIR}" -type f -exec chmod 664 {} \;
|
||
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm run build"
|
||
}
|
||
|
||
relink_and_reload() {
|
||
if [[ -d /etc/letsencrypt/renewal-hooks/deploy ]]; then
|
||
run-parts /etc/letsencrypt/renewal-hooks/deploy || true
|
||
fi
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx || true
|
||
fi
|
||
}
|
||
|
||
log "App bereitstellen …"
|
||
mkdir -p "$(dirname "$APP_DIR")"
|
||
chown -R "$APP_USER":"$APP_GROUP" "$(dirname "$APP_DIR")"
|
||
|
||
# Repo holen oder Laravel anlegen – passe GIT_REPO/GIT_BRANCH bei Bedarf an
|
||
GIT_REPO="${GIT_REPO:-https://git.nexlab.at/boban/mailwolt.git}"
|
||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||
|
||
if [[ "${GIT_REPO}" == "https://example.com/your-repo-placeholder.git" ]]; then
|
||
[[ -d "$APP_DIR" && -n "$(ls -A "$APP_DIR" 2>/dev/null || true)" ]] || \
|
||
sudo -u "$APP_USER" -H bash -lc "cd /var/www && composer create-project laravel/laravel ${APP_USER} --no-interaction"
|
||
else
|
||
if [[ ! -d "${APP_DIR}/.git" ]]; then
|
||
sudo -u "$APP_USER" -H bash -lc "git clone --depth=1 -b ${GIT_BRANCH} ${GIT_REPO} ${APP_DIR}"
|
||
else
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && git fetch --depth=1 origin ${GIT_BRANCH} && git reset --hard origin/${GIT_BRANCH}"
|
||
fi
|
||
[[ -f "${APP_DIR}/composer.json" ]] && sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist"
|
||
fi
|
||
|
||
ENV_FILE="${APP_DIR}/.env"
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && cp -n .env.example .env || true"
|
||
grep -q '^APP_KEY=' "$ENV_FILE" || echo "APP_KEY=" >> "$ENV_FILE"
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan key:generate --force || true"
|
||
|
||
# --- App-URL/Hosts ----------------------------------------------------------
|
||
#SERVER_PUBLIC_IPV4="${SERVER_PUBLIC_IPV4:-}"
|
||
#if [[ -z "$SERVER_PUBLIC_IPV4" ]] && command -v curl >/dev/null 2>&1; then
|
||
# SERVER_PUBLIC_IPV4="$(curl -fsS --max-time 2 https://ifconfig.me 2>/dev/null || true)"
|
||
# [[ "$SERVER_PUBLIC_IPV4" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || SERVER_PUBLIC_IPV4=""
|
||
#fi
|
||
#[[ -n "$SERVER_PUBLIC_IPV4" ]] || SERVER_PUBLIC_IPV4="$(detect_ip)"
|
||
#
|
||
#UI_CERT="/etc/ssl/ui/fullchain.pem"
|
||
#UI_KEY="/etc/ssl/ui/privkey.pem"
|
||
#
|
||
#if [[ -n "${UI_HOST:-}" ]]; then
|
||
# APP_HOST_VAL="$UI_HOST"
|
||
# APP_URL_VAL="https://${UI_HOST}"
|
||
#else
|
||
# APP_HOST_VAL="$SERVER_PUBLIC_IPV4"
|
||
# SCHEME="http"
|
||
# [[ -s "$UI_CERT" && -s "$UI_KEY" ]] && SCHEME="https"
|
||
# APP_URL_VAL="${SCHEME}://${SERVER_PUBLIC_IPV4}"
|
||
#fi
|
||
|
||
SERVER_PUBLIC_IPV4="${SERVER_PUBLIC_IPV4:-}"
|
||
if [[ -z "$SERVER_PUBLIC_IPV4" ]] && command -v curl >/dev/null 2>&1; then
|
||
SERVER_PUBLIC_IPV4="$(curl -fsS --max-time 2 https://ifconfig.me 2>/dev/null || true)"
|
||
[[ "$SERVER_PUBLIC_IPV4" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || SERVER_PUBLIC_IPV4=""
|
||
fi
|
||
[[ -n "$SERVER_PUBLIC_IPV4" ]] || SERVER_PUBLIC_IPV4="$(detect_ip)"
|
||
|
||
UI_CERT="/etc/ssl/ui/fullchain.pem"
|
||
UI_KEY="/etc/ssl/ui/privkey.pem"
|
||
|
||
# DEV-Modus: immer IP als Host, http (kein example.com / keine Fake-Domain)
|
||
if [[ "${DEV_MODE:-0}" = "1" || "${APP_ENV:-production}" = "local" ]]; then
|
||
APP_HOST_VAL="$SERVER_PUBLIC_IPV4"
|
||
APP_URL_VAL="http://${APP_HOST_VAL}"
|
||
else
|
||
# PROD/normal: wenn UI_HOST gesetzt → benutzen, sonst IP
|
||
if [[ -n "${UI_HOST:-}" ]]; then
|
||
APP_HOST_VAL="$UI_HOST"
|
||
APP_URL_VAL="https://${UI_HOST}"
|
||
else
|
||
APP_HOST_VAL="$SERVER_PUBLIC_IPV4"
|
||
SCHEME="http"
|
||
[[ -s "$UI_CERT" && -s "$UI_KEY" ]] && SCHEME="https"
|
||
APP_URL_VAL="${SCHEME}://${APP_HOST_VAL}"
|
||
fi
|
||
fi
|
||
|
||
SECURE=$([[ "${APP_ENV}" = "production" ]] && echo true || echo false)
|
||
|
||
# --- .env schreiben ---------------------------------------------------------
|
||
upsert_env APP_URL "${APP_URL_VAL}"
|
||
|
||
if [[ "${PROXY_MODE:-0}" -eq 1 ]]; then
|
||
TP_LIST="127.0.0.1,::1"
|
||
[[ -n "${NPM_IP:-}" ]] && TP_LIST="${TP_LIST},${NPM_IP}"
|
||
upsert_env TRUSTED_PROXIES "$TP_LIST"
|
||
upsert_env TRUSTED_HEADERS "x-forwarded-all"
|
||
else
|
||
upsert_env TRUSTED_PROXIES ""
|
||
upsert_env TRUSTED_HEADERS "x-forwarded-all"
|
||
fi
|
||
|
||
upsert_env APP_HOST "${APP_HOST_VAL}"
|
||
upsert_env APP_NAME "${APP_NAME}"
|
||
upsert_env APP_ENV "${APP_ENV:-production}"
|
||
upsert_env APP_DEBUG "${APP_DEBUG:-false}"
|
||
|
||
upsert_env APP_LOCALE "${APP_LOCALE:-de}"
|
||
upsert_env APP_FALLBACK_LOCALE "en"
|
||
|
||
upsert_env SERVER_PUBLIC_IPV4 "${SERVER_PUBLIC_IPV4}"
|
||
upsert_env SERVER_PUBLIC_IPV6 "${SERVER_PUBLIC_IPV6:-}"
|
||
|
||
upsert_env SYSMAIL_SUB "${SYSMAIL_SUB}"
|
||
upsert_env SYSMAIL_DOMAIN "${SYSMAIL_DOMAIN}"
|
||
upsert_env DKIM_ENABLE "${DKIM_ENABLE}"
|
||
upsert_env DKIM_SELECTOR "${DKIM_SELECTOR}"
|
||
upsert_env DKIM_GENERATE "${DKIM_GENERATE}"
|
||
|
||
upsert_env BASE_DOMAIN "${BASE_DOMAIN}"
|
||
upsert_env UI_SUB "${UI_SUB}"
|
||
upsert_env WEBMAIL_SUB "${WEBMAIL_SUB}"
|
||
upsert_env MTA_SUB "${MTA_SUB}"
|
||
upsert_env LE_EMAIL "${LE_EMAIL:-admin@${BASE_DOMAIN}}"
|
||
|
||
upsert_env DB_CONNECTION "mysql"
|
||
upsert_env DB_HOST "127.0.0.1"
|
||
upsert_env DB_PORT "3306"
|
||
upsert_env DB_DATABASE "${DB_NAME}"
|
||
upsert_env DB_USERNAME "${DB_USER}"
|
||
upsert_env DB_PASSWORD "${DB_PASS}"
|
||
|
||
upsert_env CACHE_SETTINGS_STORE "redis"
|
||
upsert_env CACHE_STORE "redis"
|
||
upsert_env CACHE_DRIVER "redis"
|
||
upsert_env CACHE_PREFIX "${APP_USER_PREFIX}_cache:"
|
||
upsert_env SESSION_DRIVER "redis"
|
||
upsert_env SESSION_SECURE_COOKIE "${SECURE}" # DEV=false, PROD=true
|
||
upsert_env SESSION_SAMESITE "lax"
|
||
upsert_env REDIS_CLIENT "phpredis"
|
||
upsert_env REDIS_HOST "127.0.0.1"
|
||
upsert_env REDIS_PORT "6379"
|
||
upsert_env REDIS_PASSWORD "${REDIS_PASS}"
|
||
upsert_env REDIS_DB "0"
|
||
upsert_env REDIS_CACHE_DB "1"
|
||
upsert_env REDIS_CACHE_CONNECTION "cache"
|
||
upsert_env REDIS_CACHE_LOCK_CONNECTION "default"
|
||
|
||
upsert_env BROADCAST_DRIVER "reverb"
|
||
upsert_env QUEUE_CONNECTION "redis"
|
||
upsert_env LOG_CHANNEL "daily"
|
||
|
||
upsert_env REVERB_APP_ID "${APP_USER_PREFIX}"
|
||
grep -q '^REVERB_APP_KEY=' "$ENV_FILE" || upsert_env REVERB_APP_KEY "${APP_USER_PREFIX}_$(openssl rand -hex 16)"
|
||
grep -q '^REVERB_APP_SECRET=' "$ENV_FILE" || upsert_env REVERB_APP_SECRET "${APP_USER_PREFIX}_$(openssl rand -hex 32)"
|
||
upsert_env REVERB_HOST "\${APP_HOST}"
|
||
upsert_env REVERB_PORT "443"
|
||
upsert_env REVERB_SCHEME "https"
|
||
upsert_env REVERB_PATH "/ws"
|
||
upsert_env REVERB_SCALING_ENABLED "true"
|
||
upsert_env REVERB_SCALING_CHANNEL "reverb"
|
||
|
||
upsert_env VITE_REVERB_APP_KEY "\${REVERB_APP_KEY}"
|
||
upsert_env VITE_REVERB_HOST "\${REVERB_HOST}"
|
||
upsert_env VITE_REVERB_PORT "\${REVERB_PORT}"
|
||
upsert_env VITE_REVERB_SCHEME "\${REVERB_SCHEME}"
|
||
upsert_env VITE_REVERB_PATH "\${REVERB_PATH}"
|
||
|
||
upsert_env REVERB_SERVER_APP_KEY "\${REVERB_APP_KEY}"
|
||
upsert_env REVERB_SERVER_HOST "127.0.0.1"
|
||
upsert_env REVERB_SERVER_PORT "8080"
|
||
upsert_env REVERB_SERVER_PATH ""
|
||
upsert_env REVERB_SERVER_SCHEME "http"
|
||
|
||
# --- DEV Block (optional) ---------------------------------------------------
|
||
DEV_MODE="${DEV_MODE:-0}"
|
||
if [[ "$DEV_MODE" = "1" ]]; then
|
||
sed -i '/^# --- MailWolt DEV/,/^# --- \/MailWolt DEV/d' "${ENV_FILE}"
|
||
cat >> "${ENV_FILE}" <<CONF
|
||
# --- MailWolt DEV ---
|
||
VITE_DEV_HOST=127.0.0.1
|
||
VITE_DEV_PORT=5173
|
||
VITE_HMR_PROTOCOL=wss
|
||
VITE_HMR_CLIENT_PORT=443
|
||
VITE_HMR_HOST=${SERVER_PUBLIC_IPV4}
|
||
VITE_DEV_ORIGIN=$(grep '^APP_URL=' "${ENV_FILE}" | cut -d= -f2-)
|
||
# --- /MailWolt DEV ---
|
||
CONF
|
||
fi
|
||
|
||
# --- Zertifikate in /etc/ssl/* bereitstellen, bevor Laravel irgendwas liest --
|
||
relink_and_reload
|
||
|
||
# --- RECHTE FIXEN: storage & bootstrap/cache (www-data + mailwolt) ----------
|
||
log "Setze korrekte Rechte für Laravel-Verzeichnisse …"
|
||
cd "${APP_DIR}"
|
||
chgrp -R www-data storage bootstrap/cache || true
|
||
find storage bootstrap/cache -type d -exec chmod 2775 {} \; || true
|
||
find storage bootstrap/cache -type f -exec chmod 0664 {} \; || true
|
||
setfacl -R -m u:www-data:rwx,u:${APP_USER}:rwx storage bootstrap/cache || true
|
||
setfacl -dR -m u:www-data:rwx,u:${APP_USER}:rwx storage bootstrap/cache || true
|
||
log "[✓] Schreibrechte für Laravel korrigiert."
|
||
|
||
# --- DKIM: Verzeichnisse & Basisrechte --------------------------------------
|
||
install -d -m 2775 -o "$APP_USER" -g www-data "$APP_DIR/storage/app/private"
|
||
install -d -m 2775 -o "$APP_USER" -g www-data "$APP_DIR/storage/app/private/dkim"
|
||
setfacl -R -m u:${APP_USER}:rwx,u:www-data:rwx "$APP_DIR/storage/app/private" || true
|
||
setfacl -dR -m u:${APP_USER}:rwx,u:www-data:rwx "$APP_DIR/storage/app/private" || true
|
||
|
||
# --- OpenDKIM: keys & DNS-Verzeichnis --------------------------------------
|
||
install -d -m 0750 -o opendkim -g opendkim /etc/opendkim
|
||
install -d -m 0750 -o opendkim -g opendkim /etc/opendkim/keys
|
||
install -d -m 0755 -o root -g root /etc/mailwolt
|
||
install -d -m 0755 -o root -g root /etc/mailwolt/dns
|
||
|
||
# --- Caches leeren, Migrationen ausführen -----------------------------------
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear"
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan migrate --force"
|
||
|
||
# --- Seeder (legt Domains/DKIM etc. an) -------------------------------------
|
||
if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan db:seed --class=SystemDomainSeeder --force"
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan db:seed --class=SystemBackupSeeder --force"
|
||
fi
|
||
|
||
# --- DKIM für SYSMAIL_DOMAIN via App erzeugen & per Helper einhängen --------
|
||
DKIM_ENABLE="${DKIM_ENABLE:-1}"
|
||
DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
|
||
SYSMAIL_DOMAIN="${SYSMAIL_DOMAIN:-sysmail.${BASE_DOMAIN}}"
|
||
|
||
if [[ "${DKIM_ENABLE}" = "1" && -n "${SYSMAIL_DOMAIN}" ]]; then
|
||
log "Erzeuge/aktualisiere DKIM für ${SYSMAIL_DOMAIN} (Selector: ${DKIM_SELECTOR}) …"
|
||
|
||
# 1) In der App generieren (als mailwolt), und Pfad + TXT zurückgeben
|
||
OUT="$(sudo -u "${APP_USER}" -H bash -lc "
|
||
set -e
|
||
cd '${APP_DIR}'
|
||
php -r '
|
||
require \"vendor/autoload.php\";
|
||
\$app=require \"bootstrap/app.php\";
|
||
\$app->make(Illuminate\\Contracts\\Console\\Kernel::class)->bootstrap();
|
||
\$d = App\\Models\\Domain::firstOrCreate([\"domain\"=>\"${SYSMAIL_DOMAIN}\"],[\"is_active\"=>1,\"is_system\"=>1]);
|
||
\$r = app(App\\Services\\DkimService::class)->generateForDomain(\$d, 2048, \"${DKIM_SELECTOR}\");
|
||
echo \$r[\"priv_path\"], \"\\n\";
|
||
echo \$r[\"dns_txt\"], \"\\n\";
|
||
'
|
||
")"
|
||
|
||
PRIV_PATH="$(printf '%s\n' "$OUT" | sed -n '1p')"
|
||
DNS_TXT="$(printf '%s\n' "$OUT" | sed -n '2,$p')"
|
||
|
||
if [[ -z "$PRIV_PATH" || ! -s "$PRIV_PATH" ]]; then
|
||
echo "[!] DKIM priv_path fehlt oder Datei leer: $PRIV_PATH" >&2
|
||
exit 1
|
||
fi
|
||
|
||
TMP_TXT="$(mktemp /tmp/dkim_txt_XXXXXX.txt)"
|
||
printf '%s' "$DNS_TXT" >"$TMP_TXT"
|
||
|
||
# 2) Root-Helper ausführen (hängt Key ein, pflegt Key/SigningTable, kopiert TXT)
|
||
if [[ -x /usr/local/sbin/mailwolt-install-dkim ]]; then
|
||
/usr/local/sbin/mailwolt-install-dkim "${SYSMAIL_DOMAIN}" "${DKIM_SELECTOR}" "${PRIV_PATH}" "${TMP_TXT}"
|
||
else
|
||
echo "[!] Helper /usr/local/sbin/mailwolt-install-dkim fehlt oder ist nicht ausführbar." >&2
|
||
fi
|
||
|
||
rm -f "$TMP_TXT" || true
|
||
|
||
# 3) OpenDKIM neu laden
|
||
touch /run/mailwolt.need-opendkim-reload || true
|
||
else
|
||
log "DKIM übersprungen (DKIM_ENABLE=${DKIM_ENABLE}, SYSMAIL_DOMAIN='${SYSMAIL_DOMAIN}')."
|
||
fi
|
||
|
||
|
||
# --- TLSA aus App heraus (idempotent; läuft, wenn Zert lesbar ist) ----------
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan dns:tlsa:refresh || true"
|
||
|
||
# --- Build Frontend (nur wenn nötig) ----------------------------------------
|
||
if [[ -f "${APP_DIR}/package.json" && ! -f "${APP_DIR}/public/build/manifest.json" ]]; then
|
||
safe_frontend_build
|
||
fi
|
||
|
||
# --- Abschluss: Caches + Rechte + Reloads -----------------------------------
|
||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear && php artisan config:cache && php artisan optimize:clear"
|
||
|
||
# Konsistente Rechte/ACL für das gesamte App-Verzeichnis
|
||
chown -R "$APP_USER":"$APP_GROUP" "$APP_DIR"
|
||
find "$APP_DIR" -type d -exec chmod 2775 {} \;
|
||
find "$APP_DIR" -type f -exec chmod 664 {} \;
|
||
setfacl -R -m g:"$APP_GROUP":rwX -m d:g:"$APP_GROUP":rwX "$APP_DIR" || true
|
||
|
||
# Laravel-Write-Dirs sicherstellen (mit setgid & ACL)
|
||
install -d -m 2775 -o "$APP_USER" -g "$APP_GROUP" "$APP_DIR/storage" "$APP_DIR/bootstrap/cache"
|
||
chgrp -R www-data "$APP_DIR/storage" "$APP_DIR/bootstrap/cache" || true
|
||
find "$APP_DIR/storage" "$APP_DIR/bootstrap/cache" -type d -exec chmod 2775 {} \; || true
|
||
find "$APP_DIR/storage" "$APP_DIR/bootstrap/cache" -type f -exec chmod 0664 {} \; || true
|
||
setfacl -R -m u:www-data:rwx,u:${APP_USER}:rwx "$APP_DIR/storage" "$APP_DIR/bootstrap/cache" || true
|
||
setfacl -dR -m u:www-data:rwx,u:${APP_USER}:rwx "$APP_DIR/storage" "$APP_DIR/bootstrap/cache" || true |