| Komponente | Funktion |
|---|---|
| nginx (gw/nginx:cache-purge) | HTTP/2 via nginx-proxy, gzip, 30 Tage Browser-Cache für statische Assets, FastCGI-Cache für PHP-Output (60 min) |
| PHP-FPM (gw/php:static-mail) | PHP 8.3-Alpine + OPcache + msmtp als sendmail-Ersatz |
| HTTPS | automatisch via acme-companion (LetsEncrypt) |
| fail2ban-Schutz | greift auch hier (gleiche Log-Pfade), die WP-Filter matchen halt nichts — kein Schaden |
/mnt/data/new-static-site.sh
Wait – noch üpberarbeiten, viel zu viel!
Bash
#!/bin/bash
# Provisioniert eine schlanke Static-Site (mit optionalem PHP) unter
# /mnt/data/sites/<name>.
#
# Stack: nginx (gw/nginx:cache-purge) + PHP-FPM (gw/php:static).
# Zweck: vorwiegend HTML/CSS/JS. PHP läuft mit, falls du mal ein
# Formular (z.B. via PHPMailer + SMTP) brauchst — kein FastCGI-Cache,
# kein msmtp, keine Mail-Magic im Image.
#
# SMTP-Creds (wenn angegeben) werden NUR in .credentials notiert —
# du übernimmst sie selber in dein PHPMailer-Setup im PHP-Code.
#
# Erzeugt:
# /mnt/data/sites/<name>/ docker-compose, .credentials (chmod 600)
# /mnt/data/sites/<name>/public/ Web-Root (HTML/CSS/JS/PHP)
# /mnt/data/sites/<name>/config/ nginx.conf, php.ini
# /mnt/data/logs/<name>/nginx/ access.log + error.log
set -euo pipefail
read -p "Site-Name (nur a-z 0-9 -, z.B. landing-x): " SITE_NAME
read -p "Domain (z.B. landing.ch): " DOMAIN
echo
echo "SMTP für Formulare via PHPMailer (leer lassen = keine SMTP-Notiz):"
read -p " SMTP-Host: " SMTP_HOST
SMTP_PORT="" SMTP_USER="" SMTP_PASS="" SMTP_FROM=""
if [[ -n "$SMTP_HOST" ]]; then
read -p " SMTP-Port [587]: " SMTP_PORT; SMTP_PORT=${SMTP_PORT:-587}
read -p " SMTP-User: " SMTP_USER
read -rsp " SMTP-Pass: " SMTP_PASS; echo
read -p " Absender (From): " SMTP_FROM
fi
[[ -z "$SITE_NAME" || -z "$DOMAIN" ]] && { echo "❌ Site-Name + Domain Pflicht"; exit 1; }
[[ ! "$SITE_NAME" =~ ^[a-z0-9-]+$ ]] && { echo "❌ Site-Name: nur a-z 0-9 -"; exit 1; }
[[ ! "$DOMAIN" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] && { echo "❌ Domain ungültig"; exit 1; }
SITE_DIR="/mnt/data/sites/$SITE_NAME"
[[ -e "$SITE_DIR" ]] && { echo "❌ $SITE_DIR existiert bereits"; exit 1; }
cleanup() {
echo ""
echo "❌ Fehler — räume auf..."
if [[ -f "$SITE_DIR/docker-compose.yml" ]]; then
(cd "$SITE_DIR" && docker compose down -v 2>/dev/null) || true
fi
rm -rf "$SITE_DIR"
echo "Cleanup fertig."
exit 1
}
trap cleanup ERR INT TERM
mkdir -p "$SITE_DIR/public" "$SITE_DIR/config"
mkdir -p "/mnt/data/logs/$SITE_NAME/nginx"
cat > "$SITE_DIR/public/index.html" << EOF
<!doctype html>
<html lang="de"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>$DOMAIN</title></head>
<body><h1>$DOMAIN</h1><p>Static site is up.</p></body></html>
EOF
cat > "$SITE_DIR/config/php.ini" << 'PHPEOF'
upload_max_filesize = 32M
post_max_size = 32M
max_execution_time = 60
memory_limit = 128M
opcache.enable = 1
opcache.memory_consumption = 64
opcache.max_accelerated_files = 4000
opcache.revalidate_freq = 2
opcache.validate_timestamps = 1
PHPEOF
cat > "$SITE_DIR/config/nginx.conf" << 'NGINXEOF'
server {
listen 80;
root /var/www/html;
index index.html index.php;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/javascript application/json image/svg+xml;
location ~ /\. { deny all; }
# nginx vererbt add_header NICHT in Child-locations mit eigenen add_header
# — Snippet pro location includen.
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff|woff2|ttf|otf|eot)$ {
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "public, no-transform" always;
expires 30d;
access_log off;
try_files $uri =404;
}
location / {
include /etc/nginx/snippets/security-headers.conf;
try_files $uri $uri/ /index.php?$args =404;
}
location ~ \.php$ {
include /etc/nginx/snippets/security-headers.conf;
try_files $uri =404;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
NGINXEOF
cat > "$SITE_DIR/docker-compose.yml" << EOF
services:
php:
image: gw/php:static
container_name: ${SITE_NAME}-php
restart: unless-stopped
volumes:
- ./public:/var/www/html
- ./config/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
networks:
- internal
nginx:
image: gw/nginx:cache-purge
container_name: ${SITE_NAME}-nginx
restart: unless-stopped
command: sh -c "umask 002 && exec nginx -g 'daemon off;'"
environment:
VIRTUAL_HOST: ${DOMAIN}
LETSENCRYPT_HOST: ${DOMAIN}
volumes:
- ./public:/var/www/html:ro
- ./config/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /mnt/data/logs/${SITE_NAME}/nginx:/var/log/nginx
networks:
- internal
- nginx-proxy_default
depends_on:
- php
networks:
internal:
nginx-proxy_default:
external: true
EOF
cd "$SITE_DIR"
docker compose up -d
if [[ -n "$SMTP_HOST" ]]; then
cat > "$SITE_DIR/.credentials" << EOF
Site: $SITE_NAME
Domain: https://$DOMAIN
SMTP (für PHPMailer im PHP-Code zu verwenden):
Host: $SMTP_HOST
Port: $SMTP_PORT
User: $SMTP_USER
Pass: $SMTP_PASS
From: $SMTP_FROM
EOF
chmod 600 "$SITE_DIR/.credentials"
fi
trap - ERR INT TERM
echo ""
echo "✅ Static-Site erstellt!"
echo " URL: https://$DOMAIN"
echo " Web-Root: $SITE_DIR/public/ (HTML, CSS, JS, PHP hier ablegen)"
if [[ -n "$SMTP_HOST" ]]; then
echo " SMTP-Notiz: $SITE_DIR/.credentials (chmod 600)"
echo " — Werte selber in dein PHPMailer-Setup übernehmen."
fi
echo ""