mailwolt-installer/scripts/80-app.sh

342 lines
14 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
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