diff --git a/config/nginx/site.conf.tmpl b/config/nginx/site.conf.tmpl deleted file mode 100644 index c9e6bbd..0000000 --- a/config/nginx/site.conf.tmpl +++ /dev/null @@ -1,64 +0,0 @@ -server { - listen 80 default_server; - listen [::]:80 default_server; - - # ACME - location ^~ /.well-known/acme-challenge/ { - root /var/www/letsencrypt; - allow all; - } - - # Wenn SSL da: redirect auf 443, sonst direkt App - {% if ssl %} - return 301 https://$host$request_uri; - {% endif %} -} - -server { - listen 443 ssl${NGINX_HTTP2_SUFFIX}; - listen [::]:443 ssl${NGINX_HTTP2_SUFFIX}; - - ssl_certificate ${UI_CERT}; - ssl_certificate_key ${UI_KEY}; - ssl_protocols TLSv1.2 TLSv1.3; - - server_name _; - - root ${APP_DIR}/public; - index index.php index.html; - - access_log /var/log/nginx/app_ssl_access.log; - error_log /var/log/nginx/app_ssl_error.log; - - client_max_body_size 25m; - - location / { try_files $uri $uri/ /index.php?$query_string; } - location ~ \.php$ { - include snippets/fastcgi-php.conf; - # Der pass (unix vs tcp) wird vom System gesetzt; Debian snippet kümmert sich - fastcgi_pass unix:/run/php/php-fpm.sock; - try_files $uri =404; - } - 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/; - } -} diff --git a/scripts/10-provision.sh b/scripts/10-provision.sh deleted file mode 100644 index 68fce27..0000000 --- a/scripts/10-provision.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -log "Paketquellen aktualisieren…" -export DEBIAN_FRONTEND=noninteractive -apt-get update -y - -# MariaDB include-Workaround -mkdir -p /etc/mysql /etc/mysql/mariadb.conf.d -[[ -f /etc/mysql/mariadb.cnf ]] || echo '!include /etc/mysql/mariadb.conf.d/*.cnf' > /etc/mysql/mariadb.cnf - -log "Pakete installieren… (das dauert etwas)" -apt-get -y -o Dpkg::Options::="--force-confdef" \ - -o Dpkg::Options::="--force-confold" install \ - postfix postfix-mysql \ - dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql \ - mariadb-server mariadb-client \ - redis-server \ - rspamd \ - opendkim opendkim-tools \ - nginx \ - php php-fpm php-cli php-mbstring php-xml php-curl php-zip php-mysql php-redis php-gd unzip curl \ - composer git \ - certbot python3-certbot-nginx \ - fail2ban \ - ca-certificates rsyslog sudo openssl netcat-openbsd monit acl - -# HTTP/2 prüfen -NGINX_HTTP2_SUPPORTED=0 -if nginx -V 2>&1 | grep -q http_v2; then - NGINX_HTTP2_SUPPORTED=1; log "Nginx: HTTP/2 verfügbar." -else - warn "Nginx http_v2 fehlt – versuche nginx-full…" - apt-get install -y nginx-full || true; systemctl restart nginx || true - nginx -V 2>&1 | grep -q http_v2 && NGINX_HTTP2_SUPPORTED=1 || warn "HTTP/2 weiterhin nicht verfügbar." -fi -export NGINX_HTTP2_SUFFIX=$([[ "$NGINX_HTTP2_SUPPORTED" = "1" ]] && echo " http2" || echo "") - -# Verzeichnisse / User -log "Verzeichnisse & Benutzer…" -mkdir -p /etc/postfix/sql /etc/dovecot/conf.d /etc/rspamd/local.d /var/mail/vhosts -id vmail >/dev/null 2>&1 || adduser --system --group --home /var/mail vmail -chown -R vmail:vmail /var/mail - -id "$APP_USER" >/dev/null 2>&1 || adduser --disabled-password --gecos "" "$APP_USER" -usermod -a -G "$APP_GROUP" "$APP_USER" - -# Redis absichern -log "Redis absichern…" -REDIS_CONF="/etc/redis/redis.conf" -REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}" -sed -i 's/^\s*#\?\s*bind .*/bind 127.0.0.1/' "$REDIS_CONF" -sed -i 's/^\s*#\?\s*protected-mode .*/protected-mode yes/' "$REDIS_CONF" -if grep -qE '^\s*#?\s*requirepass ' "$REDIS_CONF"; then - sed -i "s/^\s*#\?\s*requirepass .*/requirepass ${REDIS_PASS}/" "$REDIS_CONF" -else - printf "\nrequirepass %s\n" "${REDIS_PASS}" >> "$REDIS_CONF" -fi -systemctl enable --now redis-server -systemctl restart redis-server diff --git a/scripts/20-ssl.sh b/scripts/20-ssl.sh deleted file mode 100644 index 173fda7..0000000 --- a/scripts/20-ssl.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -# Stabile Pfade -MAIL_SSL_DIR="/etc/ssl/mail" -UI_SSL_DIR="/etc/ssl/ui" -WEBMAIL_SSL_DIR="/etc/ssl/webmail" - -ensure_dir root root 0755 "$MAIL_SSL_DIR" -ensure_dir root root 0755 "$UI_SSL_DIR" -ensure_dir root root 0755 "$WEBMAIL_SSL_DIR" -ensure_dir root root 0755 "/var/www/letsencrypt" - -# Self-signed Quick-Gen (wenn kein LE kommt) -self_signed(){ - local dir="$1" - local cert="${dir}/fullchain.pem" key="${dir}/privkey.pem" - [[ -s "$cert" && -s "$key" ]] && return 0 - log "Self-signed für $dir …" - openssl req -x509 -newkey rsa:2048 -sha256 -days 825 -nodes \ - -subj "/CN=${SERVER_PUBLIC_IPV4}/O=${APP_NAME}/C=DE" \ - -keyout "$key" -out "$cert" >/dev/null 2>&1 - chmod 600 "$key"; chmod 644 "$cert" -} - -self_signed "$MAIL_SSL_DIR" -self_signed "$UI_SSL_DIR" -self_signed "$WEBMAIL_SSL_DIR" - -issue_cert(){ - local host="$1" - if resolve_ok "$host"; then - log "LE für $host …" - certbot certonly --agree-tos -m "${LE_EMAIL}" \ - --non-interactive --webroot -w /var/www/letsencrypt -d "$host" \ - || warn "LE fehlgeschlagen für $host – Self-signed bleibt aktiv." - else - warn "DNS zeigt nicht auf diese IP: $host – LE wird übersprungen." - fi -} - -link_if_present(){ - local host="$1" target_dir="$2" - local base="/etc/letsencrypt/live/$host" - if [[ -f "$base/fullchain.pem" && -f "$base/privkey.pem" ]]; then - ln -sf "$base/fullchain.pem" "$target_dir/fullchain.pem" - ln -sf "$base/privkey.pem" "$target_dir/privkey.pem" - log "TLS verlinkt: $target_dir -> $base" - fi -} - -# Echte Domain? Dann versuchen -if [[ "$BASE_DOMAIN" != "example.com" ]]; then - issue_cert "$UI_HOST" - issue_cert "$WEBMAIL_HOST" - issue_cert "$MAIL_HOSTNAME" - - link_if_present "$UI_HOST" "$UI_SSL_DIR" - link_if_present "$WEBMAIL_HOST" "$WEBMAIL_SSL_DIR" - link_if_present "$MAIL_HOSTNAME" "$MAIL_SSL_DIR" -else - warn "BASE_DOMAIN=example.com – bleibe bei Self-signed." -fi - -# LE-Deploy-Hook (Symlinks aktuell halten) -install -d /etc/letsencrypt/renewal-hooks/deploy -cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh <<'HOOK' -#!/usr/bin/env bash -set -euo pipefail -UI_SSL_DIR="/etc/ssl/ui" -WEBMAIL_SSL_DIR="/etc/ssl/webmail" -MAIL_SSL_DIR="/etc/ssl/mail" -UI_HOST="${UI_HOST}" -WEBMAIL_HOST="${WEBMAIL_HOST}" -MX_HOST="${MAIL_HOSTNAME}" - -link_if() { - local le_base="/etc/letsencrypt/live/$1" target_dir="$2" - if [[ -f "$le_base/fullchain.pem" && -f "$le_base/privkey.pem" ]]; then - install -d -m 0755 "$target_dir" - ln -sf "$le_base/fullchain.pem" "$target_dir/fullchain.pem" - ln -sf "$le_base/privkey.pem" "$target_dir/privkey.pem" - echo "[+] Linked $target_dir -> $le_base" - fi -} -link_if "$UI_HOST" "$UI_SSL_DIR" -link_if "$WEBMAIL_HOST" "$WEBMAIL_SSL_DIR" -link_if "$MX_HOST" "$MAIL_SSL_DIR" -systemctl reload nginx || true -HOOK -chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh diff --git a/scripts/30-db.sh b/scripts/30-db.sh deleted file mode 100644 index 1b38047..0000000 --- a/scripts/30-db.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -DB_NAME="${DB_NAME:-${APP_USER}}" -DB_USER="${DB_USER:-${APP_USER}}" -DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}" - -log "MariaDB vorbereiten…" -systemctl enable --now mariadb - -mysql -uroot < /etc/postfix/sql/mysql-virtual-mailbox-maps.cf < /etc/postfix/sql/mysql-virtual-alias-maps.cf </dev/null 2>&1 || true diff --git a/scripts/50-dovecot.sh b/scripts/50-dovecot.sh deleted file mode 100644 index 663ec1d..0000000 --- a/scripts/50-dovecot.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -MAIL_SSL_DIR="/etc/ssl/mail" -MAIL_CERT="${MAIL_SSL_DIR}/fullchain.pem" -MAIL_KEY="${MAIL_SSL_DIR}/privkey.pem" - -log "Dovecot konfigurieren…" - -cat > /etc/dovecot/dovecot.conf <<'CONF' -!include_try /etc/dovecot/conf.d/*.conf -CONF - -cat > /etc/dovecot/conf.d/10-mail.conf <<'CONF' -protocols = imap pop3 lmtp -mail_location = maildir:/var/mail/vhosts/%d/%n - -namespace inbox { - inbox = yes -} - -mail_privileged_group = mail -CONF - -cat > /etc/dovecot/conf.d/10-auth.conf <<'CONF' -disable_plaintext_auth = yes -auth_mechanisms = plain login -!include_try auth-sql.conf.ext -CONF - -cat > /etc/dovecot/dovecot-sql.conf.ext < /etc/dovecot/conf.d/auth-sql.conf.ext <<'CONF' -passdb { - driver = sql - args = /etc/dovecot/dovecot-sql.conf.ext -} -userdb { - driver = static - args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n -} -CONF -chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext; chmod 640 /etc/dovecot/conf.d/auth-sql.conf.ext - -cat > /etc/dovecot/conf.d/10-master.conf <<'CONF' -service lmtp { - unix_listener /var/spool/postfix/private/dovecot-lmtp { - mode = 0600 - user = postfix - group = postfix - } -} -service auth { - unix_listener /var/spool/postfix/private/auth { - mode = 0660 - user = postfix - group = postfix - } -} -service imap-login { - inet_listener imap { port = 143 } - inet_listener imaps { port = 993; ssl = yes } -} -service pop3-login { - inet_listener pop3 { port = 110 } - inet_listener pop3s { port = 995; ssl = yes } -} -CONF - -DOVECOT_SSL_CONF="/etc/dovecot/conf.d/10-ssl.conf" -grep -q '^ssl\s*=' "$DOVECOT_SSL_CONF" 2>/dev/null || echo "ssl = required" >> "$DOVECOT_SSL_CONF" -if grep -q '^\s*ssl_cert\s*=' "$DOVECOT_SSL_CONF"; then - sed -i "s|^\s*ssl_cert\s*=.*|ssl_cert = <${MAIL_CERT}|" "$DOVECOT_SSL_CONF" -else - echo "ssl_cert = <${MAIL_CERT}" >> "$DOVECOT_SSL_CONF" -fi -if grep -q '^\s*ssl_key\s*=' "$DOVECOT_SSL_CONF"; then - sed -i "s|^\s*ssl_key\s*=.*|ssl_key = <${MAIL_KEY}|" "$DOVECOT_SSL_CONF" -else - echo "ssl_key = <${MAIL_KEY}" >> "$DOVECOT_SSL_CONF" -fi - -mkdir -p /var/spool/postfix/private -chown postfix:postfix /var/spool/postfix /var/spool/postfix/private -chmod 0755 /var/spool/postfix /var/spool/postfix/private - -systemctl enable dovecot >/dev/null 2>&1 || true diff --git a/scripts/60-rspamd-opendkim.sh b/scripts/60-rspamd-opendkim.sh deleted file mode 100644 index 4add956..0000000 --- a/scripts/60-rspamd-opendkim.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -log "Rspamd + OpenDKIM aktivieren…" - -cat > /etc/rspamd/local.d/worker-controller.inc <<'CONF' -password = "admin"; -bind_socket = "127.0.0.1:11334"; -CONF -systemctl enable --now rspamd || true - -cat > /etc/opendkim.conf <<'CONF' -Syslog yes -UMask 002 -Mode sv -Socket inet:8891@127.0.0.1 -Canonicalization relaxed/simple -On-BadSignature accept -On-Default accept -On-KeyNotFound accept -On-NoSignature accept -LogWhy yes -OversignHeaders From -CONF -systemctl enable --now opendkim || true diff --git a/scripts/70-nginx.sh b/scripts/70-nginx.sh deleted file mode 100644 index 16b737e..0000000 --- a/scripts/70-nginx.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -NGINX_SITE="/etc/nginx/sites-available/${APP_USER}.conf" -NGINX_SITE_LINK="/etc/nginx/sites-enabled/${APP_USER}.conf" -TEMPLATE="$(cd .. && pwd)/config/nginx/site.conf.tmpl" - -# Platzhalter für Template -export APP_DIR -export UI_SSL_DIR="/etc/ssl/ui" -export UI_CERT="${UI_SSL_DIR}/fullchain.pem" -export UI_KEY="${UI_SSL_DIR}/privkey.pem" -export NGINX_HTTP2_SUFFIX - -# Template -> Site -log "Nginx konfigurieren…" -envsubst '$APP_DIR $UI_CERT $UI_KEY $NGINX_HTTP2_SUFFIX' < "$TEMPLATE" > "$NGINX_SITE" - -ln -sf "$NGINX_SITE" "$NGINX_SITE_LINK" -if nginx -t; then - systemctl enable --now nginx - systemctl reload nginx || true -else - die "Nginx-Konfiguration fehlerhaft – /var/log/nginx prüfen." -fi diff --git a/scripts/80-app.sh b/scripts/80-app.sh deleted file mode 100644 index 8adfe4b..0000000 --- a/scripts/80-app.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -log "Laravel bereitstellen…" -mkdir -p "$(dirname "$APP_DIR")"; chown -R "$APP_USER":"$APP_GROUP" "$(dirname "$APP_DIR")" - -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 " - set -e - cd ${APP_DIR} - git fetch --depth=1 origin ${GIT_BRANCH} || true - git checkout ${GIT_BRANCH} 2>/dev/null || git checkout -B ${GIT_BRANCH} - if git merge-base --is-ancestor HEAD origin/${GIT_BRANCH}; then git pull --ff-only; else git reset --hard origin/${GIT_BRANCH}; git clean -fd; fi - " -fi - -if [[ -f "${APP_DIR}/composer.json" ]]; then - sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --no-dev" -fi - -# .env -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}" || sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan key:generate --force || true" - -upsert_env(){ - local k="$1" v="$2" - local ek ev; ek="$(printf '%s' "$k" | sed -e 's/[.[\*^$(){}+?|/]/\\&/g')"; ev="$(printf '%s' "$v" | sed -e 's/[&/]/\\&/g')" - if grep -qE "^[#[:space:]]*${ek}=" "$ENV_FILE"; then - sed -Ei "s|^[#[:space:]]*${ek}=.*|${k}=${ev}|g" "$ENV_FILE" - else - printf '%s=%s\n' "$k" "$v" >> "$ENV_FILE" - fi -} - -# APP_URL/Host -APP_HOST_VAL="$SERVER_PUBLIC_IPV4" -UI_LE_CERT="/etc/letsencrypt/live/${UI_HOST}/fullchain.pem" -UI_LE_KEY="/etc/letsencrypt/live/${UI_HOST}/privkey.pem" -if [[ "$BASE_DOMAIN" != "example.com" && -n "$UI_HOST" ]] && ( resolve_ok "$UI_HOST" || [[ -f "$UI_LE_CERT" && -f "$UI_LE_KEY" ]] ); then - APP_HOST_VAL="$UI_HOST" -fi -if [[ "$APP_HOST_VAL" = "$UI_HOST" && -f "$UI_LE_CERT" && -f "$UI_LE_KEY" ]]; then - APP_URL_VAL="https://${UI_HOST}" -else - if [[ -f "/etc/ssl/ui/fullchain.pem" && -f "/etc/ssl/ui/privkey.pem" ]]; then - APP_URL_VAL="https://${SERVER_PUBLIC_IPV4}" - else - APP_URL_VAL="http://${SERVER_PUBLIC_IPV4}" - fi -fi - -# .env schreiben -upsert_env APP_URL "${APP_URL_VAL}" -upsert_env APP_HOST "${APP_HOST_VAL}" -upsert_env APP_NAME "${APP_NAME}" -upsert_env APP_ENV "${APP_ENV}" -upsert_env APP_DEBUG "${APP_DEBUG}" -upsert_env 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 BASE_DOMAIN "${BASE_DOMAIN}" -upsert_env UI_SUB "${UI_SUB}" -upsert_env WEBMAIL_SUB "${WEBMAIL_SUB}" -upsert_env SYSTEM_SUB "${SYSTEM_SUB}" -upsert_env MTA_SUB "${MTA_SUB}" -upsert_env LE_EMAIL "${LE_EMAIL}" - -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 "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" - -# Reverb / Queue (wie bei dir) -upsert_env BROADCAST_DRIVER "reverb" -upsert_env QUEUE_CONNECTION "redis" -upsert_env LOG_CHANNEL "daily" -upsert_env REVERB_APP_ID "${APP_USER_PREFIX}" -upsert_env REVERB_APP_KEY "${APP_USER_PREFIX}_$(openssl rand -hex 16)" -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" - -# Optimize + Migrate + Seed -sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear && php artisan config:cache" -sudo systemctl restart php*-fpm || true - -sudo -u "$APP_USER" -H bash -lc " - set -e - cd ${APP_DIR} - php artisan optimize:clear - php artisan migrate --force - php artisan config:cache - php artisan optimize:clear -" - -if [[ "$BASE_DOMAIN" != "example.com" ]]; then - sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan db:seed --class=SystemDomainSeeder --no-interaction || true" -else - echo "[i] BASE_DOMAIN=example.com – Seeder übersprungen." -fi diff --git a/scripts/90-services.sh b/scripts/90-services.sh deleted file mode 100644 index 9116684..0000000 --- a/scripts/90-services.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -log "systemd Units (Reverb/Schedule/Queue)…" - -cat > /etc/systemd/system/${APP_USER}-ws.service < /etc/systemd/system/${APP_USER}-schedule.service < /etc/systemd/system/${APP_USER}-queue.service </dev/null 2>&1 || true -fi -systemctl enable --now ${APP_USER}-schedule -systemctl enable --now ${APP_USER}-queue - -systemctl reload nginx || true -systemctl restart php*-fpm || true - -db_ready(){ - mysql -u"${DB_USER}" -p"${DB_PASS}" -h 127.0.0.1 -D "${DB_NAME}" -e "SHOW TABLES LIKE 'migrations'\G" >/dev/null 2>&1 -} -if db_ready; then - systemctl reload postfix || true - systemctl reload dovecot || true -else - echo "[i] DB noch nicht migriert – überspringe Postfix/Dovecot reload." -fi diff --git a/scripts/95-monit.sh b/scripts/95-monit.sh deleted file mode 100644 index cc3da56..0000000 --- a/scripts/95-monit.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -log "Monit konfigurieren…" -cat > /etc/monit/monitrc <<'EOF' -set daemon 60 -set logfile syslog facility log_daemon - -check process postfix with pidfile /var/spool/postfix/pid/master.pid - start program = "/bin/systemctl start postfix" - stop program = "/bin/systemctl stop postfix" - if failed port 25 protocol smtp then restart - if failed port 465 type tcp ssl then restart - if failed port 587 type tcp then restart - -check process dovecot with pidfile /var/run/dovecot/master.pid - start program = "/bin/systemctl start dovecot" - stop program = "/bin/systemctl stop dovecot" - if failed port 143 type tcp then restart - if failed port 993 type tcp ssl then restart - if failed port 110 type tcp then restart - if failed port 995 type tcp ssl then restart - -check process mariadb with pidfile /var/run/mysqld/mysqld.pid - start program = "/bin/systemctl start mariadb" - stop program = "/bin/systemctl stop mariadb" - if failed port 3306 type tcp then restart - -check process redis-server with pidfile /run/redis/redis-server.pid - start program = "/bin/systemctl start redis-server" - stop program = "/bin/systemctl stop redis-server" - if failed port 6379 type tcp then restart - -check process rspamd with pidfile /run/rspamd/rspamd.pid - start program = "/bin/systemctl start rspamd" - stop program = "/bin/systemctl stop rspamd" - if failed port 11332 type tcp then restart - -check process opendkim with pidfile /run/opendkim/opendkim.pid - start program = "/bin/systemctl start opendkim" - stop program = "/bin/systemctl stop opendkim" - if failed port 8891 type tcp then restart - -check process nginx with pidfile /run/nginx.pid - start program = "/bin/systemctl start nginx" - stop program = "/bin/systemctl stop nginx" - if failed port 80 type tcp then restart - if failed port 443 type tcp ssl then restart - -# Reverb WebSocket -check process mailwolt-ws matching "reverb:start" - start program = "/bin/systemctl start mailwolt-ws" - stop program = "/bin/systemctl stop mailwolt-ws" - if failed host 127.0.0.1 port 8080 type tcp for 2 cycles then restart - if 5 restarts within 5 cycles then timeout -EOF - -chmod 600 /etc/monit/monitrc -monit -t && systemctl enable --now monit -monit reload -monit summary || true diff --git a/scripts/98-motd.sh b/scripts/98-motd.sh deleted file mode 100644 index 987e2b1..0000000 --- a/scripts/98-motd.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -install -d /usr/local/bin -cat >/usr/local/bin/mw-motd <<'SH' -#!/usr/bin/env bash -set -euo pipefail -NC="\033[0m"; CY="\033[1;36m"; GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"; GY="\033[0;90m" -printf "\033[1;36m" -cat <<'ASCII' -:::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: ::::::::::: -+:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: -+:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ -+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+ -+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+ -#+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+# -### ### ### ### ########### ########## ### ### ######## ########## ### -ASCII -printf "\033[0m\n" -now="$(date '+%Y-%m-%d %H:%M:%S %Z')" -fqdn="$(hostname -f 2>/dev/null || hostname)" -ip_int="$(hostname -I 2>/dev/null | awk '{print $1}')" -ip_ext=""; command -v curl >/dev/null 2>&1 && ip_ext="$(curl -s --max-time 1 https://ifconfig.me || true)" -upt="$(uptime -p 2>/dev/null || true)" -cores="$(nproc 2>/dev/null || echo -n '?')" -mhz="$(LC_ALL=C lscpu 2>/dev/null | awk -F: '/MHz/{gsub(/ /,"",$2); printf("%.0f MHz",$2); exit}')" -load="$(awk '{print $1" / "$2" / "$3}' /proc/loadavg 2>/dev/null)" -mem_total="$(awk '/MemTotal/{printf "%.2f GB",$2/1024/1024}' /proc/meminfo)" -mem_free="$(awk '/MemAvailable/{printf "%.2f GB",$2/1024/1024}' /proc/meminfo)" -svc_status(){ systemctl is-active --quiet "$1" && echo -e "${GR}OK${NC}" || echo -e "${RD}FAIL${NC}"; } -printf "${CY}Information as of:${NC} ${YE}%s${NC}\n" "$now" -printf "${GY}FQDN :${NC} %s\n" "$fqdn" -if [ -n "$ip_ext" ]; then printf "${GY}IP :${NC} %s ${GY}(external:${NC} %s${GY})${NC}\n" "${ip_int:-?}" "$ip_ext"; else printf "${GY}IP :${NC} %s\n" "${ip_int:-?}"; fi -printf "${GY}Uptime :${NC} %s\n" "${upt:-?}" -printf "${GY}Core(s) :${NC} %s core(s) at ${CY}%s${NC}\n" "$cores" "${mhz:-?}" -printf "${GY}Load :${NC} %s (1 / 5 / 15)\n" "${load:-?}" -printf "${GY}Memory :${NC} ${RD}%s${NC} ${GY}(free)${NC} / ${CY}%s${NC} ${GY}(total)${NC}\n" "${mem_free:-?}" "${mem_total:-?}" -echo -printf "${GY}Services :${NC} postfix: $(svc_status postfix) dovecot: $(svc_status dovecot) nginx: $(svc_status nginx) mariadb: $(svc_status mariadb) redis: $(svc_status redis)\n" -SH -chmod +x /usr/local/bin/mw-motd - -if [[ -d /etc/update-motd.d ]]; then - cat >/etc/update-motd.d/10-mailwolt <<'SH' -#!/usr/bin/env bash -/usr/local/bin/mw-motd -SH - chmod +x /etc/update-motd.d/10-mailwolt - [[ -f /etc/update-motd.d/50-motd-news ]] && chmod -x /etc/update-motd.d/50-motd-news || true - [[ -f /etc/update-motd.d/80-livepatch ]] && chmod -x /etc/update-motd.d/80-livepatch || true -else - cat >/etc/profile.d/10-mailwolt-motd.sh <<'SH' -case "$-" in *i*) /usr/local/bin/mw-motd ;; esac -SH -fi -: > /etc/motd 2>/dev/null || true diff --git a/scripts/99-summary.sh b/scripts/99-summary.sh deleted file mode 100644 index f9bc136..0000000 --- a/scripts/99-summary.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source ./lib.sh - -ok(){ echo -e " [${GREEN}OK${NC}]"; } -fail(){ echo -e " [${RED}FAIL${NC}]"; } - -echo "[+] Quick-Healthchecks…" -printf " • MariaDB … " ; mysqladmin ping --silent >/dev/null 2>&1 && ok || fail -printf " • Redis … " ; if command -v redis-cli >/dev/null 2>&1; then - if [[ -n "${REDIS_PASS:-}" && "${REDIS_PASS}" != "null" ]]; then redis-cli -a "${REDIS_PASS}" ping 2>/dev/null | grep -q PONG && ok || fail - else redis-cli ping 2>/dev/null | grep -q PONG && ok || fail; fi -else fail; fi - -# PHP-FPM -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 "$s"; return; }; done - [[ -S "/run/php/php-fpm.sock" ]] && { echo "/run/php/php-fpm.sock"; return; } - echo "127.0.0.1:9000" -} -PHP_FPM_SOCK="$(detect_php_fpm_sock)" -printf " • PHP-FPM … " ; if [[ "$PHP_FPM_SOCK" == 127.0.0.1:9000 ]]; then ss -ltn | grep -q ":9000 " && ok || fail; else [[ -S "$PHP_FPM_SOCK" ]] && ok || fail; fi - -# App HTTP -printf " • App … " ; if command -v curl >/dev/null 2>&1; then - if [[ -f "/etc/ssl/ui/fullchain.pem" && -f "/etc/ssl/ui/privkey.pem" ]]; then curl -skI "https://127.0.0.1" >/dev/null 2>&1 && ok || fail; else curl -sI "http://127.0.0.1" >/dev/null 2>&1 && ok || fail; fi -else echo -e " ${GREY}(curl fehlt)${NC}"; fi - -check_port(){ local label="$1" cmd="$2"; printf " • %-5s … " "$label"; timeout 8s bash -lc "$cmd" >/dev/null 2>&1 && ok || fail; } -check_port "25" 'printf "QUIT\r\n" | nc -w 3 127.0.0.1 25' -check_port "465" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:465 -quiet -ign_eof' -check_port "587" 'printf "EHLO x\r\nSTARTTLS\r\nQUIT\r\n" | openssl s_client -starttls smtp -connect 127.0.0.1:587 -quiet -ign_eof' -check_port "110" 'printf "QUIT\r\n" | nc -w 3 127.0.0.1 110' -check_port "995" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:995 -quiet -ign_eof' -check_port "143" 'printf ". LOGOUT\r\n" | nc -w 3 127.0.0.1 143' -check_port "993" 'printf ". LOGOUT\r\n" | openssl s_client -connect 127.0.0.1:993 -quiet -ign_eof' - -# Abschluss -echo -e " -${GREEN}${BAR}${NC} -${GREEN} ✔ ${APP_NAME} Bootstrap abgeschlossen${NC} -${GREEN}${BAR}${NC} - Aufruf UI: ${CYAN}$( [[ -f /etc/ssl/ui/fullchain.pem ]] && echo https || echo http )://${SERVER_PUBLIC_IPV4}${NC} - UI Host bevorzugt: ${GREY}${UI_HOST}${NC} - Mail-Host: ${GREY}${MAIL_HOSTNAME}${NC} - Nginx Site: ${GREY}/etc/nginx/sites-available/${APP_USER}.conf${NC} - TLS (stabile Pfade): ${GREY}/etc/ssl/{ui,webmail,mail}/{fullchain.pem,privkey.pem}${NC} -${GREEN}${BAR}${NC} -" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh deleted file mode 100644 index e8f7911..0000000 --- a/scripts/bootstrap.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "$0")" -source ./lib.sh - -require_root -header - -# ── Pflicht: Mailserver-FQDN ────────────────────────────────────────────── -MAIL_FQDN="${MAIL_FQDN:-}" -if [[ -z "${MAIL_FQDN}" ]]; then - read -r -p "Mailserver FQDN (z.B. mx.example.com): " MAIL_FQDN -fi -[[ "$MAIL_FQDN" == *.* ]] || die "MAIL_FQDN (z.B. mx.example.com) ist Pflicht." - -# Ableitungen -MTA_SUB="${MAIL_FQDN%%.*}" -BASE_DOMAIN="${MAIL_FQDN#*.}" -UI_SUB="${UI_SUB:-ui}" -WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}" -SYSTEM_SUB="${SYSTEM_SUB:-system}" # kommt aus Seeder (App), hier nur Derivate - -export APP_NAME="${APP_NAME:-MailWolt}" -export APP_ENV="${APP_ENV:-production}" -export APP_DEBUG="${APP_DEBUG:-false}" - -export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB SYSTEM_SUB -export UI_HOST="${UI_SUB}.${BASE_DOMAIN}" -export WEBMAIL_HOST="${WEBMAIL_SUB}.${BASE_DOMAIN}" -export MAIL_HOSTNAME="${MAIL_FQDN}" -export SYSTEM_HOSTNAME="${SYSTEM_SUB}.${BASE_DOMAIN}" - -export SERVER_PUBLIC_IPV4="$(detect_ip)" -export SERVER_PUBLIC_IPV6="$(detect_ipv6)" - -# Zeitzone/Locale heuristisch (kannst du später per UI anpassen) -export APP_TZ="$(detect_timezone)" -export APP_LOCALE="$(guess_locale_from_tz "$APP_TZ")" - -# LE-Kontakt -export LE_EMAIL="${LE_EMAIL:-admin@${BASE_DOMAIN}}" - -# Repo/Branch (für 80-app.sh) -export GIT_REPO="${GIT_REPO:-https://git.nexlab.at/boban/mailwolt.git}" -export GIT_BRANCH="${GIT_BRANCH:-main}" -export APP_USER="${APP_USER:-mailwolt}" -export APP_GROUP="${APP_GROUP:-www-data}" -export APP_DIR="/var/www/${APP_USER}" -export APP_USER_PREFIX="${APP_USER_PREFIX:-mw}" - -# Run modules -./10-provision.sh -./20-ssl.sh -./30-db.sh -./40-postfix.sh -./50-dovecot.sh -./60-rspamd-opendkim.sh -./70-nginx.sh -./80-app.sh -./90-services.sh -./95-monit.sh -./98-motd.sh -./99-summary.sh diff --git a/scripts/lib.sh b/scripts/lib.sh deleted file mode 100644 index 6be3443..0000000 --- a/scripts/lib.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ── Styling ──────────────────────────────────────────────────────────────── -GREEN="$(printf '\033[1;32m')"; YELLOW="$(printf '\033[1;33m')" -RED="$(printf '\033[1;31m')"; CYAN="$(printf '\033[1;36m')" -GREY="$(printf '\033[0;90m')"; NC="$(printf '\033[0m')" -BAR="──────────────────────────────────────────────────────────────────────────────" - -header(){ echo -e "${CYAN}${BAR}${NC} -${CYAN} 888b d888 d8b 888 888 888 888 888 ${NC} -${CYAN} 8888b d8888 Y8P 888 888 o 888 888 888 ${NC} -${CYAN} 88888b.d88888 888 888 d8b 888 888 888 ${NC} -${CYAN} 888Y88888P888 8888b. 888 888 888 d888b 888 .d88b. 888 888888 ${NC} -${CYAN} 888 Y888P 888 '88b 888 888 888d88888b888 d88''88b 888 888 ${NC} -${CYAN} 888 Y8P 888 .d888888 888 888 88888P Y88888 888 888 888 888 ${NC} -${CYAN} 888 ' 888 888 888 888 888 8888P Y8888 Y88..88P 888 Y88b. ${NC} -${CYAN} 888 888 'Y888888 888 888 888P Y888 'Y88P' 888 'Y888 ${NC} -${CYAN}${BAR}${NC}\n"; } - -log(){ echo -e "${GREEN}[+]${NC} $*"; } -warn(){ echo -e "${YELLOW}[!]${NC} $*"; } -err(){ echo -e "${RED}[x]${NC} $*"; } -die(){ err "$*"; exit 1; } -require_root(){ [[ "$(id -u)" -eq 0 ]] || die "Bitte als root ausführen."; } - -detect_ip(){ - local ip - ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')" || true - [[ -n "${ip:-}" ]] || ip="$(hostname -I 2>/dev/null | awk '{print $1}')" - [[ -n "${ip:-}" ]] || die "Konnte Server-IP nicht ermitteln." - echo "$ip" -} - -detect_ipv6(){ - local ip6 - ip6="$(ip -6 addr show scope global 2>/dev/null | awk '/inet6/{print $2}' | cut -d/ -f1 | head -n1)" || true - [[ -n "${ip6:-}" ]] || ip6="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i ~ /:/){print $i; exit}}')" || true - echo "${ip6:-}" -} - -detect_timezone(){ - if command -v timedatectl >/dev/null 2>&1; then - tz="$(timedatectl show -p Timezone --value 2>/dev/null | tr -d '[:space:]')" || true - [[ -n "${tz:-}" && "$tz" == */* ]] && { echo "$tz"; return; } - fi - if [[ -r /etc/timezone ]]; then - tz="$(sed -n '1p' /etc/timezone | tr -d '[:space:]')" || true - [[ -n "${tz:-}" && "$tz" == */* ]] && { echo "$tz"; return; } - fi - if [[ -L /etc/localtime ]]; then - target="$(readlink -f /etc/localtime 2>/dev/null || true)" - target="${target#/usr/share/zoneinfo/}" - [[ "$target" == */* ]] && { echo "$target"; return; } - fi - if command -v curl >/dev/null 2>&1; then - tz="$(curl -fsSL --max-time 3 https://ipapi.co/timezone 2>/dev/null || true)" - [[ -n "${tz:-}" && "$tz" == */* ]] && { echo "$tz"; return; } - fi - echo "UTC" -} - -guess_locale_from_tz(){ - local tz="${1:-UTC}" - case "$tz" in - Europe/Berlin|Europe/Vienna|Europe/Zurich|Europe/Luxembourg|Europe/Brussels|Europe/Amsterdam) echo "de";; - *) echo "en";; - esac -} - -resolve_ok(){ - local host="$1" ip="${SERVER_PUBLIC_IPV4:-}" - [[ -n "$ip" ]] || return 1 - getent ahosts "$host" | awk '{print $1}' | sort -u | grep -q -F "$ip" -} - -ensure_dir(){ install -d -m "${3:-0755}" -o "${1:-root}" -g "${2:-root}" "${4}"; }