mailwolt-installer/scripts/70-nginx.sh

380 lines
11 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
log "Nginx konfigurieren …"
# ── Flags/Umgebung (vom Bootstrap gesetzt; hier Fallbacks) ────────────────
DEV_MODE="${DEV_MODE:-0}" # 1 = DEV (Vite-Proxy aktiv), 0 = PROD
PROXY_MODE="${PROXY_MODE:-0}" # 1 = NPM/Proxy davor, Backend spricht nur HTTP:80
NPM_IP="${NPM_IP:-}" # z.B. 10.10.20.20
# Erwartet vom Bootstrap/Installer exportiert:
: "${UI_HOST:?UI_HOST fehlt}"
: "${WEBMAIL_HOST:?WEBMAIL_HOST fehlt}"
: "${APP_DIR:?APP_DIR fehlt}"
ACME_ROOT="/var/www/letsencrypt"
install -d -m 0755 "$ACME_ROOT"
# Default-Sites entfernen (verhindert doppelten default_server)
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default || true
# HTTP/2-Unterstützung erkennen
NGINX_HTTP2_SUFFIX=""
if nginx -V 2>&1 | grep -q http_v2; then
NGINX_HTTP2_SUFFIX=" http2"
fi
# PHP-FPM Socket/TCP finden → fastcgi_pass bauen
detect_php_fpm_sock(){
for v in 8.3 8.2 8.1 8.0 7.4; do
s="/run/php/php${v}-fpm.sock"
[[ -S "$s" ]] && { echo "unix:${s}"; return; }
done
[[ -S "/run/php/php-fpm.sock" ]] && { echo "unix:/run/php/php-fpm.sock"; return; }
echo "127.0.0.1:9000"
}
PHP_FPM_TARGET="$(detect_php_fpm_sock)"
if [[ "$PHP_FPM_TARGET" == unix:* ]]; then
FASTCGI_PASS="fastcgi_pass ${PHP_FPM_TARGET};"
else
FASTCGI_PASS="fastcgi_pass ${PHP_FPM_TARGET};"
fi
# ── Builder 1: HTTP-only (Proxy-Mode: TLS endet im NPM) ───────────────────
## $1=host, $2=outfile
#build_site_http_only(){
# local host="$1" outfile="$2"
#
# local def=""
# [[ "${DEV_MODE}" = "1" ]] && def=" default_server"
# [[ -z "${host}" || "${host}" = "_" ]] && host="_"
#
# cat > "$outfile" <<CONF
## --- ${host} : HTTP (kein Redirect, kein TLS; läuft hinter Reverse-Proxy) ---
#server {
# listen 80;
# listen [::]:80;
# server_name ${host};
#
# # ACME HTTP-01 (optional; meist übernimmt das der Proxy)
# location ^~ /.well-known/acme-challenge/ {
# root ${ACME_ROOT};
# allow all;
# }
#
# root ${APP_DIR}/public;
# index index.php index.html;
#
# access_log /var/log/nginx/${host}_access.log;
# error_log /var/log/nginx/${host}_error.log;
#
# client_max_body_size 25m;
#
# location / { try_files \$uri \$uri/ /index.php?\$query_string; }
#
# location ~ \.php\$ {
# include snippets/fastcgi-php.conf;
# ${FASTCGI_PASS}
# }
#
# location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
# location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
#
# # WebSocket: Laravel Reverb (Backend intern HTTP)
# location /ws/ {
# proxy_http_version 1.1;
# proxy_set_header Upgrade \$http_upgrade;
# proxy_set_header Connection "Upgrade";
# proxy_set_header Host \$host;
# proxy_read_timeout 60s;
# proxy_send_timeout 60s;
# proxy_pass http://127.0.0.1:8080/;
# }
#
# # Reverb HTTP API
# location /apps/ {
# proxy_http_version 1.1;
# proxy_set_header Host \$host;
# proxy_read_timeout 60s;
# proxy_send_timeout 60s;
# proxy_pass http://127.0.0.1:8080/apps/;
# }
#CONF
#
# if [[ "${DEV_MODE}" = "1" ]]; then
# cat >> "$outfile" <<'CONF'
# # DEV: Vite-Proxy (HMR)
# location ^~ /@vite/ { proxy_pass http://127.0.0.1:5173/@vite/; proxy_set_header Host $host; }
# location ^~ /node_modules/ { proxy_pass http://127.0.0.1:5173/node_modules/; proxy_set_header Host $host; }
# location ^~ /resources/ { proxy_pass http://127.0.0.1:5173/resources/; proxy_set_header Host $host; }
#CONF
# fi
#
# echo "}" >> "$outfile"
#}
build_site_http_only(){
local host="$1" outfile="$2"
# DEV: IP-Zugriff ohne Hostname → default_server + server_name _
local def=""
if [[ "${DEV_MODE}" = "1" ]]; then
def=" default_server"
host="_"
fi
[[ -z "${host}" || "${host}" = "_" ]] && host="_"
cat > "$outfile" <<CONF
# --- ${host} : HTTP (kein Redirect, kein TLS; läuft hinter Reverse-Proxy/DEV) ---
server {
listen 80${def};
listen [::]:80${def};
server_name ${host};
# ACME HTTP-01 (optional; meist übernimmt das der Proxy)
location ^~ /.well-known/acme-challenge/ {
root ${ACME_ROOT};
allow all;
}
root ${APP_DIR}/public;
index index.php index.html;
access_log /var/log/nginx/${host/_/__}_access.log;
error_log /var/log/nginx/${host/_/__}_error.log;
client_max_body_size 25m;
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
${FASTCGI_PASS}
}
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
# WebSocket: Laravel Reverb
location /ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host \$host;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_pass http://127.0.0.1:8080/;
}
# Reverb HTTP API
location /apps/ {
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_pass http://127.0.0.1:8080/apps/;
}
CONF
if [[ "${DEV_MODE}" = "1" ]]; then
cat >> "$outfile" <<'CONF'
# DEV: Vite-Proxy (HMR)
location ^~ /@vite/ { proxy_pass http://127.0.0.1:5173/@vite/; proxy_set_header Host $host; }
location ^~ /node_modules/ { proxy_pass http://127.0.0.1:5173/node_modules/; proxy_set_header Host $host; }
location ^~ /resources/ { proxy_pass http://127.0.0.1:5173/resources/; proxy_set_header Host $host; }
CONF
fi
echo "}" >> "$outfile"
}
# ── Builder 2: 80→443 Redirect + 443/TLS (Live-Server) ────────────────────
# $1=host, $2=cert_dir (/etc/ssl/ui | /etc/ssl/webmail), $3=outfile
build_site_tls(){
local host="$1" cert_dir="$2" outfile="$3"
local cert="${cert_dir}/fullchain.pem"
local key="${cert_dir}/privkey.pem"
cat > "$outfile" <<CONF
# --- ${host} : HTTP (ACME + Redirect) ---
server {
listen 80;
listen [::]:80;
server_name ${host};
# ACME HTTP-01 auf Port 80
location ^~ /.well-known/acme-challenge/ {
root ${ACME_ROOT};
allow all;
}
return 301 https://\$host\$request_uri;
}
# --- ${host} : HTTPS ---
server {
listen 443 ssl${NGINX_HTTP2_SUFFIX};
listen [::]:443 ssl${NGINX_HTTP2_SUFFIX};
server_name ${host};
ssl_certificate ${cert};
ssl_certificate_key ${key};
ssl_protocols TLSv1.2 TLSv1.3;
# WICHTIG: ACME auch auf 443, sonst 404 bei Redirects
location ^~ /.well-known/acme-challenge/ {
root ${ACME_ROOT};
allow all;
}
root ${APP_DIR}/public;
index index.php index.html;
access_log /var/log/nginx/${host}_ssl_access.log;
error_log /var/log/nginx/${host}_ssl_error.log;
client_max_body_size 25m;
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
${FASTCGI_PASS}
}
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
# WebSocket: Laravel Reverb (Backend intern HTTP)
location /ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host \$host;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_pass http://127.0.0.1:8080/;
}
# Reverb HTTP API
location /apps/ {
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_pass http://127.0.0.1:8080/apps/;
}
CONF
if [[ "${DEV_MODE}" = "1" ]]; then
cat >> "$outfile" <<'CONF'
# DEV: Vite-Proxy
location ^~ /@vite/ { proxy_pass http://127.0.0.1:5173/@vite/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; }
location ^~ /node_modules/ { proxy_pass http://127.0.0.1:5173/node_modules/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; }
location ^~ /resources/ { proxy_pass http://127.0.0.1:5173/resources/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; }
CONF
fi
echo "}" >> "$outfile"
}
build_site_acme_only(){
local host="$1" outfile="$2"
cat > "$outfile" <<CONF
# --- ${host} : ACME-only (80 + 443), KEIN App-Root ---
server {
listen 80;
listen [::]:80;
server_name ${host};
# HTTP-01 Challenge exakt ausliefern
location ^~ /.well-known/acme-challenge/ {
root ${ACME_ROOT};
default_type "text/plain";
try_files \$uri =404;
}
# Alles andere → nach https
location / { return 301 https://\$host\$request_uri; }
}
server {
listen 443 ssl${NGINX_HTTP2_SUFFIX};
listen [::]:443 ssl${NGINX_HTTP2_SUFFIX};
server_name ${host};
ssl_certificate /etc/ssl/mail/fullchain.pem;
ssl_certificate_key /etc/ssl/mail/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Auch via https die Challenge bedienen (falls Redirects gefolgt werden)
location ^~ /.well-known/acme-challenge/ {
root ${ACME_ROOT};
default_type "text/plain";
try_files \$uri =404;
}
# Sonst nichts preisgeben
location / { return 444; }
}
CONF
}
# ── Sites erzeugen ─────────────────────────────────────────────────────────
MX_SITE="/etc/nginx/sites-available/mx-mailwolt.conf"
UI_SITE="/etc/nginx/sites-available/ui-mailwolt.conf"
WEBMAIL_SITE="/etc/nginx/sites-available/webmail-mailwolt.conf"
# UI & Webmail wie gehabt …
#if [[ "${PROXY_MODE:-0}" -eq 1 ]]; then
# build_site_http_only "$UI_HOST" "$UI_SITE"
# build_site_http_only "$WEBMAIL_HOST" "$WEBMAIL_SITE"
#else
# build_site_tls "$UI_HOST" "/etc/ssl/ui" "$UI_SITE"
# build_site_tls "$WEBMAIL_HOST" "/etc/ssl/webmail" "$WEBMAIL_SITE"
#fi
# UI & Webmail …
if [[ "${DEV_MODE}" = "1" ]]; then
# DEV: per IP erreichbar → Catch-All („_“) und HTTP-only
build_site_http_only "_" "$UI_SITE"
build_site_http_only "_" "$WEBMAIL_SITE"
else
if [[ "${PROXY_MODE:-0}" -eq 1 ]]; then
build_site_http_only "$UI_HOST" "$UI_SITE"
build_site_http_only "$WEBMAIL_HOST" "$WEBMAIL_SITE"
else
build_site_tls "$UI_HOST" "/etc/ssl/ui" "$UI_SITE"
build_site_tls "$WEBMAIL_HOST" "/etc/ssl/webmail" "$WEBMAIL_SITE"
fi
fi
# MX: **immer** ACME-only (kein Laravel dahinter)
build_site_acme_only "${MAIL_HOSTNAME}" "$MX_SITE"
ln -sf "$UI_SITE" /etc/nginx/sites-enabled/ui-mailwolt.conf
ln -sf "$WEBMAIL_SITE" /etc/nginx/sites-enabled/webmail-mailwolt.conf
ln -sf "$MX_SITE" /etc/nginx/sites-enabled/mx-mailwolt.conf
# ── Real-IP nur, wenn Proxy davor ──────────────────────────────────────────
if [[ "${PROXY_MODE}" -eq 1 && -n "${NPM_IP}" ]]; then
cat > /etc/nginx/conf.d/realip.conf <<NGX
real_ip_header X-Forwarded-For;
set_real_ip_from ${NPM_IP};
real_ip_recursive on;
NGX
else
rm -f /etc/nginx/conf.d/realip.conf || true
fi
# ── Test & reload ──────────────────────────────────────────────────────────
if nginx -t; then
systemctl enable --now nginx >/dev/null 2>&1 || true
systemctl reload nginx || true
else
die "nginx -t fehlgeschlagen siehe /var/log/nginx/*.log"
fi