Compare commits
No commits in common. "main" and "v1.1.163" have entirely different histories.
|
|
@ -33,7 +33,7 @@ SESSION_ENCRYPT=false
|
|||
SESSION_PATH=/
|
||||
# For cross-subdomain session sharing (e.g. webmail on mail.example.com):
|
||||
# SESSION_DOMAIN=.example.com
|
||||
SESSION_DOMAIN=
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
#BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/backups
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class WizardDomains extends Command
|
|||
// Das Script erstellt erst die Vhosts (mit ACME-Location), dann certbot --webroot
|
||||
$helper = '/usr/local/sbin/mailwolt-apply-domains';
|
||||
$out = shell_exec(sprintf(
|
||||
'sudo -n %s --ui-host %s --webmail-host %s --mail-host %s --ssl-auto %d',
|
||||
'sudo -n %s --ui-host %s --webmail-host %s --mail-host %s --ssl-auto %d 2>&1',
|
||||
escapeshellarg($helper),
|
||||
escapeshellarg($ui),
|
||||
escapeshellarg($webmail),
|
||||
|
|
@ -67,34 +67,27 @@ class WizardDomains extends Command
|
|||
$ssl ? 1 : 0,
|
||||
));
|
||||
|
||||
// Shell-Script schreibt per-Domain-Status selbst in die State-Dateien.
|
||||
// Fallback: Domains die noch auf running/pending stehen auf error setzen.
|
||||
$helperOk = $out !== null && !str_contains((string) $out, '[x]');
|
||||
$outStr = (string) $out;
|
||||
|
||||
foreach (['ui', 'mail', 'webmail'] as $key) {
|
||||
$status = trim((string) @file_get_contents(self::STATE_DIR . "/{$key}"));
|
||||
$status = file_get_contents(self::STATE_DIR . "/{$key}");
|
||||
if ($status === 'running' || $status === 'pending') {
|
||||
file_put_contents(self::STATE_DIR . "/{$key}", 'error');
|
||||
$domain = $domains[$key] ?? '';
|
||||
if ($domain && str_contains($outStr, "[!] {$domain}:")) {
|
||||
file_put_contents(self::STATE_DIR . "/{$key}", 'noipv6');
|
||||
} else {
|
||||
file_put_contents(self::STATE_DIR . "/{$key}", $helperOk ? 'done' : 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// done-Datei: Shell-Script schreibt "1"/"0"; Fallback wenn Script abstürzte.
|
||||
$doneVal = trim((string) @file_get_contents(self::STATE_DIR . '/done'));
|
||||
if ($doneVal === '') {
|
||||
file_put_contents(self::STATE_DIR . '/done', '0');
|
||||
$doneVal = '0';
|
||||
}
|
||||
file_put_contents(self::STATE_DIR . '/done', $helperOk ? '1' : '0');
|
||||
Setting::set('ssl_configured', $helperOk ? '1' : '0');
|
||||
|
||||
// ssl_configured anhand tatsächlich ausgestellter LE-Zertifikate bestimmen
|
||||
$hasAnyCert = false;
|
||||
foreach ($domains as $domain) {
|
||||
if ($domain && is_dir("/etc/letsencrypt/live/{$domain}")) {
|
||||
$hasAnyCert = true;
|
||||
break;
|
||||
if ($helperOk && $ssl) {
|
||||
$this->updateEnv(base_path('.env'), 'SESSION_SECURE_COOKIE', 'true');
|
||||
}
|
||||
}
|
||||
Setting::set('ssl_configured', $hasAnyCert ? '1' : '0');
|
||||
|
||||
// SESSION_SECURE_COOKIE wird nicht automatisch gesetzt —
|
||||
// nginx leitet HTTP→HTTPS weiter, Secure-Flag wird im Admin gesetzt
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,8 +169,6 @@ class Wizard extends Component
|
|||
|
||||
public function pollSetup(): void
|
||||
{
|
||||
if ($this->setupDone) return;
|
||||
|
||||
foreach (['ui', 'mail', 'webmail'] as $key) {
|
||||
$file = self::STATE_DIR . "/{$key}";
|
||||
$this->domainStatus[$key] = is_readable($file)
|
||||
|
|
|
|||
135
installer.sh
135
installer.sh
|
|
@ -76,9 +76,10 @@ footer_ok() {
|
|||
echo -e ""
|
||||
echo -e " ${CYAN}➜ Setup-Wizard jetzt öffnen:${NC}"
|
||||
echo -e " ${CYAN}http://${ip}/setup${NC}"
|
||||
echo -e " ${GREY}https://${ip}/setup${NC} (self-signed Zertifikat)"
|
||||
echo -e ""
|
||||
echo -e " Laravel Root: ${GREY}${app_dir}${NC}"
|
||||
echo -e " Mail-TLS Cert: ${GREY}${cert_dir}/{cert.pem,key.pem}${NC} (Postfix/Dovecot)"
|
||||
echo -e " Self-signed Cert: ${GREY}${cert_dir}/{cert.pem,key.pem}${NC}"
|
||||
echo -e " Postfix/Dovecot: ${GREY}25, 465, 587, 110, 995, 143, 993${NC}"
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo
|
||||
|
|
@ -452,10 +453,37 @@ server {
|
|||
access_log /var/log/nginx/${APP_USER}_access.log;
|
||||
error_log /var/log/nginx/${APP_USER}_error.log;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
try_files \$uri =404;
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.php?\$query_string;
|
||||
}
|
||||
location ~ \.php\$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:${PHP_FPM_SOCK};
|
||||
}
|
||||
location ^~ /livewire/ {
|
||||
try_files \$uri /index.php?\$query_string;
|
||||
}
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ {
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate ${CERT};
|
||||
ssl_certificate_key ${KEY};
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
|
||||
access_log /var/log/nginx/${APP_USER}_ssl_access.log;
|
||||
error_log /var/log/nginx/${APP_USER}_ssl_error.log;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.php?\$query_string;
|
||||
}
|
||||
|
|
@ -672,16 +700,8 @@ if [ "${SSL_AUTO}" = "1" ]; then
|
|||
done
|
||||
fi
|
||||
|
||||
# --- Phase 3: Finale Vhosts ---
|
||||
# Nur HTTPS wenn LE-Cert tatsächlich vorhanden, sonst HTTP-only (kein self-signed Fallback)
|
||||
UI_HAS_CERT=0
|
||||
WM_HAS_CERT=0
|
||||
[ -f "/etc/letsencrypt/live/${UI_HOST}/fullchain.pem" ] && UI_HAS_CERT=1
|
||||
[ -f "/etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem" ] && WM_HAS_CERT=1
|
||||
# --- Phase 3: Finale Vhosts (LE-Cert oder self-signed Fallback) ---
|
||||
(
|
||||
|
||||
if [ "${UI_HAS_CERT}" = "1" ] || [ "${WM_HAS_CERT}" = "1" ]; then
|
||||
# Mindestens ein Cert vorhanden → HTTP-Redirect Block
|
||||
cat <<CONF
|
||||
server {
|
||||
listen 80;
|
||||
|
|
@ -695,105 +715,74 @@ server {
|
|||
location / { return 301 https://\$host\$request_uri; }
|
||||
}
|
||||
CONF
|
||||
|
||||
if [ -n "${UI_HOST}" ]; then
|
||||
if [ "${SSL_AUTO}" = "1" ] && [ -f "/etc/letsencrypt/live/${UI_HOST}/fullchain.pem" ]; then
|
||||
CERT_UI="/etc/letsencrypt/live/${UI_HOST}/fullchain.pem"
|
||||
KEY_UI="/etc/letsencrypt/live/${UI_HOST}/privkey.pem"
|
||||
else
|
||||
# Kein Cert → HTTP-only, App läuft auf Port 80 weiter
|
||||
cat <<CONF
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root ${ACME_ROOT};
|
||||
try_files \$uri =404;
|
||||
}
|
||||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||||
location ~ \.php\$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:${PHP_FPM_SOCK};
|
||||
}
|
||||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||||
}
|
||||
CONF
|
||||
CERT_UI="/etc/mailwolt/ssl/cert.pem"
|
||||
KEY_UI="/etc/mailwolt/ssl/key.pem"
|
||||
fi
|
||||
|
||||
if [ -n "${UI_HOST}" ] && [ "${UI_HAS_CERT}" = "1" ]; then
|
||||
cat <<CONF
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name ${UI_HOST};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${UI_HOST}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${UI_HOST}/privkey.pem;
|
||||
ssl_certificate ${CERT_UI};
|
||||
ssl_certificate_key ${KEY_UI};
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
|
||||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||||
location ~ \.php\$ {
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_param HTTPS on;
|
||||
fastcgi_pass unix:${PHP_FPM_SOCK};
|
||||
}
|
||||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)$ { expires 30d; access_log off; }
|
||||
}
|
||||
CONF
|
||||
fi
|
||||
|
||||
if [ -n "${WEBMAIL_HOST}" ] && [ "${WM_HAS_CERT}" = "1" ]; then
|
||||
if [ -n "${WEBMAIL_HOST}" ]; then
|
||||
if [ "${SSL_AUTO}" = "1" ] && [ -f "/etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem" ]; then
|
||||
CERT_WM="/etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem"
|
||||
KEY_WM="/etc/letsencrypt/live/${WEBMAIL_HOST}/privkey.pem"
|
||||
else
|
||||
CERT_WM="/etc/mailwolt/ssl/cert.pem"
|
||||
KEY_WM="/etc/mailwolt/ssl/key.pem"
|
||||
fi
|
||||
cat <<CONF
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name ${WEBMAIL_HOST};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${WEBMAIL_HOST}/privkey.pem;
|
||||
ssl_certificate ${CERT_WM};
|
||||
ssl_certificate_key ${KEY_WM};
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
|
||||
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
|
||||
location ~ \.php\$ {
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_param HTTPS on;
|
||||
fastcgi_pass unix:${PHP_FPM_SOCK};
|
||||
}
|
||||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)$ { expires 30d; access_log off; }
|
||||
}
|
||||
CONF
|
||||
fi
|
||||
) > "${NGINX_SITE}"
|
||||
|
||||
# State-Dateien VOR dem nginx-Switch schreiben:
|
||||
# Browser-Poll (alle 2s) liest done=1 → Polling stoppt → "Zum Login" erscheint.
|
||||
# Danach 6s sleep → nginx switchet auf HTTPS → User klickt Link → funktioniert.
|
||||
STATE_DIR="/var/lib/mailwolt/wizard"
|
||||
if [ -d "${STATE_DIR}" ]; then
|
||||
for k in ui mail webmail; do
|
||||
[ -f "${STATE_DIR}/${k}" ] && printf "done" > "${STATE_DIR}/${k}"
|
||||
done
|
||||
if [ "${UI_HAS_CERT}" = "1" ] || [ "${WM_HAS_CERT}" = "1" ]; then
|
||||
printf "1" > "${STATE_DIR}/done"
|
||||
else
|
||||
printf "0" > "${STATE_DIR}/done"
|
||||
fi
|
||||
sleep 6
|
||||
fi
|
||||
|
||||
nginx -t && systemctl reload nginx
|
||||
HELPER
|
||||
chmod 755 /usr/local/sbin/mailwolt-apply-domains
|
||||
|
|
@ -805,7 +794,6 @@ install -m 755 "${APP_DIR}/update.sh" /usr/local/sbin/mailwolt-update
|
|||
cat > /etc/sudoers.d/mailwolt-certbot <<'SUDOERS'
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-apply-domains
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update
|
||||
www-data ALL=(root) NOPASSWD: /usr/bin/certbot
|
||||
SUDOERS
|
||||
chmod 440 /etc/sudoers.d/mailwolt-certbot
|
||||
|
||||
|
|
@ -881,7 +869,7 @@ check process dovecot with pidfile /run/dovecot/master.pid
|
|||
if failed host 127.0.0.1 port 993 type tcpssl for 3 cycles then restart
|
||||
if 5 restarts within 10 cycles then alert
|
||||
|
||||
check process mariadb matching "mysqld"
|
||||
check process mariadb with pidfile /run/mysqld/mysqld.pid
|
||||
start program = "/bin/systemctl start mariadb"
|
||||
stop program = "/bin/systemctl stop mariadb"
|
||||
if failed host 127.0.0.1 port 3306 type tcp for 2 cycles then restart
|
||||
|
|
@ -915,6 +903,7 @@ check process nginx with pidfile /run/nginx.pid
|
|||
start program = "/bin/systemctl start nginx"
|
||||
stop program = "/bin/systemctl stop nginx"
|
||||
if failed host 127.0.0.1 port 80 type tcp for 2 cycles then restart
|
||||
if failed host 127.0.0.1 port 443 type tcpssl for 2 cycles then restart
|
||||
if 5 restarts within 10 cycles then alert
|
||||
|
||||
check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AgentMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AskMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Ask2AgentMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EditMigrationStateService">
|
||||
<option name="migrationStatus" value="COMPLETED" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/mailwolt-installer.iml" filepath="$PROJECT_DIR$/.idea/mailwolt-installer.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# ===================== HTTP (Port 80) =====================
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
# ACME HTTP-01
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
allow all;
|
||||
}
|
||||
|
||||
__HTTP_BODY__
|
||||
}
|
||||
|
||||
# ===================== HTTPS (Port 443) ====================
|
||||
__SSL_SERVER_BLOCK__
|
||||
|
|
@ -0,0 +1,909 @@
|
|||
#!/usr/bin/env bash
|
||||
(
|
||||
export HISTFILE=
|
||||
set +o history
|
||||
set -euo pipefail
|
||||
|
||||
##############################################
|
||||
# MailWolt #
|
||||
# Bootstrap Installer v1.0 #
|
||||
##############################################
|
||||
|
||||
# ===== CLI-Flags (-dev / -stag) =====
|
||||
APP_ENV="${APP_ENV:-production}"
|
||||
APP_DEBUG="${APP_DEBUG:-false}"
|
||||
|
||||
DEV_MODE=0
|
||||
STAG_MODE=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-dev)
|
||||
DEV_MODE=1
|
||||
APP_ENV="local"
|
||||
APP_DEBUG="true"
|
||||
;;
|
||||
-stag|-staging)
|
||||
STAG_MODE=1
|
||||
APP_ENV="staging"
|
||||
APP_DEBUG="false"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# ===== Branding & Pfade =====
|
||||
APP_NAME="${APP_NAME:-MailWolt}"
|
||||
|
||||
APP_USER="${APP_USER:-mailwolt}"
|
||||
APP_GROUP="${APP_GROUP:-www-data}"
|
||||
|
||||
APP_DIR="/var/www/${APP_USER}"
|
||||
|
||||
ADMIN_USER="${APP_USER}"
|
||||
ADMIN_EMAIL="admin@localhost"
|
||||
ADMIN_PASS="ChangeMe"
|
||||
|
||||
CONF_BASE="/etc/${APP_USER}"
|
||||
CERT_DIR="${CONF_BASE}/ssl"
|
||||
CERT="${CERT_DIR}/cert.pem"
|
||||
KEY="${CERT_DIR}/key.pem"
|
||||
|
||||
NGINX_SITE="/etc/nginx/sites-available/${APP_USER}.conf"
|
||||
NGINX_SITE_LINK="/etc/nginx/sites-enabled/${APP_USER}.conf"
|
||||
|
||||
DB_NAME="${DB_NAME:-${APP_USER}}"
|
||||
DB_USER="${DB_USER:-${APP_USER}}"
|
||||
DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
|
||||
|
||||
GIT_REPO="${GIT_REPO:-http://10.10.20.81:3000/boban/mailwolt.git}"
|
||||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||||
|
||||
NODE_SETUP="${NODE_SETUP:-deb}"
|
||||
|
||||
# ===== Styling =====
|
||||
GREEN="\033[1;32m"; YELLOW="\033[1;33m"; RED="\033[1;31m"; CYAN="\033[1;36m"; GREY="\033[0;90m"; NC="\033[0m"
|
||||
BAR="──────────────────────────────────────────────────────────────────────────────"
|
||||
|
||||
header() {
|
||||
echo -e "${CYAN}${BAR}${NC}"
|
||||
echo -e "${CYAN} 888b d888 d8b 888 888 888 888 888 ${NC}"
|
||||
echo -e "${CYAN} 8888b d8888 Y8P 888 888 o 888 888 888 ${NC}"
|
||||
echo -e "${CYAN} 88888b.d88888 888 888 d8b 888 888 888 ${NC}"
|
||||
echo -e "${CYAN} 888Y88888P888 8888b. 888 888 888 d888b 888 .d88b. 888 888888 ${NC}"
|
||||
echo -e "${CYAN} 888 Y888P 888 '88b 888 888 888d88888b888 d88''88b 888 888 ${NC}"
|
||||
echo -e "${CYAN} 888 Y8P 888 .d888888 888 888 88888P Y88888 888 888 888 888 ${NC}"
|
||||
echo -e "${CYAN} 888 ' 888 888 888 888 888 8888P Y8888 Y88..88P 888 Y88b. ${NC}"
|
||||
echo -e "${CYAN} 888 888 'Y888888 888 888 888P Y888 'Y88P' 888 'Y888 ${NC}"
|
||||
echo -e "${CYAN}${BAR}${NC}"
|
||||
echo
|
||||
}
|
||||
|
||||
print_bootstrap_summary() {
|
||||
local ip="$1"
|
||||
local admin_user="$2"
|
||||
local admin_pass="$3"
|
||||
local GREEN="\033[1;32m"
|
||||
local CYAN="\033[1;36m"
|
||||
local GREY="\033[0;90m"
|
||||
local YELLOW="\033[1;33m"
|
||||
local RED="\033[1;31m"
|
||||
local NC="\033[0m"
|
||||
local BAR="${BAR:-──────────────────────────────────────────────────────────────────────────────}"
|
||||
local scheme="http"
|
||||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then scheme="https"; fi
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo -e "${GREEN} ✔ ${APP_NAME} Bootstrap erfolgreich abgeschlossen${NC}"
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo -e " Bootstrap-Login (nur für ERSTEN Login & Wizard):"
|
||||
echo -e " User: ${YELLOW}${admin_user}${NC}"
|
||||
echo -e " Passwort: ${RED}${admin_pass}${NC}"
|
||||
echo
|
||||
echo -e " Aufruf: ${CYAN}${scheme}://${ip}${NC}"
|
||||
echo -e " Laravel Root: ${GREY}${APP_DIR}${NC}"
|
||||
echo -e " Nginx Site: ${GREY}${NGINX_SITE}${NC}"
|
||||
echo -e " Self-signed Cert: ${GREY}${CERT_DIR}/{cert.pem,key.pem}${NC}"
|
||||
echo -e " Postfix/Dovecot Ports aktiv: ${GREY}25, 465, 587, 110, 995, 143, 993${NC}"
|
||||
echo -e " Rspamd/OpenDKIM: ${GREY}aktiv (DKIM-Keys später im Wizard)${NC}"
|
||||
echo -e " Monit (Watchdog): ${GREY}installiert, NICHT aktiviert${NC}"
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo
|
||||
}
|
||||
|
||||
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||||
err() { echo -e "${RED}[x]${NC} $*"; }
|
||||
require_root() { [ "$(id -u)" -eq 0 ] || { err "Bitte als root ausführen."; exit 1; }; }
|
||||
|
||||
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:-}" ]] || { err "Konnte Server-IP nicht ermitteln."; exit 1; }
|
||||
echo "$ip"
|
||||
}
|
||||
|
||||
gen() { head -c 512 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c "${1:-28}" || true; }
|
||||
pw() { gen 28; }
|
||||
short() { gen 16; }
|
||||
|
||||
# ===== Start =====
|
||||
require_root
|
||||
header
|
||||
|
||||
SERVER_IP="$(detect_ip)"
|
||||
MAIL_HOSTNAME="${MAIL_HOSTNAME:-"bootstrap.local"}"
|
||||
TZ="${TZ:-""}"
|
||||
|
||||
echo -e "${GREY}Server-IP erkannt: ${SERVER_IP}${NC}"
|
||||
[ -n "$TZ" ] && { ln -fs "/usr/share/zoneinfo/${TZ}" /etc/localtime || true; }
|
||||
|
||||
log "Paketquellen aktualisieren…"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -y
|
||||
|
||||
# ---- MariaDB-Workaround ----
|
||||
log "MariaDB-Workaround vorbereiten…"
|
||||
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
|
||||
|
||||
# ---- Basis-Pakete installieren ----
|
||||
log "Pakete installieren… (dies kann einige Minuten dauern)"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
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
|
||||
|
||||
NGINX_HTTP2_SUPPORTED=0
|
||||
if nginx -V 2>&1 | grep -q http_v2; then
|
||||
NGINX_HTTP2_SUPPORTED=1
|
||||
log "Nginx: HTTP/2-Unterstützung vorhanden ✅"
|
||||
else
|
||||
warn "Nginx: HTTP/2-Modul nicht gefunden – wechsle auf 'nginx-full'…"
|
||||
apt-get install -y nginx-full || true
|
||||
systemctl restart nginx || true
|
||||
if nginx -V 2>&1 | grep -q http_v2; then
|
||||
NGINX_HTTP2_SUPPORTED=1
|
||||
log "Nginx: HTTP/2 jetzt verfügbar ✅"
|
||||
else
|
||||
warn "HTTP/2 weiterhin nicht verfügbar (verwende SSL ohne http2)."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$NGINX_HTTP2_SUPPORTED" = "1" ]; then
|
||||
NGINX_HTTP2_SUFFIX=" http2"
|
||||
else
|
||||
NGINX_HTTP2_SUFFIX=""
|
||||
fi
|
||||
|
||||
# ===== Verzeichnisse / User =====
|
||||
log "Verzeichnisse und Benutzer anlegen…"
|
||||
mkdir -p "${CERT_DIR}" /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"
|
||||
|
||||
# ===== Self-signed TLS (SAN = IP) =====
|
||||
OSSL_CFG="${CERT_DIR}/openssl.cnf"
|
||||
if [ ! -s "$CERT" ] || [ ! -s "$KEY" ]; then
|
||||
log "Erzeuge Self-Signed TLS Zertifikat (SAN=IP:${SERVER_IP})…"
|
||||
install -d -m 0750 -o root -g "${APP_USER}" "${CERT_DIR}"
|
||||
cat > "$OSSL_CFG" <<CFG
|
||||
[req]
|
||||
default_bits = 2048
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
req_extensions = req_ext
|
||||
distinguished_name = dn
|
||||
|
||||
[dn]
|
||||
CN = ${SERVER_IP}
|
||||
O = ${APP_NAME}
|
||||
C = DE
|
||||
|
||||
[req_ext]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
IP.1 = ${SERVER_IP}
|
||||
CFG
|
||||
openssl req -x509 -newkey rsa:2048 -days 825 -nodes \
|
||||
-keyout "$KEY" -out "$CERT" -config "$OSSL_CFG"
|
||||
chown root:"${APP_USER}" "$KEY" "$CERT"
|
||||
chmod 640 "$KEY" "$CERT"
|
||||
chmod 750 "${CERT_DIR}"
|
||||
fi
|
||||
|
||||
DEV_USER="${SUDO_USER:-$USER}"
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
setfacl -m u:${DEV_USER}:x "${CONF_BASE}" "${CERT_DIR}" || true
|
||||
setfacl -m u:${DEV_USER}:r "$CERT" "$KEY" || true
|
||||
fi
|
||||
|
||||
# ===== MariaDB =====
|
||||
log "MariaDB vorbereiten…"
|
||||
systemctl enable --now mariadb
|
||||
mysql -uroot <<SQL
|
||||
CREATE DATABASE IF NOT EXISTS ${DB_NAME}
|
||||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost';
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'127.0.0.1';
|
||||
ALTER USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
|
||||
ALTER USER '${DB_USER}'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
|
||||
# ===== Postfix =====
|
||||
postconf -e "myhostname = ${MAIL_HOSTNAME}"
|
||||
postconf -e "myorigin = \$myhostname"
|
||||
postconf -e "mydestination = "
|
||||
postconf -e "inet_interfaces = all"
|
||||
postconf -e "inet_protocols = ipv4"
|
||||
postconf -e "smtpd_banner = \$myhostname ESMTP"
|
||||
postconf -e "smtpd_tls_cert_file = ${CERT}"
|
||||
postconf -e "smtpd_tls_key_file = ${KEY}"
|
||||
postconf -e "smtpd_tls_security_level = may"
|
||||
postconf -e "smtp_tls_security_level = may"
|
||||
postconf -e "smtpd_tls_received_header = yes"
|
||||
postconf -e "disable_vrfy_command = yes"
|
||||
postconf -e "smtpd_helo_required = yes"
|
||||
postconf -e "milter_default_action = accept"
|
||||
postconf -e "milter_protocol = 6"
|
||||
postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
postconf -e "smtpd_sasl_type = dovecot"
|
||||
postconf -e "smtpd_sasl_path = private/auth"
|
||||
postconf -e "smtpd_sasl_auth_enable = yes"
|
||||
postconf -e "smtpd_sasl_security_options = noanonymous"
|
||||
postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"
|
||||
postconf -e "smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination"
|
||||
postconf -M "smtp/inet=smtp inet n - n - - smtpd -o smtpd_peername_lookup=no -o smtpd_timeout=30s"
|
||||
postconf -M "submission/inet=submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_peername_lookup=no -o smtpd_tls_security_level=encrypt -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||||
postconf -M "smtps/inet=smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_peername_lookup=no -o smtpd_tls_wrappermode=yes -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||||
postconf -M "pickup/unix=pickup unix n - y 60 1 pickup"
|
||||
postconf -M "cleanup/unix=cleanup unix n - y - 0 cleanup"
|
||||
postconf -M "qmgr/unix=qmgr unix n - n 300 1 qmgr"
|
||||
|
||||
install -d -o root -g postfix -m 750 /etc/postfix/sql
|
||||
install -o root -g postfix -m 640 /dev/null /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||||
cat > /etc/postfix/sql/mysql-virtual-mailbox-maps.cf <<CONF
|
||||
hosts = 127.0.0.1
|
||||
user = ${DB_USER}
|
||||
password = ${DB_PASS}
|
||||
dbname = ${DB_NAME}
|
||||
# query = SELECT 1 FROM mail_users u JOIN domains d ON d.id = u.domain_id WHERE u.email = '%s' AND u.is_active = 1 AND d.is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:postfix /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||||
chmod 640 /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||||
|
||||
install -o root -g postfix -m 640 /dev/null /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||||
cat > /etc/postfix/sql/mysql-virtual-alias-maps.cf <<CONF
|
||||
hosts = 127.0.0.1
|
||||
user = ${DB_USER}
|
||||
password = ${DB_PASS}
|
||||
dbname = ${DB_NAME}
|
||||
# query = SELECT destination FROM mail_aliases a JOIN domains d ON d.id = a.domain_id WHERE a.source = '%s' AND a.is_active = 1 AND d.is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:postfix /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||||
chmod 640 /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||||
|
||||
systemctl restart postfix
|
||||
systemctl enable --now postfix
|
||||
|
||||
# ===== Dovecot =====
|
||||
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 <<CONF
|
||||
driver = mysql
|
||||
connect = host=127.0.0.1 dbname=${DB_NAME} user=${DB_USER} password=${DB_PASS}
|
||||
default_pass_scheme = BLF-CRYPT
|
||||
# password_query = SELECT email AS user, password_hash AS password FROM mail_users WHERE email = '%u' AND is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:dovecot /etc/dovecot/dovecot-sql.conf.ext
|
||||
chmod 640 /etc/dovecot/dovecot-sql.conf.ext
|
||||
|
||||
cat > /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
|
||||
sudo chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext
|
||||
sudo 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
|
||||
|
||||
cat > /etc/dovecot/conf.d/10-ssl.conf <<CONF
|
||||
ssl = required
|
||||
ssl_cert = <${CERT}
|
||||
ssl_key = <${KEY}
|
||||
CONF
|
||||
|
||||
sudo mkdir -p /var/spool/postfix/private
|
||||
sudo chown postfix:postfix /var/spool/postfix /var/spool/postfix/private
|
||||
sudo chmod 0755 /var/spool/postfix /var/spool/postfix/private
|
||||
|
||||
sudo systemctl restart dovecot
|
||||
sudo systemctl restart postfix
|
||||
systemctl enable --now dovecot
|
||||
|
||||
# ===== Rspamd & OpenDKIM =====
|
||||
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
|
||||
# KeyTable / SigningTable werden nach dem Wizard gesetzt
|
||||
CONF
|
||||
systemctl enable --now opendkim || true
|
||||
|
||||
# ===== Redis =====
|
||||
log "Redis absichern (Passwort setzen & nur localhost)…"
|
||||
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
|
||||
|
||||
# ===== Nginx =====
|
||||
log "Nginx konfigurieren…"
|
||||
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default || true
|
||||
|
||||
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)"
|
||||
install -d -m 0755 /var/www/letsencrypt
|
||||
|
||||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then
|
||||
cat > "${NGINX_SITE}" <<CONF
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
location ^~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; allow all; }
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl${NGINX_HTTP2_SUFFIX};
|
||||
listen [::]:443 ssl${NGINX_HTTP2_SUFFIX};
|
||||
server_name _;
|
||||
ssl_certificate ${CERT};
|
||||
ssl_certificate_key ${KEY};
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
access_log /var/log/nginx/${APP_USER}_ssl_access.log;
|
||||
error_log /var/log/nginx/${APP_USER}_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 unix:${PHP_FPM_SOCK}; }
|
||||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
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;
|
||||
}
|
||||
}
|
||||
CONF
|
||||
else
|
||||
cat > "${NGINX_SITE}" <<CONF
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
root ${APP_DIR}/public;
|
||||
index index.php index.html;
|
||||
access_log /var/log/nginx/${APP_USER}_access.log;
|
||||
error_log /var/log/nginx/${APP_USER}_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 unix:${PHP_FPM_SOCK}; }
|
||||
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
|
||||
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg)\$ { expires 30d; access_log off; }
|
||||
}
|
||||
CONF
|
||||
fi
|
||||
|
||||
ln -sf "${NGINX_SITE}" "${NGINX_SITE_LINK}"
|
||||
if nginx -t; then
|
||||
systemctl enable --now nginx
|
||||
systemctl reload nginx || true
|
||||
else
|
||||
echo "[x] Nginx-Konfiguration fehlerhaft – bitte /var/log/nginx/* prüfen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ===== Laravel Projekt =====
|
||||
log "Laravel bereitstellen…"
|
||||
mkdir -p "$(dirname "$APP_DIR")"
|
||||
chown -R "$APP_USER":"$APP_GROUP" "$(dirname "$APP_DIR")"
|
||||
|
||||
log "Git Repo vorbereiten…"
|
||||
if [ "${GIT_REPO}" = "https://example.com/your-repo-placeholder.git" ]; then
|
||||
if [ ! -d "${APP_DIR}" ] || [ -z "$(ls -A "$APP_DIR" 2>/dev/null || true)" ]; then
|
||||
sudo -u "$APP_USER" -H bash -lc "cd /var/www && COMPOSER_ALLOW_SUPERUSER=0 composer create-project laravel/laravel ${APP_USER} --no-interaction"
|
||||
fi
|
||||
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 "
|
||||
set -e
|
||||
cd ${APP_DIR}
|
||||
git checkout ${GIT_BRANCH} 2>/dev/null || git checkout -B ${GIT_BRANCH}
|
||||
git fetch --depth=1 origin ${GIT_BRANCH}
|
||||
if git merge-base --is-ancestor HEAD origin/${GIT_BRANCH}; then
|
||||
git pull --ff-only
|
||||
else
|
||||
echo '[i] Non-fast-forward erkannt – setze hart auf origin/${GIT_BRANCH}.' >&2
|
||||
git reset --hard origin/${GIT_BRANCH}
|
||||
git clean -fd
|
||||
fi
|
||||
"
|
||||
fi
|
||||
if [ -f "${APP_DIR}/composer.json" ]; then
|
||||
if [ "${DEV_MODE}" = "1" ]; then
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist"
|
||||
else
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --no-dev"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ===== Node / Frontend =====
|
||||
if [ -f "${APP_DIR}/package.json" ]; then
|
||||
log "Node/NPM installieren…"
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
NODE_MAJ=$(node -v | sed 's/^v//' | cut -d. -f1)
|
||||
NODE_MIN=$(node -v | sed 's/^v//' | cut -d. -f2)
|
||||
if [ "$NODE_MAJ" -lt 20 ] || { [ "$NODE_MAJ" -eq 20 ] && [ "$NODE_MIN" -lt 19 ]; }; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs
|
||||
fi
|
||||
else
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
# .env anlegen & APP_KEY
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && cp -n .env.example .env || true"
|
||||
if ! grep -q '^APP_KEY=' "${APP_DIR}/.env"; then echo "APP_KEY=" >> "${APP_DIR}/.env"; fi
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan key:generate --force || true"
|
||||
fi
|
||||
|
||||
# ===== .env füllen =====
|
||||
ENV_FILE="${APP_DIR}/.env"
|
||||
upsert_env () {
|
||||
local key="$1" val="$2"
|
||||
local esc_key esc_val
|
||||
esc_key="$(printf '%s' "$key" | sed -e 's/[.[\*^$(){}+?|/]/\\&/g')"
|
||||
esc_val="$(printf '%s' "$val" | sed -e 's/[&/]/\\&/g')"
|
||||
if grep -qE "^[#[:space:]]*${esc_key}=" "$ENV_FILE"; then
|
||||
sed -Ei "s|^[#[:space:]]*${esc_key}=.*|${key}=${esc_val}|g" "$ENV_FILE"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; then
|
||||
upsert_env APP_URL "\"https://\${APP_HOST}\""
|
||||
else
|
||||
upsert_env APP_URL "\"http://\${APP_HOST}\""
|
||||
fi
|
||||
|
||||
upsert_env APP_HOST "${SERVER_IP}"
|
||||
upsert_env APP_ADMIN_USER "${ADMIN_USER}"
|
||||
upsert_env APP_ADMIN_EMAIL "${ADMIN_EMAIL}"
|
||||
upsert_env APP_ADMIN_PASS "${ADMIN_PASS}"
|
||||
upsert_env APP_NAME "${APP_NAME}"
|
||||
upsert_env APP_ENV "${APP_ENV}"
|
||||
upsert_env APP_DEBUG "${APP_DEBUG}"
|
||||
|
||||
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}"
|
||||
|
||||
# -------- WICHTIG: Cache-Store auf REDIS setzen (verhindert DB-Tabellenfehler) --------
|
||||
upsert_env CACHE_SETTINGS_STORE "redis"
|
||||
upsert_env CACHE_STORE "redis" # <- HIER geändert (vorher: database)
|
||||
upsert_env CACHE_DRIVER "redis"
|
||||
upsert_env CACHE_PREFIX "${APP_USER}_cache"
|
||||
|
||||
upsert_env SESSION_DRIVER "redis"
|
||||
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 / Vite
|
||||
upsert_env BROADCAST_DRIVER "reverb"
|
||||
upsert_env QUEUE_CONNECTION "redis"
|
||||
upsert_env REVERB_APP_ID "${APP_USER}"
|
||||
upsert_env REVERB_APP_KEY "${APP_USER}-yhp47tbt1aebhr1fgvgj"
|
||||
upsert_env REVERB_APP_SECRET "${APP_USER}-ulrdt9agwzkqwqsunbnb"
|
||||
upsert_env REVERB_HOST "127.0.0.1"
|
||||
upsert_env REVERB_PORT "443"
|
||||
upsert_env REVERB_SCHEME "https"
|
||||
upsert_env REVERB_PATH "/ws"
|
||||
upsert_env VITE_REVERB_APP_KEY "\${REVERB_APP_KEY}"
|
||||
upsert_env VITE_REVERB_PORT "\${REVERB_PORT}"
|
||||
upsert_env VITE_REVERB_SCHEME "\${REVERB_SCHEME}"
|
||||
upsert_env VITE_REVERB_PATH "\${REVERB_PATH}"
|
||||
|
||||
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=${APP_HOST}
|
||||
VITE_DEV_ORIGIN=${APP_URL}
|
||||
# --- /MailWolt DEV ---
|
||||
CONF
|
||||
cat > "${APP_DIR}/vite.config.js" <<'JS'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import laravel from 'laravel-vite-plugin'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
export default ({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const host = env.VITE_DEV_HOST || '127.0.0.1'
|
||||
const port = Number(env.VITE_DEV_PORT || 5173)
|
||||
const origin = env.VITE_DEV_ORIGIN || env.APP_URL || 'https://localhost'
|
||||
const hmrHost = env.VITE_HMR_HOST || (new URL(origin)).hostname
|
||||
return defineConfig({
|
||||
plugins: [laravel({ input: ['resources/css/app.css','resources/js/app.js'], refresh: true }), tailwindcss()],
|
||||
server: { host, port, https:false, strictPort:true,
|
||||
hmr:{ protocol: env.VITE_HMR_PROTOCOL || 'wss', host: hmrHost, clientPort:Number(env.VITE_HMR_CLIENT_PORT||443) },
|
||||
origin
|
||||
}
|
||||
})
|
||||
}
|
||||
JS
|
||||
chown "${APP_USER}:${APP_GROUP}" "${APP_DIR}/vite.config.js"
|
||||
fi
|
||||
|
||||
# ===== Frontend Build =====
|
||||
if [ -f "${APP_DIR}/package.json" ]; then
|
||||
log "Frontend Build…"
|
||||
if [ -f "${APP_DIR}/package-lock.json" ] || [ -f "${APP_DIR}/npm-shrinkwrap.json" ]; then
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm ci --no-audit --no-fund || npm install"
|
||||
else
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm install"
|
||||
fi
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && npm run build || true"
|
||||
rm -f ${APP_DIR}/bootstrap/cache/*.php
|
||||
fi
|
||||
|
||||
# ===== Rechte / PHP-FPM =====
|
||||
APP_PW="${APP_PW:-changeme}"
|
||||
if ! id -u "$APP_USER" >/dev/null 2>&1; then
|
||||
adduser --disabled-password --gecos "" "$APP_USER"
|
||||
echo "${APP_USER}:${APP_PW}" | chpasswd
|
||||
fi
|
||||
usermod -a -G "$APP_GROUP" "$APP_USER"
|
||||
|
||||
# Sichert, dass alle nötigen Ordner existieren (idempotent)
|
||||
install -d -m 0775 "${APP_DIR}/storage" \
|
||||
"${APP_DIR}/storage/framework" \
|
||||
"${APP_DIR}/storage/framework/cache" \
|
||||
"${APP_DIR}/storage/framework/cache/data" \
|
||||
"${APP_DIR}/storage/framework/sessions" \
|
||||
"${APP_DIR}/storage/framework/views" \
|
||||
"${APP_DIR}/bootstrap/cache"
|
||||
|
||||
# Besitz & Rechte
|
||||
chown -R "$APP_USER":"$APP_GROUP" "$APP_DIR"
|
||||
find "$APP_DIR" -type d -exec chmod 775 {} \;
|
||||
find "$APP_DIR" -type f -exec chmod 664 {} \;
|
||||
|
||||
[ -f "$APP_DIR/artisan" ] && chmod 755 "$APP_DIR/artisan"
|
||||
[ -d "$APP_DIR/vendor/bin" ] && chmod -R 755 "$APP_DIR/vendor/bin"
|
||||
[ -d "$APP_DIR/node_modules/.bin" ] && chmod -R 755 "$APP_DIR/node_modules/.bin"
|
||||
[ -f "$APP_DIR/node_modules/vite/bin/vite.js" ] && chmod 755 "$APP_DIR/node_modules/vite/bin/vite.js"
|
||||
find "$APP_DIR" -type f -name "*.sh" -exec chmod 755 {} \;
|
||||
|
||||
chmod -R 775 "$APP_DIR"/storage "$APP_DIR"/bootstrap/cache
|
||||
|
||||
if command -v setfacl >/dev/null 2>&1; then
|
||||
setfacl -R -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||||
setfacl -dR -m u:${APP_USER}:rwX,g:${APP_GROUP}:rwX "${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||||
fi
|
||||
|
||||
grep -q 'umask 002' /home/${APP_USER}/.profile 2>/dev/null || echo 'umask 002' >> /home/${APP_USER}/.profile
|
||||
grep -q 'umask 002' /home/${APP_USER}/.bashrc 2>/dev/null || echo 'umask 002' >> /home/${APP_USER}/.bashrc
|
||||
sudo -u "$APP_USER" -H bash -lc "npm config set umask 0002" >/dev/null 2>&1 || true
|
||||
|
||||
PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
|
||||
FPM_POOL="/etc/php/${PHPV}/fpm/pool.d/www.conf"
|
||||
if [ -f "$FPM_POOL" ]; then
|
||||
sed -i 's/^;*listen\.owner.*/listen.owner = www-data/' "$FPM_POOL"
|
||||
sed -i 's/^;*listen\.group.*/listen.group = www-data/' "$FPM_POOL"
|
||||
sed -i 's/^;*listen\.mode.*/listen.mode = 0660/' "$FPM_POOL"
|
||||
systemctl restart php${PHPV}-fpm || true
|
||||
fi
|
||||
|
||||
IDE_USER="${SUDO_USER:-}"
|
||||
if [ -n "$IDE_USER" ] && id "$IDE_USER" >/dev/null 2>&1 && command -v setfacl >/dev/null 2>&1; then
|
||||
usermod -a -G "$APP_GROUP" "$IDE_USER" || true
|
||||
setfacl -R -m u:${IDE_USER}:rwX "$APP_DIR"
|
||||
setfacl -dR -m u:${IDE_USER}:rwX "$APP_DIR"
|
||||
echo -e "${GREEN}[i]${NC} Benutzer '${IDE_USER}' wurde für Schreibzugriff freigeschaltet (ACL + Gruppe ${APP_GROUP})."
|
||||
fi
|
||||
|
||||
# Webstack neu laden
|
||||
systemctl reload nginx || true
|
||||
systemctl restart php*-fpm || true
|
||||
|
||||
# ===== Reverb systemd =====
|
||||
cat > /etc/systemd/system/mailwolt-ws.service <<EOF
|
||||
[Unit]
|
||||
Description=MailWolt WebSocket Backend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=NODE_ENV=production WS_PORT=8080
|
||||
User=${APP_USER}
|
||||
Group=${APP_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
ExecStart=/usr/bin/php artisan reverb:start --host=127.0.0.1 --port=8080 --no-interaction
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
StandardOutput=append:/var/log/mailwolt-ws.log
|
||||
StandardError=append:/var/log/mailwolt-ws.log
|
||||
KillSignal=SIGINT
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
chown root:root /etc/systemd/system/mailwolt-ws.service
|
||||
chmod 644 /etc/systemd/system/mailwolt-ws.service
|
||||
|
||||
touch /var/log/mailwolt-ws.log
|
||||
chown ${APP_USER}:${APP_GROUP} /var/log/mailwolt-ws.log
|
||||
chmod 664 /var/log/mailwolt-ws.log
|
||||
|
||||
install -d -m 775 -o "${APP_USER}" -g "${APP_GROUP}" \
|
||||
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache"
|
||||
|
||||
# ---- Laravel-Caches als APP_USER aufräumen (funktioniert jetzt ohne DB-Cache-Tabelle) ----
|
||||
sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear && php artisan config:cache"
|
||||
|
||||
# HINWEIS: Wenn du unbedingt DATABASE als Cache-Store willst, dann vor obiger Zeile:
|
||||
# sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan cache:table && php artisan migrate --force"
|
||||
|
||||
systemctl daemon-reload
|
||||
if sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan list --no-ansi 2>/dev/null | grep -qE '(^| )reverb:start( |$)'"; then
|
||||
systemctl enable --now mailwolt-ws
|
||||
else
|
||||
systemctl disable --now mailwolt-ws >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# ===== Monit =====
|
||||
log "Monit konfigurieren & starten…"
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# ===== Healthchecks =====
|
||||
GREEN="\033[1;32m"; RED="\033[1;31m"; GREY="\033[0;90m"; NC="\033[0m"
|
||||
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
|
||||
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
|
||||
printf " • App … " ; if command -v curl >/dev/null 2>&1; then
|
||||
if [ -s "${CERT}" ] && [ -s "${KEY}" ]; 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'
|
||||
|
||||
print_bootstrap_summary "$SERVER_IP" "$ADMIN_USER" "$ADMIN_PASS"
|
||||
|
||||
# ===== MOTD =====
|
||||
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}')"
|
||||
[ -z "$mhz" ] && mhz="$(awk -F: '/cpu MHz/{printf("%.0f MHz",$2); exit}' /proc/cpuinfo 2>/dev/null)"
|
||||
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
|
||||
|
||||
)
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
if [ -r /etc/mailwolt/installer.env ]; then
|
||||
. /etc/mailwolt/installer.env
|
||||
fi
|
||||
|
||||
REDIS_PASS="${REDIS_PASS:-}"
|
||||
|
||||
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
find "$SCRIPTS_DIR/.." -type f -name "*.sh" -exec sed -i 's/\r$//' {} \; || true
|
||||
|
||||
log "Pakete installieren …"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -y
|
||||
# Minimal aber vollständig
|
||||
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 opendmarc clamav \
|
||||
clamav-daemon 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 monit acl netcat-openbsd jq sqlite3
|
||||
|
||||
# <<< Apache konsequent entfernen >>>
|
||||
systemctl disable --now apache2 >/dev/null 2>&1 || true
|
||||
apt-get -y purge 'apache2*' >/dev/null 2>&1 || true
|
||||
apt-get -y autoremove >/dev/null 2>&1 || true
|
||||
|
||||
log "Systemuser/Dirs …"
|
||||
id vmail >/dev/null 2>&1 || adduser --system --group --home /var/mail vmail
|
||||
id "$APP_USER" >/dev/null 2>&1 || adduser --disabled-password --gecos "" "$APP_USER"
|
||||
# Systemuser/Dirs …
|
||||
id vmail >/dev/null 2>&1 || adduser --system --group --home /var/mail vmail
|
||||
id "$APP_USER" >/dev/null 2>&1 || adduser --disabled-password --gecos "" "$APP_USER"
|
||||
|
||||
# --- FIX: Gruppen und Berechtigungen für Maildir und Dovecot-Zugriff ---
|
||||
# vmail soll primär der Gruppe "mail" angehören, zusätzlich dovecot
|
||||
usermod -g mail -a -G dovecot vmail || true
|
||||
|
||||
# App-User in relevante Gruppen
|
||||
usermod -a -G "$APP_GROUP" "$APP_USER" || true
|
||||
usermod -a -G mail,dovecot "$APP_USER" || true
|
||||
|
||||
# Maildir-Baum für Gruppe mail lesbar
|
||||
chgrp -R mail /var/mail/vhosts || true
|
||||
chmod -R g+rx /var/mail/vhosts || true
|
||||
|
||||
# ACLs setzen, damit neue Verzeichnisse automatisch passende Rechte bekommen
|
||||
setfacl -R -m g:mail:rx /var/mail/vhosts || true
|
||||
setfacl -dR -m g:mail:rx /var/mail/vhosts || true
|
||||
usermod -a -G "$APP_GROUP" "$APP_USER" || true
|
||||
install -d -m 0755 -o root -g root /var/www
|
||||
install -d -m 0775 -o "$APP_USER" -g "$APP_GROUP" "$APP_DIR"
|
||||
|
||||
SUDOERS_DKIM="/etc/sudoers.d/mailwolt-dkim"
|
||||
cat > "${SUDOERS_DKIM}" <<'EOF'
|
||||
Defaults!/usr/local/sbin/mailwolt-install-dkim !requiretty
|
||||
Defaults!/usr/local/sbin/mailwolt-remove-dkim !requiretty
|
||||
Defaults!/usr/bin/systemctl !requiretty
|
||||
Defaults!/usr/bin/test !requiretty
|
||||
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install-dkim *
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-remove-dkim *
|
||||
www-data ALL=(root) NOPASSWD: /usr/bin/systemctl reload opendkim
|
||||
www-data ALL=(root) NOPASSWD: /usr/bin/test *
|
||||
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install-dkim *
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-remove-dkim *
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/bin/systemctl reload opendkim
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/bin/test *
|
||||
EOF
|
||||
chown root:root "${SUDOERS_DKIM}"
|
||||
chmod 440 "${SUDOERS_DKIM}"
|
||||
|
||||
if ! visudo -c -f "${SUDOERS_DKIM}" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in ${SUDOERS_DKIM} – entferne Datei."
|
||||
rm -f "${SUDOERS_DKIM}"
|
||||
fi
|
||||
|
||||
SUDOERS_DOVEADM="/etc/sudoers.d/mailwolt-doveadm"
|
||||
cat > "${SUDOERS_DOVEADM}" <<'EOF'
|
||||
Cmnd_Alias MW_DOVEADM_STATUS = /usr/bin/doveadm -f tab mailbox status -u * messages INBOX, \
|
||||
/usr/bin/doveadm mailbox status -u * messages INBOX
|
||||
www-data ALL=(vmail) NOPASSWD: MW_DOVEADM_STATUS
|
||||
mailwolt ALL=(vmail) NOPASSWD: MW_DOVEADM_STATUS
|
||||
EOF
|
||||
chown root:root "${SUDOERS_DOVEADM}"
|
||||
chmod 440 "${SUDOERS_DOVEADM}"
|
||||
visudo -c -f "${SUDOERS_DOVEADM}" || rm -f "${SUDOERS_DOVEADM}"
|
||||
|
||||
log "MariaDB include-fix …"
|
||||
mkdir -p /etc/mysql/mariadb.conf.d
|
||||
[[ -f /etc/mysql/mariadb.cnf ]] || echo '!include /etc/mysql/mariadb.conf.d/*.cnf' > /etc/mysql/mariadb.cnf
|
||||
|
||||
log "Redis absichern …"
|
||||
if [[ -z "${REDIS_PASS:-}" || "${REDIS_PASS}" == "changeme" ]]; then
|
||||
REDIS_PASS="$(openssl rand -hex 16)"
|
||||
export REDIS_PASS
|
||||
log "Neues Redis-Passwort generiert."
|
||||
fi
|
||||
# Aktiven Redis-Config-Pfad aus systemd holen (Fallback: Standard)
|
||||
REDIS_CONF="$(systemctl show -p ExecStart redis-server \
|
||||
| sed -n 's/^ExecStart=.*redis-server[[:space:]]\+\([^[:space:]]\+\).*/\1/p')"
|
||||
REDIS_CONF="${REDIS_CONF:-/etc/redis/redis.conf}"
|
||||
|
||||
# Bind + protected-mode hart setzen
|
||||
sed -i 's/^[[:space:]]*#\?[[:space:]]*bind .*/bind 127.0.0.1/' "$REDIS_CONF"
|
||||
sed -i 's/^[[:space:]]*#\?[[:space:]]*protected-mode .*/protected-mode yes/' "$REDIS_CONF"
|
||||
|
||||
# Vorherige requirepass-Zeilen entfernen (kommentiert/unkommentiert), dann neu schreiben
|
||||
sed -i '/^[[:space:]]*#\?[[:space:]]*requirepass[[:space:]]\+/d' "$REDIS_CONF"
|
||||
printf '\nrequirepass %s\n' "${REDIS_PASS}" >> "$REDIS_CONF"
|
||||
|
||||
# Dienst aktivieren & neu starten
|
||||
systemctl enable --now redis-server
|
||||
systemctl restart redis-server || true
|
||||
|
||||
# Sanity-Check (kein harter Exit, nur Log)
|
||||
if redis-cli -a "${REDIS_PASS}" ping 2>/dev/null | grep -q PONG; then
|
||||
log "Redis mit Passwort OK."
|
||||
else
|
||||
warn "Redis PING mit Passwort fehlgeschlagen – bitte /etc/redis/redis.conf prüfen."
|
||||
fi
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
CONF_BASE="/etc/${APP_USER}"
|
||||
CERT_DIR="${CONF_BASE}/ssl"
|
||||
UI_SSL_DIR="/etc/ssl/ui"; WEBMAIL_SSL_DIR="/etc/ssl/webmail"; MAIL_SSL_DIR="/etc/ssl/mail"
|
||||
UI_CERT="${UI_SSL_DIR}/fullchain.pem"; UI_KEY="${UI_SSL_DIR}/privkey.pem"
|
||||
WEBMAIL_CERT="${WEBMAIL_SSL_DIR}/fullchain.pem"; WEBMAIL_KEY="${WEBMAIL_SSL_DIR}/privkey.pem"
|
||||
MAIL_CERT="${MAIL_SSL_DIR}/fullchain.pem"; MAIL_KEY="${MAIL_SSL_DIR}/privkey.pem"
|
||||
|
||||
install -d -m 0750 "$CERT_DIR"
|
||||
CERT="${CERT_DIR}/cert.pem"; KEY="${CERT_DIR}/key.pem"
|
||||
|
||||
if [[ ! -s "$CERT" || ! -s "$KEY" ]]; then
|
||||
log "Self-signed Zertifikat erzeugen …"
|
||||
OSSL_CFG="${CERT_DIR}/openssl.cnf"
|
||||
cat > "$OSSL_CFG" <<CFG
|
||||
[req]
|
||||
default_bits=2048
|
||||
prompt=no
|
||||
default_md=sha256
|
||||
req_extensions=req_ext
|
||||
distinguished_name=dn
|
||||
[dn]
|
||||
CN=${SERVER_PUBLIC_IPV4}
|
||||
O=${APP_NAME}
|
||||
C=DE
|
||||
[req_ext]
|
||||
subjectAltName=@alt_names
|
||||
[alt_names]
|
||||
IP.1=${SERVER_PUBLIC_IPV4}
|
||||
CFG
|
||||
openssl req -x509 -newkey rsa:2048 -days 825 -nodes -keyout "$KEY" -out "$CERT" -config "$OSSL_CFG"
|
||||
chgrp www-data "$CERT" "$KEY" || true
|
||||
chmod 640 "$KEY" "$CERT"
|
||||
fi
|
||||
|
||||
install -d -m 0755 "$UI_SSL_DIR" "$WEBMAIL_SSL_DIR" "$MAIL_SSL_DIR"
|
||||
ln -sf "$CERT" "$UI_CERT"; ln -sf "$KEY" "$UI_KEY"
|
||||
ln -sf "$CERT" "$WEBMAIL_CERT";ln -sf "$KEY" "$WEBMAIL_KEY"
|
||||
ln -sf "$CERT" "$MAIL_CERT"; ln -sf "$KEY" "$MAIL_KEY"
|
||||
|
||||
# --- Mail-Zertifikate: Rechte für Postfix & Dovecot -------------------------
|
||||
# WICHTIG: Rechte am *Target* (KEY/CERT im $CERT_DIR) setzen, nicht an den Symlinks.
|
||||
if [[ -f "$KEY" && -f "$CERT" ]]; then
|
||||
echo "[+] Setze Berechtigungen für Mail-Zertifikate …"
|
||||
# Key: nur root + Gruppe lesen. Gruppe → postfix
|
||||
chgrp postfix "$KEY" || true
|
||||
chmod 640 "$KEY" || true
|
||||
# Dovecot zusätzlich Leserechte via ACL
|
||||
setfacl -m u:dovecot:r "$KEY" || true
|
||||
# Zertifikat darf weltweit lesbar sein
|
||||
chmod 644 "$CERT" || true
|
||||
else
|
||||
echo "[!] Zertifikatsdateien fehlen: $KEY oder $CERT" >&2
|
||||
fi
|
||||
|
||||
# Optional: kurze Info, wohin verlinkt wurde
|
||||
echo "[i] Mail TLS: $MAIL_CERT -> $CERT ; $MAIL_KEY -> $KEY"
|
||||
|
|
@ -0,0 +1,588 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Let's Encrypt Deploy-Hooks und Wrapper anlegen …"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 2) POSIX-kompatibler Deploy-Wrapper (von Certbot aufgerufen)
|
||||
# -------------------------------------------------------------------
|
||||
cat >/usr/local/sbin/mailwolt-deploy.sh <<'WRAP'
|
||||
#!/bin/sh
|
||||
# POSIX-safe Certbot deploy-hook (ohne bashisms)
|
||||
set -eu
|
||||
|
||||
# Installer-ENV laden (liefert UI_HOST/WEBMAIL_HOST/MAIL_HOSTNAME etc.)
|
||||
if [ -r /etc/mailwolt/installer.env ]; then
|
||||
. /etc/mailwolt/installer.env
|
||||
fi
|
||||
|
||||
UI_HOST="${UI_HOST:-}"
|
||||
WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
ACME_BASE="/etc/letsencrypt/live"
|
||||
|
||||
copy_cert() {
|
||||
le_base="$1" # z.B. /etc/letsencrypt/live/ui.example.com
|
||||
target_dir="$2" # z.B. /etc/ssl/ui
|
||||
|
||||
cert="${le_base}/fullchain.pem"
|
||||
key="${le_base}/privkey.pem"
|
||||
|
||||
[ -s "$cert" ] || { echo "[deploy] missing $cert"; return 1; }
|
||||
[ -s "$key" ] || { echo "[deploy] missing $key"; return 1; }
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
# echte Dateien (keine Symlinks), feste Rechte
|
||||
install -m 0644 "$cert" "${target_dir}/fullchain.pem"
|
||||
install -m 0600 "$key" "${target_dir}/privkey.pem"
|
||||
|
||||
echo "[+] Copied ${target_dir}/fullchain.pem und privkey.pem ← ${le_base}"
|
||||
}
|
||||
|
||||
reload_services() {
|
||||
kind="$1" # ui | mail
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if [ "$kind" = "mail" ]; then
|
||||
systemctl reload postfix 2>/dev/null || true
|
||||
systemctl reload dovecot 2>/dev/null || true
|
||||
else
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Certbot-Kontext
|
||||
LINEAGE="${RENEWED_LINEAGE:-}"
|
||||
HOST=""
|
||||
if [ -n "$LINEAGE" ]; then
|
||||
HOST="$(basename "$LINEAGE")"
|
||||
fi
|
||||
|
||||
did_any=0
|
||||
|
||||
maybe_copy_for_host() {
|
||||
host="$1"
|
||||
dir="$2"
|
||||
[ -n "$host" ] || return 0
|
||||
|
||||
# Fall A: Certbot liefert RENEWED_DOMAINS (Space-getrennt)
|
||||
if [ -n "${RENEWED_DOMAINS:-}" ]; then
|
||||
case " ${RENEWED_DOMAINS} " in
|
||||
*" ${host} "*) copy_cert "${ACME_BASE}/${host}" "${dir}" && did_any=1 ;;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Fall B: Erst-issue / kein RENEWED_DOMAINS → über LINEAGE matchen
|
||||
if [ -n "$HOST" ] && [ "$HOST" = "$host" ]; then
|
||||
copy_cert "${ACME_BASE}/${host}" "${dir}" && did_any=1
|
||||
fi
|
||||
}
|
||||
|
||||
# Gezieltes Kopieren
|
||||
maybe_copy_for_host "$UI_HOST" "/etc/ssl/ui"
|
||||
maybe_copy_for_host "$WEBMAIL_HOST" "/etc/ssl/webmail"
|
||||
maybe_copy_for_host "$MAIL_HOSTNAME" "/etc/ssl/mail"
|
||||
|
||||
# Fallback (Erstlauf): kopiere vorhandene Lineages
|
||||
if [ "$did_any" -eq 0 ]; then
|
||||
[ -n "$UI_HOST" ] && [ -d "${ACME_BASE}/${UI_HOST}" ] && copy_cert "${ACME_BASE}/${UI_HOST}" "/etc/ssl/ui"
|
||||
[ -n "$WEBMAIL_HOST" ] && [ -d "${ACME_BASE}/${WEBMAIL_HOST}" ] && copy_cert "${ACME_BASE}/${WEBMAIL_HOST}" "/etc/ssl/webmail"
|
||||
[ -n "$MAIL_HOSTNAME" ] && [ -d "${ACME_BASE}/${MAIL_HOSTNAME}" ] && copy_cert "${ACME_BASE}/${MAIL_HOSTNAME}" "/etc/ssl/mail"
|
||||
fi
|
||||
|
||||
# TLSA-Refresh (tolerant falls App noch nicht ready)
|
||||
if command -v php >/dev/null 2>&1 && [ -f /var/www/mailwolt/artisan ]; then
|
||||
(cd /var/www/mailwolt && php artisan dns:tlsa:refresh) || true
|
||||
fi
|
||||
|
||||
# Services neu laden
|
||||
if [ -n "$HOST" ]; then
|
||||
if [ -n "$MAIL_HOSTNAME" ] && [ "$HOST" = "$MAIL_HOSTNAME" ]; then
|
||||
reload_services mail
|
||||
else
|
||||
reload_services ui
|
||||
fi
|
||||
else
|
||||
reload_services ui
|
||||
fi
|
||||
|
||||
exit 0
|
||||
WRAP
|
||||
chmod +x /usr/local/sbin/mailwolt-deploy.sh
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 3) Certbot deploy-hook, der den Wrapper aufruft
|
||||
# -------------------------------------------------------------------
|
||||
install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-certs.sh <<'HOOK'
|
||||
#!/bin/sh
|
||||
exec /usr/local/sbin/mailwolt-deploy.sh
|
||||
HOOK
|
||||
chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-certs.sh
|
||||
|
||||
|
||||
log "[✓] MailWolt Deploy-Hook eingerichtet"
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
## Persistente Installer-Variablen (werden vom Wrapper gelesen)
|
||||
#install -d -m 0755 /etc/mailwolt
|
||||
#cat >/etc/mailwolt/installer.env <<EOF
|
||||
#UI_HOST=${UI_HOST}
|
||||
#WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
#MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
#BASE_DOMAIN=${BASE_DOMAIN}
|
||||
#LE_EMAIL=${LE_EMAIL:-admin@${BASE_DOMAIN}}
|
||||
#SYSMAIL_SUB="${SYSMAIL_SUB}"
|
||||
#SYSMAIL_DOMAIN="${SYSMAIL_DOMAIN}"
|
||||
#DKIM_ENABLE="${DKIM_ENABLE}"
|
||||
#DKIM_SELECTOR="${DKIM_SELECTOR}"
|
||||
#DKIM_GENERATE="${DKIM_GENERATE}"
|
||||
#APP_ENV=${APP_ENV:-production}
|
||||
#EOF
|
||||
#
|
||||
#log "Let's Encrypt Deploy-Hooks und Wrapper anlegen …"
|
||||
#
|
||||
## 1) Wrapper, den Certbot bei Issue/Renew aufruft
|
||||
#cat >/usr/local/sbin/mw-deploy.sh <<'WRAP'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
## Installer-Variablen laden
|
||||
#set +u
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
#set -u
|
||||
#
|
||||
#UI_HOST="${UI_HOST:-}"
|
||||
#WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
#MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
#
|
||||
## --- Kopieren statt Symlinks (damit Laravel lesen kann) ---------------------
|
||||
#copy_cert() {
|
||||
# local le_base="$1" target_dir="$2"
|
||||
# local cert="${le_base}/fullchain.pem"
|
||||
# local key="${le_base}/privkey.pem"
|
||||
#
|
||||
# [[ -s "$cert" && -s "$key" ]] || return 0
|
||||
#
|
||||
# install -d -m 0755 "$target_dir"
|
||||
#
|
||||
# # Vorhandene Symlinks entfernen, sonst kopierst du in die LE-Datei hinein
|
||||
# [ -L "${target_dir}/fullchain.pem" ] && rm -f "${target_dir}/fullchain.pem"
|
||||
# [ -L "${target_dir}/privkey.pem" ] && rm -f "${target_dir}/privkey.pem"
|
||||
#
|
||||
# # Echte Dateien ablegen
|
||||
# install -m 0644 "$cert" "${target_dir}/fullchain.pem"
|
||||
# install -m 0600 "$key" "${target_dir}/privkey.pem"
|
||||
#
|
||||
# echo "[+] Copied ${target_dir}/fullchain.pem und privkey.pem ← ${le_base}"
|
||||
#}
|
||||
#
|
||||
## Nur Domains bearbeiten, die in diesem Lauf betroffen sind.
|
||||
## Bei manchen Distros ist RENEWED_DOMAINS auf Erst-issue leer -> Fallback nutzen.
|
||||
#RDOMS=" ${RENEWED_DOMAINS:-} "
|
||||
#did_any=0
|
||||
#
|
||||
#maybe_copy_for() {
|
||||
# local host="$1" dir="$2"
|
||||
# [[ -z "$host" ]] && return 0
|
||||
# if [[ "$RDOMS" == *" ${host} "* ]]; then
|
||||
# copy_cert "/etc/letsencrypt/live/${host}" "${dir}"
|
||||
# did_any=1
|
||||
# fi
|
||||
#}
|
||||
#
|
||||
## 1) Normalfall: nur die vom Certbot gemeldeten Hosts kopieren
|
||||
#maybe_copy_for "$UI_HOST" "/etc/ssl/ui"
|
||||
#maybe_copy_for "$WEBMAIL_HOST" "/etc/ssl/webmail"
|
||||
#maybe_copy_for "$MAIL_HOSTNAME" "/etc/ssl/mail"
|
||||
#
|
||||
## 2) Fallback: Beim Erstlauf/Edge-Cases alles kopieren, was bereits existiert
|
||||
#if [[ "$did_any" -eq 0 ]]; then
|
||||
# [[ -n "$UI_HOST" && -d "/etc/letsencrypt/live/${UI_HOST}" ]] && copy_cert "/etc/letsencrypt/live/${UI_HOST}" "/etc/ssl/ui"
|
||||
# [[ -n "$WEBMAIL_HOST" && -d "/etc/letsencrypt/live/${WEBMAIL_HOST}" ]] && copy_cert "/etc/letsencrypt/live/${WEBMAIL_HOST}" "/etc/ssl/webmail"
|
||||
# [[ -n "$MAIL_HOSTNAME" && -d "/etc/letsencrypt/live/${MAIL_HOSTNAME}"]] && copy_cert "/etc/letsencrypt/live/${MAIL_HOSTNAME}"/etc/ssl/mail
|
||||
#fi
|
||||
#
|
||||
## Optional: TLSA via Laravel (tolerant, falls App noch nicht gebaut)
|
||||
#if command -v php >/dev/null 2>&1 && [ -d /var/www/mailwolt ] && [ -f /var/www/mailwolt/artisan ]; then
|
||||
# (cd /var/www/mailwolt && php artisan dns:tlsa:refresh) || true
|
||||
#fi
|
||||
#
|
||||
## Nginx nur neu laden, wenn aktiv
|
||||
#if systemctl is-active --quiet nginx; then
|
||||
# systemctl reload nginx || true
|
||||
#fi
|
||||
#WRAP
|
||||
#chmod +x /usr/local/sbin/mw-deploy.sh
|
||||
#
|
||||
## 2) Certbot-Deploy-Hook: ruft den Wrapper bei jeder erfolgreichen Ausstellung/Renew auf
|
||||
#install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
#cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-certs.sh <<'HOOK'
|
||||
##!/usr/bin/env bash
|
||||
#exec /usr/local/sbin/mw-deploy.sh
|
||||
#HOOK
|
||||
#chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-certs.sh
|
||||
#
|
||||
#log "[✓] MailWolt Deploy-Hook eingerichtet"
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
## Persistente Installer-Variablen (werden vom Wrapper gelesen)
|
||||
#install -d -m 0755 /etc/mailwolt
|
||||
#cat >/etc/mailwolt/installer.env <<EOF
|
||||
#UI_HOST=${UI_HOST}
|
||||
#WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
#MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
#BASE_DOMAIN=${BASE_DOMAIN}
|
||||
#LE_EMAIL=${LE_EMAIL:-admin@${BASE_DOMAIN}}
|
||||
#APP_ENV=${APP_ENV:-production}
|
||||
#EOF
|
||||
#
|
||||
#log "Let's Encrypt Deploy-Hooks und Wrapper anlegen …"
|
||||
#
|
||||
## 1) Wrapper, den Certbot bei Issue/Renew aufruft
|
||||
#cat >/usr/local/sbin/mw-deploy.sh <<'WRAP'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
## Installer-Variablen laden
|
||||
#set +u
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
#set -u
|
||||
#
|
||||
#UI_HOST="${UI_HOST:-}"
|
||||
#WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
#MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
#
|
||||
## --- Kopieren statt Symlinks (damit Laravel lesen kann) ---------------------
|
||||
#copy_cert() {
|
||||
# local le_base="$1" target_dir="$2"
|
||||
# local cert="${le_base}/fullchain.pem"
|
||||
# local key="${le_base}/privkey.pem"
|
||||
#
|
||||
# [[ -s "$cert" && -s "$key" ]] || return 0
|
||||
#
|
||||
# # Zielordner sicherstellen
|
||||
# install -d -m 0755 "$target_dir"
|
||||
#
|
||||
# # Falls vorher Symlinks existieren → entfernen, sonst würde "install" das Ziel des Links überschreiben
|
||||
# [ -L "${target_dir}/fullchain.pem" ] && rm -f "${target_dir}/fullchain.pem"
|
||||
# [ -L "${target_dir}/privkey.pem" ] && rm -f "${target_dir}/privkey.pem"
|
||||
#
|
||||
# # KOPIEREN mit sauberen Rechten (Chain world-readable, Key nur root)
|
||||
# install -m 0644 "$cert" "${target_dir}/fullchain.pem"
|
||||
# install -m 0600 "$key" "${target_dir}/privkey.pem"
|
||||
#
|
||||
# echo "[+] Copied ${target_dir}/fullchain.pem und privkey.pem ← ${le_base}"
|
||||
#}
|
||||
#
|
||||
## Nur für Domains arbeiten, die in diesem Lauf betroffen sind
|
||||
#RDOMS=" ${RENEWED_DOMAINS:-} "
|
||||
#
|
||||
## UI
|
||||
#if [[ -n "$UI_HOST" && "$RDOMS" == *" ${UI_HOST} "* ]]; then
|
||||
# copy_cert "/etc/letsencrypt/live/${UI_HOST}" "/etc/ssl/ui"
|
||||
#fi
|
||||
## Webmail
|
||||
#if [[ -n "$WEBMAIL_HOST" && "$RDOMS" == *" ${WEBMAIL_HOST} "* ]]; then
|
||||
# copy_cert "/etc/letsencrypt/live/${WEBMAIL_HOST}" "/etc/ssl/webmail"
|
||||
#fi
|
||||
## MX
|
||||
#if [[ -n "$MAIL_HOSTNAME" && "$RDOMS" == *" ${MAIL_HOSTNAME} "* ]]; then
|
||||
# copy_cert "/etc/letsencrypt/live/${MAIL_HOSTNAME}" "/etc/ssl/mail"
|
||||
#fi
|
||||
#
|
||||
## Optional: TLSA via Laravel (still tolerant, falls App noch nicht gebaut)
|
||||
#if command -v php >/dev/null 2>&1 && [ -d /var/www/mailwolt ] && [ -f /var/www/mailwolt/artisan ]; then
|
||||
# (cd /var/www/mailwolt && php artisan dns:tlsa:refresh) || true
|
||||
#fi
|
||||
#
|
||||
## Nginx nur neu laden, wenn aktiv
|
||||
#if systemctl is-active --quiet nginx; then
|
||||
# systemctl reload nginx || true
|
||||
#fi
|
||||
#WRAP
|
||||
#chmod +x /usr/local/sbin/mw-deploy.sh
|
||||
#
|
||||
## 2) Certbot-Deploy-Hook: ruft den Wrapper bei jeder erfolgreichen Ausstellung/Renew auf
|
||||
#install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
#cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh <<'HOOK'
|
||||
##!/usr/bin/env bash
|
||||
#exec /usr/local/sbin/mw-deploy.sh
|
||||
#HOOK
|
||||
#chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh
|
||||
#
|
||||
#log "[✓] MailWolt Deploy-Hook eingerichtet"
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
#install -d -m 0755 /etc/mailwolt
|
||||
#cat >/etc/mailwolt/installer.env <<EOF
|
||||
#UI_HOST=${UI_HOST}
|
||||
#WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
#MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
#BASE_DOMAIN=${BASE_DOMAIN}
|
||||
#LE_EMAIL=${LE_EMAIL:-admin@${BASE_DOMAIN}}
|
||||
#APP_ENV=${APP_ENV:-production}
|
||||
#EOF
|
||||
#
|
||||
#log "Let's Encrypt Deploy-Hooks und Wrapper anlegen …"
|
||||
#
|
||||
## 1) Wrapper, den Certbot bei Issue/Renew aufruft
|
||||
#cat >/usr/local/sbin/mw-deploy.sh <<'WRAP'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
## Installer-Variablen laden (UI_HOST, WEBMAIL_HOST, MAIL_HOSTNAME, optional LE_EMAIL etc.)
|
||||
#set +u
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
#set -u
|
||||
#
|
||||
#UI_HOST="${UI_HOST:-}"
|
||||
#WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
#MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
#
|
||||
#link_if() {
|
||||
# local le_base="$1" target_dir="$2"
|
||||
# local cert="${le_base}/fullchain.pem"
|
||||
# local key="${le_base}/privkey.pem"
|
||||
# [[ -s "$cert" && -s "$key" ]] || return 0
|
||||
# install -d -m 0755 "$target_dir"
|
||||
# ln -sf "$cert" "${target_dir}/fullchain.pem"
|
||||
# ln -sf "$key" "${target_dir}/privkey.pem"
|
||||
# chmod 644 "${target_dir}/fullchain.pem" 2>/dev/null || true
|
||||
# chmod 600 "${target_dir}/privkey.pem" 2>/dev/null || true
|
||||
# echo "[+] Linked ${target_dir} -> ${le_base}"
|
||||
#}
|
||||
#
|
||||
## Nur für Domains arbeiten, die im aktuellen Lauf erneuert/ausgestellt wurden
|
||||
#RDOMS=" ${RENEWED_DOMAINS:-} "
|
||||
#
|
||||
## UI
|
||||
#if [[ -n "$UI_HOST" && "$RDOMS" == *" ${UI_HOST} "* ]]; then
|
||||
# link_if "/etc/letsencrypt/live/${UI_HOST}" "/etc/ssl/ui"
|
||||
#fi
|
||||
## Webmail
|
||||
#if [[ -n "$WEBMAIL_HOST" && "$RDOMS" == *" ${WEBMAIL_HOST} "* ]]; then
|
||||
# link_if "/etc/letsencrypt/live/${WEBMAIL_HOST}" "/etc/ssl/webmail"
|
||||
#fi
|
||||
## MX
|
||||
#if [[ -n "$MAIL_HOSTNAME" && "$RDOMS" == *" ${MAIL_HOSTNAME} "* ]]; then
|
||||
# link_if "/etc/letsencrypt/live/${MAIL_HOSTNAME}" "/etc/ssl/mail"
|
||||
#fi
|
||||
#
|
||||
## Optional: TLSA via Laravel, falls App schon vorhanden (sonst still überspringen)
|
||||
#if command -v php >/dev/null 2>&1 && [ -d /var/www/mailwolt ]; then
|
||||
# (cd /var/www/mailwolt && php artisan dns:tlsa:refresh) || true
|
||||
#fi
|
||||
#
|
||||
## Nginx nur neu laden, wenn aktiv
|
||||
#if systemctl is-active --quiet nginx; then
|
||||
# systemctl reload nginx || true
|
||||
#fi
|
||||
#WRAP
|
||||
#chmod +x /usr/local/sbin/mw-deploy.sh
|
||||
#
|
||||
## 2) Certbot-Deploy-Hooks einrichten (ruft nur den Wrapper auf)
|
||||
#install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
#cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh <<'HOOK'
|
||||
##!/usr/bin/env bash
|
||||
#exec /usr/local/sbin/mw-deploy.sh
|
||||
#HOOK
|
||||
#chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh
|
||||
#
|
||||
#log "[✓] MailWolt Deploy-Hook eingerichtet"
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
#log "Let's Encrypt Deploy-Hooks und Wrapper anlegen …"
|
||||
#
|
||||
## 1) Wrapper-Skript, das Symlinks setzt und Nginx reloaded
|
||||
#cat >/usr/local/sbin/mw-deploy.sh <<'WRAP'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
#link_if() {
|
||||
# local le_base="$1" target_dir="$2"
|
||||
# local cert="${le_base}/fullchain.pem"
|
||||
# local key="${le_base}/privkey.pem"
|
||||
# [[ -s "$cert" && -s "$key" ]] || return 0
|
||||
# install -d -m 0755 "$target_dir"
|
||||
# ln -sf "$cert" "${target_dir}/fullchain.pem"
|
||||
# ln -sf "$key" "${target_dir}/privkey.pem"
|
||||
# chmod 644 "${target_dir}/fullchain.pem" 2>/dev/null || true
|
||||
# chmod 600 "${target_dir}/privkey.pem" 2>/dev/null || true
|
||||
# echo "[+] Linked ${target_dir} -> ${le_base}"
|
||||
#}
|
||||
#
|
||||
#UI_HOST="${UI_HOST:-}"
|
||||
#WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
#MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
#
|
||||
#[[ -n "$UI_HOST" ]] && link_if "/etc/letsencrypt/live/${UI_HOST}" "/etc/ssl/ui"
|
||||
#[[ -n "$WEBMAIL_HOST" ]] && link_if "/etc/letsencrypt/live/${WEBMAIL_HOST}" "/etc/ssl/webmail"
|
||||
#[[ -n "$MAIL_HOSTNAME" ]] && link_if "/etc/letsencrypt/live/${MAIL_HOSTNAME}" "/etc/ssl/mail"
|
||||
#
|
||||
#if systemctl is-active --quiet nginx; then
|
||||
# systemctl reload nginx || true
|
||||
#fi
|
||||
#WRAP
|
||||
#
|
||||
#chmod +x /usr/local/sbin/mw-deploy.sh
|
||||
#
|
||||
## 2) Certbot Deploy-Hook-Verzeichnis + Symlink für Renewals
|
||||
#install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
#cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh <<'HOOK'
|
||||
##!/usr/bin/env bash
|
||||
#exec /usr/local/sbin/mw-deploy.sh
|
||||
#HOOK
|
||||
#chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh
|
||||
#
|
||||
#log "[✓] MailWolt Deploy-Hook eingerichtet"
|
||||
#
|
||||
###!/usr/bin/env bash
|
||||
##set -euo pipefail
|
||||
##source ./lib.sh
|
||||
##
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
### 21-le-deploy-hook.sh
|
||||
### • legt /etc/mailwolt/installer.env an (falls fehlt)
|
||||
### • erzeugt Deploy-Hooks:
|
||||
### - 50-mailwolt-symlinks.sh → verlinkt LE-Zerts nach /etc/ssl/{ui,webmail,mail}
|
||||
### - 60-mailwolt-tlsa.sh → aktualisiert TLSA (3 1 1) für MX bei jedem Renew
|
||||
### • KEIN Reload von Postfix/Dovecot (kommt später im Installer)
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
##
|
||||
### 0) Hostnamen persistent speichern (für spätere Deploys)
|
||||
##install -d -m 0755 /etc/mailwolt
|
||||
##if [[ ! -f /etc/mailwolt/installer.env ]]; then
|
||||
## cat >/etc/mailwolt/installer.env <<EOF
|
||||
##UI_HOST=${UI_HOST}
|
||||
##WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
##MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
##EOF
|
||||
## echo "[+] /etc/mailwolt/installer.env erstellt."
|
||||
##fi
|
||||
##
|
||||
### 1) Deploy-Hooks-Verzeichnis anlegen
|
||||
##install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
|
||||
##
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
### 2) 50-mailwolt-symlinks.sh
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
##cat >/etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh <<HOOK
|
||||
###!/usr/bin/env bash
|
||||
##set -euo pipefail
|
||||
##
|
||||
##UI_LE="/etc/letsencrypt/live/${UI_HOST}"
|
||||
##WEBMAIL_LE="/etc/letsencrypt/live/${WEBMAIL_HOST}"
|
||||
##MX_LE="/etc/letsencrypt/live/${MAIL_HOSTNAME}"
|
||||
##
|
||||
##UI_SSL_DIR="/etc/ssl/ui"
|
||||
##WEBMAIL_SSL_DIR="/etc/ssl/webmail"
|
||||
##MAIL_SSL_DIR="/etc/ssl/mail"
|
||||
##
|
||||
### Zielverzeichnisse anlegen (einmalig)
|
||||
##install -d -m 0755 "\$UI_SSL_DIR" "\$WEBMAIL_SSL_DIR" "\$MAIL_SSL_DIR"
|
||||
##
|
||||
##link_if() {
|
||||
## local le_base="\$1" target_dir="\$2"
|
||||
## local cert="\${le_base}/fullchain.pem"
|
||||
## local key="\${le_base}/privkey.pem"
|
||||
## [[ -s "\$cert" && -s "\$key" ]] || return 0
|
||||
## ln -sf "\$cert" "\${target_dir}/fullchain.pem"
|
||||
## ln -sf "\$key" "\${target_dir}/privkey.pem"
|
||||
## chmod 644 "\${target_dir}/fullchain.pem" 2>/dev/null || true
|
||||
## chmod 600 "\${target_dir}/privkey.pem" 2>/dev/null || true
|
||||
## echo "[+] Linked \${target_dir} -> \${le_base}"
|
||||
##}
|
||||
##
|
||||
### Verlinken (nur wenn Host konfiguriert)
|
||||
##[[ -n "${UI_HOST}" ]] && link_if "\$UI_LE" "\$UI_SSL_DIR"
|
||||
##[[ -n "${WEBMAIL_HOST}" ]] && link_if "\$WEBMAIL_LE" "\$WEBMAIL_SSL_DIR"
|
||||
##[[ -n "${MAIL_HOSTNAME}" ]] && link_if "\$MX_LE" "\$MAIL_SSL_DIR"
|
||||
##
|
||||
### Nur reloaden, wenn Nginx aktiv ist (Installer startet ihn später erst)
|
||||
##if systemctl is-active --quiet nginx; then
|
||||
## systemctl reload nginx || true
|
||||
##fi
|
||||
##HOOK
|
||||
##chmod +x /etc/letsencrypt/renewal-hooks/deploy/50-mailwolt-symlinks.sh
|
||||
##
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
### 3) 60-mailwolt-tlsa.sh
|
||||
### → nutzt Laravel, falls vorhanden; sonst Fallback mit OpenSSL.
|
||||
### → schreibt nur, wenn sich der Hash geändert hat (idempotent)
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
##cat >/etc/letsencrypt/renewal-hooks/deploy/60-mailwolt-tlsa.sh <<'HOOK'
|
||||
###!/usr/bin/env bash
|
||||
##set -euo pipefail
|
||||
##
|
||||
### installer.env lesen
|
||||
##set +u
|
||||
##[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
##set -u
|
||||
##
|
||||
##APP_ENV_VAL="${APP_ENV:-production}"
|
||||
##BASE_DOMAIN_VAL="${BASE_DOMAIN:-example.com}"
|
||||
##
|
||||
##case "$APP_ENV_VAL" in
|
||||
## local|dev|development) exit 0 ;;
|
||||
##esac
|
||||
##[ "$BASE_DOMAIN_VAL" = "example.com" ] && exit 0
|
||||
##
|
||||
##MX_HOST="${MAIL_HOSTNAME:-}"
|
||||
##SERVICE="_25._tcp"
|
||||
##DNS_DIR="/etc/mailwolt/dns"
|
||||
##OUT_FILE="${DNS_DIR}/${MX_HOST}.tlsa.txt"
|
||||
##
|
||||
### Nur reagieren, wenn MX-Zertifikat betroffen war
|
||||
##case " ${RENEWED_DOMAINS:-} " in
|
||||
## *" ${MX_HOST} "*) ;;
|
||||
## *) exit 0 ;;
|
||||
##esac
|
||||
##
|
||||
##CERT="${RENEWED_LINEAGE}/fullchain.pem"
|
||||
##[ -s "$CERT" ] || exit 0
|
||||
##
|
||||
### Wenn Laravel vorhanden ist → interner Command (DB + Datei idempotent)
|
||||
##if command -v php >/dev/null 2>&1 && [ -d /var/www/mailwolt ]; then
|
||||
## cd /var/www/mailwolt || exit 0
|
||||
## php artisan dns:tlsa:refresh || true
|
||||
## exit 0
|
||||
##fi
|
||||
##
|
||||
### Fallback: nur Datei aktualisieren, wenn Hash sich ändert
|
||||
##HASH="$(openssl x509 -in "$CERT" -noout -pubkey \
|
||||
## | openssl pkey -pubin -outform DER \
|
||||
## | openssl dgst -sha256 | sed 's/^.*= //')"
|
||||
##NEW_LINE="${SERVICE}.${MX_HOST}. IN TLSA 3 1 1 ${HASH}"
|
||||
##
|
||||
##mkdir -p "$DNS_DIR"
|
||||
##
|
||||
##if [ -r "$OUT_FILE" ] && grep -q "IN TLSA" "$OUT_FILE"; then
|
||||
## if grep -q "$HASH" "$OUT_FILE"; then
|
||||
## echo "[TLSA] Unverändert – kein Update nötig."
|
||||
## exit 0
|
||||
## fi
|
||||
##fi
|
||||
##
|
||||
##echo "$NEW_LINE" > "$OUT_FILE"
|
||||
##echo "[TLSA] Aktualisiert: $NEW_LINE"
|
||||
##HOOK
|
||||
##chmod +x /etc/letsencrypt/renewal-hooks/deploy/60-mailwolt-tlsa.sh
|
||||
##
|
||||
### ────────────────────────────────────────────────────────────────────────────
|
||||
##echo "[✓] Deploy-Hooks installiert."
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Installiere DKIM-Helper …"
|
||||
|
||||
install -d -m 0755 /usr/local/sbin
|
||||
|
||||
cat >/usr/local/sbin/mailwolt-install-dkim <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="$1" # z.B. sysmail.toastra.com
|
||||
SELECTOR="${2:-mwl1}"
|
||||
|
||||
[[ -n "$DOMAIN" ]] || { echo "Usage: $0 <domain> [selector]"; exit 2; }
|
||||
|
||||
KEYDIR="/etc/opendkim/keys/${DOMAIN}"
|
||||
PRIV="${KEYDIR}/${SELECTOR}.private"
|
||||
TXT="${KEYDIR}/${SELECTOR}.txt"
|
||||
|
||||
install -d -m 0750 -o opendkim -g opendkim "$KEYDIR"
|
||||
|
||||
if [[ ! -s "$PRIV" ]]; then
|
||||
opendkim-genkey -b 2048 -s "$SELECTOR" -d "$DOMAIN" -D "$KEYDIR"
|
||||
chown opendkim:opendkim "$PRIV"
|
||||
chmod 600 "$PRIV"
|
||||
fi
|
||||
|
||||
grep -q "^${SELECTOR}\._domainkey\.${DOMAIN} " /etc/opendkim/KeyTable 2>/dev/null \
|
||||
|| echo "${SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${SELECTOR}:${PRIV}" >> /etc/opendkim/KeyTable
|
||||
|
||||
grep -q "^\*@${DOMAIN} " /etc/opendkim/SigningTable 2>/dev/null \
|
||||
|| echo "*@${DOMAIN} ${SELECTOR}._domainkey.${DOMAIN}" >> /etc/opendkim/SigningTable
|
||||
|
||||
install -d -m 0755 /etc/mailwolt/dns
|
||||
[[ -s "$TXT" ]] && cp -f "$TXT" "/etc/mailwolt/dns/dkim-${DOMAIN}.txt" || true
|
||||
|
||||
systemctl restart opendkim
|
||||
EOF
|
||||
|
||||
log "[✓] DKIM-Helper installiert: /usr/local/sbin/mailwolt-install-dkim"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "MariaDB vorbereiten …"
|
||||
systemctl enable --now mariadb
|
||||
mysql -uroot <<SQL
|
||||
CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
#!/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 "Postfix konfigurieren …"
|
||||
|
||||
# --- TLS-Dateirechte (falls du sie in /etc/mailwolt/ssl spiegelst) -----------
|
||||
if [[ -e "${MAIL_KEY}" ]]; then
|
||||
chgrp -R postfix /etc/mailwolt/ssl || true
|
||||
chmod 750 /etc/mailwolt/ssl || true
|
||||
chmod 640 /etc/mailwolt/ssl/key.pem /etc/mailwolt/ssl/cert.pem || true
|
||||
fi
|
||||
|
||||
# --- Basiskonfiguration -------------------------------------------------------
|
||||
/usr/sbin/postconf -e "myhostname = ${MAIL_HOSTNAME}"
|
||||
/usr/sbin/postconf -e "myorigin = \$myhostname"
|
||||
/usr/sbin/postconf -e "mydestination = "
|
||||
/usr/sbin/postconf -e "inet_interfaces = all"
|
||||
/usr/sbin/postconf -e "inet_protocols = all"
|
||||
/usr/sbin/postconf -e "smtpd_banner = \$myhostname ESMTP"
|
||||
|
||||
# --- TLS ----------------------------------------------------------------------
|
||||
/usr/sbin/postconf -e "smtpd_tls_cert_file = ${MAIL_CERT}"
|
||||
/usr/sbin/postconf -e "smtpd_tls_key_file = ${MAIL_KEY}"
|
||||
/usr/sbin/postconf -e "smtpd_tls_security_level = may"
|
||||
/usr/sbin/postconf -e "smtpd_use_tls = yes"
|
||||
/usr/sbin/postconf -e "smtpd_tls_received_header = yes"
|
||||
/usr/sbin/postconf -e "smtpd_tls_protocols = !SSLv2,!SSLv3"
|
||||
/usr/sbin/postconf -e "smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3"
|
||||
/usr/sbin/postconf -e "smtpd_tls_loglevel = 1"
|
||||
/usr/sbin/postconf -e "smtp_tls_security_level = may"
|
||||
/usr/sbin/postconf -e "smtp_tls_loglevel = 1"
|
||||
|
||||
DH_FILE="/etc/ssl/private/dhparam.pem"
|
||||
if [[ ! -s "$DH_FILE" ]]; then
|
||||
log "Generiere 2048-Bit DH-Parameter …"
|
||||
openssl dhparam -out "$DH_FILE" 2048
|
||||
chmod 600 "$DH_FILE"
|
||||
chown root:root "$DH_FILE"
|
||||
fi
|
||||
/usr/sbin/postconf -e "smtpd_tls_dh1024_param_file = ${DH_FILE}"
|
||||
/usr/sbin/postconf -e "smtpd_tls_dh1024_param_file = ${DH_FILE}"
|
||||
/usr/sbin/postconf -e "smtpd_tls_eecdh_grade = strong"
|
||||
/usr/sbin/postconf -e "tls_preempt_cipherlist = yes"
|
||||
|
||||
# Nur moderne TLS-Versionen (auch für ausgehendes SMTP)
|
||||
# (überschreibt die älteren Zeilen oben)
|
||||
/usr/sbin/postconf -e "smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
|
||||
/usr/sbin/postconf -e "smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
|
||||
/usr/sbin/postconf -e "smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1"
|
||||
|
||||
# Hohe Cipher, alte raus
|
||||
/usr/sbin/postconf -e "smtpd_tls_ciphers = high"
|
||||
/usr/sbin/postconf -e "smtp_tls_ciphers = high"
|
||||
/usr/sbin/postconf -e "smtpd_tls_exclude_ciphers = aNULL,eNULL,MD5,RC4,DES,3DES"
|
||||
/usr/sbin/postconf -e "smtp_tls_exclude_ciphers = aNULL,eNULL,MD5,RC4,DES,3DES"
|
||||
|
||||
# --- SMTP Sicherheit ----------------------------------------------------------
|
||||
/usr/sbin/postconf -e "disable_vrfy_command = yes"
|
||||
/usr/sbin/postconf -e "smtpd_helo_required = yes"
|
||||
|
||||
# --- Milter -------------------------------------------------------------------
|
||||
/usr/sbin/postconf -e "milter_default_action = accept"
|
||||
/usr/sbin/postconf -e "milter_protocol = 6"
|
||||
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
|
||||
# --- SASL Auth via Dovecot ----------------------------------------------------
|
||||
/usr/sbin/postconf -e "smtpd_sasl_type = dovecot"
|
||||
/usr/sbin/postconf -e "smtpd_sasl_path = private/auth"
|
||||
/usr/sbin/postconf -e "smtpd_sasl_auth_enable = yes"
|
||||
/usr/sbin/postconf -e "smtpd_sasl_security_options = noanonymous"
|
||||
|
||||
# --- Recipient & Relay Restriction --------------------------------------------
|
||||
/usr/sbin/postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"
|
||||
/usr/sbin/postconf -e "smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination"
|
||||
|
||||
# --- Listener / Master.cf Definition ------------------------------------------
|
||||
/usr/sbin/postconf -M "smtp/inet=smtp inet n - n - - smtpd -o smtpd_peername_lookup=no -o smtpd_timeout=30s"
|
||||
/usr/sbin/postconf -M "submission/inet=submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_peername_lookup=no -o smtpd_tls_security_level=encrypt -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||||
/usr/sbin/postconf -M "smtps/inet=smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_peername_lookup=no -o smtpd_tls_wrappermode=yes -o smtpd_tls_auth_only=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject"
|
||||
|
||||
# postscreen ggf. deaktivieren
|
||||
sed -i 's/^[[:space:]]*smtp[[:space:]]\+inet[[:space:]]\+.*postscreen/# &/' /etc/postfix/master.cf || true
|
||||
|
||||
# --- SQL Maps (Verzeichnis zuerst!) -------------------------------------------
|
||||
install -d -o root -g postfix -m 750 /etc/postfix/sql
|
||||
|
||||
# Domains
|
||||
cat > /etc/postfix/sql/mysql-virtual-domains.cf <<CONF
|
||||
hosts = 127.0.0.1
|
||||
user = ${DB_USER}
|
||||
password = ${DB_PASS}
|
||||
dbname = ${DB_NAME}
|
||||
query = SELECT 1 FROM domains WHERE domain = '%s' AND is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:postfix /etc/postfix/sql/mysql-virtual-domains.cf
|
||||
chmod 640 /etc/postfix/sql/mysql-virtual-domains.cf
|
||||
|
||||
# Mailboxen
|
||||
cat > /etc/postfix/sql/mysql-virtual-mailbox-maps.cf <<CONF
|
||||
hosts = 127.0.0.1
|
||||
user = ${DB_USER}
|
||||
password = ${DB_PASS}
|
||||
dbname = ${DB_NAME}
|
||||
query = SELECT 1 FROM mail_users u JOIN domains d ON d.id = u.domain_id WHERE u.email = '%s' AND u.is_active = 1 AND u.can_login = 1 AND u.password_hash IS NOT NULL AND d.is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:postfix /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||||
chmod 640 /etc/postfix/sql/mysql-virtual-mailbox-maps.cf
|
||||
|
||||
# Aliase
|
||||
cat > /etc/postfix/sql/mysql-virtual-alias-maps.cf <<CONF
|
||||
hosts = 127.0.0.1
|
||||
user = ${DB_USER}
|
||||
password = ${DB_PASS}
|
||||
dbname = ${DB_NAME}
|
||||
query = SELECT COALESCE(mu.email, r.email) AS destination FROM mail_aliases a JOIN domains d ON d.id = a.domain_id JOIN mail_alias_recipients r ON r.alias_id = a.id LEFT JOIN mail_users mu ON mu.id = r.mail_user_id WHERE d.domain = SUBSTRING_INDEX('%s','@',-1) AND a.local = SUBSTRING_INDEX('%s','@', 1) AND a.is_active = 1 AND d.is_active = 1 AND (mu.email IS NOT NULL OR r.email IS NOT NULL) ORDER BY r.position ASC;
|
||||
CONF
|
||||
chown root:postfix /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||||
chmod 640 /etc/postfix/sql/mysql-virtual-alias-maps.cf
|
||||
|
||||
# Aktivieren
|
||||
/usr/sbin/postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/mysql-virtual-domains.cf"
|
||||
/usr/sbin/postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/mysql-virtual-mailbox-maps.cf"
|
||||
/usr/sbin/postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/sql/mysql-virtual-alias-maps.cf"
|
||||
/usr/sbin/postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"
|
||||
|
||||
# --- Dienst aktivieren & neu laden --------------------------------------------
|
||||
#systemctl enable postfix >/dev/null 2>&1 || true
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
#!/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 …"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 1) vmail-Benutzer/Gruppe & Mailspool vorbereiten (DYNAMIC UID!)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Sicherstellen, dass die Gruppe 'mail' existiert (auf Debian/Ubuntu idR vorhanden)
|
||||
getent group mail >/dev/null || groupadd -g 8 mail || true
|
||||
|
||||
# vmail anlegen, wenn er fehlt. Bevorzugt UID 109, falls frei – sonst automatisch.
|
||||
if ! getent passwd vmail >/dev/null; then
|
||||
if ! getent passwd 109 >/dev/null; then
|
||||
useradd -u 109 -g mail -d /var/mail -M -s /usr/sbin/nologin vmail
|
||||
else
|
||||
useradd -g mail -d /var/mail -M -s /usr/sbin/nologin vmail
|
||||
fi
|
||||
fi
|
||||
|
||||
# Tatsächliche vmail-UID ermitteln (wird unten in die Dovecot-Config geschrieben)
|
||||
VMAIL_UID="$(id -u vmail)"
|
||||
|
||||
# Mailspool-Basis
|
||||
install -d -m 0770 -o vmail -g mail /var/mail/vhosts
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 2) Dovecot Grundgerüst
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Hauptdatei
|
||||
install -d -m 0755 /etc/dovecot/conf.d
|
||||
cat > /etc/dovecot/dovecot.conf <<'CONF'
|
||||
!include_try /etc/dovecot/conf.d/*.conf
|
||||
CONF
|
||||
|
||||
# Mail-Location & Namespace + UID-Grenzen
|
||||
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
|
||||
mail_access_groups = mail
|
||||
first_valid_uid = ${VMAIL_UID}
|
||||
last_valid_uid = ${VMAIL_UID}
|
||||
CONF
|
||||
|
||||
cat > /etc/dovecot/conf.d/15-mailboxes.conf <<'CONF'
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
|
||||
mailbox Drafts {
|
||||
special_use = \Drafts
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Junk {
|
||||
special_use = \Junk
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Trash {
|
||||
special_use = \Trash
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Sent {
|
||||
special_use = \Sent
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Archive {
|
||||
special_use = \Archive
|
||||
auto = create
|
||||
}
|
||||
}
|
||||
CONF
|
||||
|
||||
# Auth
|
||||
cat > /etc/dovecot/conf.d/10-auth.conf <<'CONF'
|
||||
disable_plaintext_auth = yes
|
||||
auth_mechanisms = plain login
|
||||
!include_try auth-sql.conf.ext
|
||||
|
||||
auth_cache_size = 10M
|
||||
auth_cache_ttl = 1 hour
|
||||
CONF
|
||||
|
||||
# SQL-Anbindung (Passwörter aus App-DB)
|
||||
cat > /etc/dovecot/dovecot-sql.conf.ext <<CONF
|
||||
driver = mysql
|
||||
connect = host=127.0.0.1 dbname=${DB_NAME} user=${DB_USER} password=${DB_PASS}
|
||||
default_pass_scheme = BLF-CRYPT
|
||||
password_query = SELECT u.email AS user, u.password_hash AS password FROM mail_users u JOIN domains d ON d.id = u.domain_id WHERE u.email = '%u' AND u.is_active = 1 AND u.can_login = 1 AND u.password_hash IS NOT NULL AND d.is_active = 1 LIMIT 1;
|
||||
CONF
|
||||
chown root:dovecot /etc/dovecot/dovecot-sql.conf.ext
|
||||
chmod 640 /etc/dovecot/dovecot-sql.conf.ext
|
||||
|
||||
# Auth-SQL → userdb static auf vmail:mail (Home unter /var/mail/vhosts/%d/%n)
|
||||
cat > /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=mail 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
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 3) IMAP Optimierung (iOS/IDLE-freundlich)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
cat > /etc/dovecot/conf.d/20-imap.conf <<'CONF'
|
||||
# IMAP-spezifische Einstellungen
|
||||
|
||||
imap_idle_notify_interval = 2 mins
|
||||
imap_hibernate_timeout = 0
|
||||
|
||||
protocol imap {
|
||||
mail_max_userip_connections = 20
|
||||
imap_logout_format = in=%i out=%o deleted=%{deleted} expunged=%{expunged}
|
||||
}
|
||||
CONF
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 4) Master Services (LMTP, AUTH, IMAP, POP3, STATS)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
}
|
||||
unix_listener auth-userdb {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = mail
|
||||
}
|
||||
process_limit = 1
|
||||
}
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
port = 143
|
||||
}
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
ssl = yes
|
||||
}
|
||||
process_limit = 128
|
||||
process_min_avail = 10
|
||||
service_count = 0
|
||||
vsz_limit = 512M
|
||||
}
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
port = 110
|
||||
}
|
||||
inet_listener pop3s {
|
||||
port = 995
|
||||
ssl = yes
|
||||
}
|
||||
process_limit = 50
|
||||
service_count = 0
|
||||
}
|
||||
CONF
|
||||
|
||||
# --- Dovecot: doveadm-server für App-Zugriff ---
|
||||
cat >/etc/dovecot/conf.d/99-mailwolt-perms.conf <<'CONF'
|
||||
service auth {
|
||||
unix_listener auth-userdb {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = mail
|
||||
}
|
||||
}
|
||||
|
||||
service stats {
|
||||
unix_listener stats-reader {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = mail
|
||||
}
|
||||
unix_listener stats-writer {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = mail
|
||||
}
|
||||
}
|
||||
CONF
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 5) SSL-Konfiguration (ohne DH-Param-Erzeugung)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DOVECOT_SSL_CONF="/etc/dovecot/conf.d/10-ssl.conf"
|
||||
touch "$DOVECOT_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
|
||||
grep -q '^ssl_min_protocol' "$DOVECOT_SSL_CONF" || echo "ssl_min_protocol = TLSv1.2" >> "$DOVECOT_SSL_CONF"
|
||||
grep -q '^ssl_prefer_server_ciphers' "$DOVECOT_SSL_CONF" || echo "ssl_prefer_server_ciphers = yes" >> "$DOVECOT_SSL_CONF"
|
||||
|
||||
grep -q '^ssl_dh' "$DOVECOT_SSL_CONF" || echo "ssl_dh = </etc/ssl/private/dhparam.pem" >> "$DOVECOT_SSL_CONF"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 6) Verzeichnisse & Rechte prüfen
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
mkdir -p /var/spool/postfix/private
|
||||
chown root:root /var/spool/postfix
|
||||
chmod 0755 /var/spool/postfix
|
||||
chown postfix:postfix /var/spool/postfix/private
|
||||
chmod 0755 /var/spool/postfix/private
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 7) Abschluss
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
log "Dovecot-Konfiguration abgeschlossen."
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Rspamd + OpenDKIM einrichten …"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# ENV laden
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
set +u
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
set -u
|
||||
|
||||
BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
|
||||
SYSMAIL_DOMAIN="${SYSMAIL_DOMAIN:-sysmail.${BASE_DOMAIN}}" # z.B. sysmail.example.com
|
||||
DKIM_ENABLE="${DKIM_ENABLE:-1}" # 1=OpenDKIM aktiv
|
||||
DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}" # z.B. mwl1
|
||||
DKIM_GENERATE="${DKIM_GENERATE:-0}" # 1=Key generieren, falls fehlt
|
||||
RSPAMD_CONTROLLER_PASSWORD="${RSPAMD_CONTROLLER_PASSWORD:-admin}"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Rspamd (Controller + Milter)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
install -d -m 0750 /etc/rspamd/local.d
|
||||
|
||||
if command -v rspamadm >/dev/null 2>&1; then
|
||||
RSPAMD_HASH="$(rspamadm pw -p "${RSPAMD_CONTROLLER_PASSWORD}")"
|
||||
else
|
||||
RSPAMD_HASH="${RSPAMD_CONTROLLER_PASSWORD}"
|
||||
fi
|
||||
|
||||
cat >/etc/rspamd/local.d/worker-controller.inc <<CONF
|
||||
worker "controller" {
|
||||
bind_socket = "127.0.0.1:11334";
|
||||
password = "${RSPAMD_HASH}";
|
||||
}
|
||||
CONF
|
||||
|
||||
cat >/etc/rspamd/local.d/statistic.conf <<CONF
|
||||
classifier "bayes" {
|
||||
backend = "redis";
|
||||
autolearn = true;
|
||||
autolearn_threshold = 6.0;
|
||||
ham_symbols = ["BAYES_HAM"];
|
||||
spam_symbols = ["BAYES_SPAM"];
|
||||
min_learns = 10;
|
||||
store_tokens = true;
|
||||
per_user = false;
|
||||
}
|
||||
CONF
|
||||
|
||||
cat >/etc/rspamd/local.d/worker-proxy.inc <<'CONF'
|
||||
worker "proxy" {
|
||||
bind_socket = "127.0.0.1:11332";
|
||||
milter = yes;
|
||||
timeout = 120s;
|
||||
|
||||
upstream "scan" {
|
||||
default = yes;
|
||||
self_scan = yes;
|
||||
servers = "127.0.0.1:11333";
|
||||
}
|
||||
}
|
||||
CONF
|
||||
|
||||
cat >/etc/rspamd/local.d/worker-normal.inc <<'CONF'
|
||||
worker "normal" {
|
||||
bind_socket = "127.0.0.1:11333";
|
||||
}
|
||||
CONF
|
||||
|
||||
cat >/etc/rspamd/local.d/milter_headers.conf <<'CONF'
|
||||
use = ["authentication-results"];
|
||||
header = "Authentication-Results";
|
||||
CONF
|
||||
|
||||
cat >/etc/rspamd/local.d/options.inc <<'CONF'
|
||||
dns {
|
||||
servers = ["9.9.9.9:53", "1.1.1.1:53"];
|
||||
timeout = 5s;
|
||||
retransmits = 2;
|
||||
}
|
||||
CONF
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Rspamd Redis-Konfiguration
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
log "Rspamd Redis konfigurieren …"
|
||||
|
||||
: "${REDIS_PASS:=}"
|
||||
|
||||
cat >/etc/rspamd/local.d/redis.conf <<CONF
|
||||
servers = "127.0.0.1:6379";
|
||||
${REDIS_PASS:+password = "${REDIS_PASS}";}
|
||||
db = 0;
|
||||
CONF
|
||||
|
||||
# Eigentümer und Rechte setzen
|
||||
chown root:_rspamd /etc/rspamd/local.d /etc/rspamd/local.d/redis.conf
|
||||
chmod 750 /etc/rspamd/local.d
|
||||
chmod 640 /etc/rspamd/local.d/redis.conf
|
||||
|
||||
# Testweise prüfen, ob Redis erreichbar ist (nicht kritisch)
|
||||
if command -v redis-cli >/dev/null 2>&1; then
|
||||
if [[ -n "${REDIS_PASS}" ]]; then
|
||||
if redis-cli -h 127.0.0.1 -p 6379 -a "${REDIS_PASS}" ping >/dev/null 2>&1; then
|
||||
log "[✓] Redis erreichbar und Passwort akzeptiert."
|
||||
else
|
||||
log "[!] Warnung: Redis antwortet nicht oder Passwort falsch."
|
||||
fi
|
||||
else
|
||||
if redis-cli -h 127.0.0.1 -p 6379 ping >/dev/null 2>&1; then
|
||||
log "[✓] Redis erreichbar (ohne Passwort)."
|
||||
else
|
||||
log "[!] Warnung: Redis antwortet nicht."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
systemctl enable --now rspamd || true
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# OpenDKIM – nur wenn DKIM_ENABLE=1
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ "${DKIM_ENABLE}" != "1" ]]; then
|
||||
log "DKIM_ENABLE=0 → OpenDKIM wird übersprungen."
|
||||
/usr/sbin/postconf -e "milter_default_action = accept"
|
||||
/usr/sbin/postconf -e "milter_protocol = 6"
|
||||
/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332"
|
||||
/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
install -d -m 0755 /etc/opendkim
|
||||
install -d -m 0750 /etc/opendkim/keys
|
||||
chown -R opendkim:opendkim /etc/opendkim
|
||||
chmod 750 /etc/opendkim/keys
|
||||
|
||||
# TrustedHosts
|
||||
cat >/etc/opendkim/TrustedHosts <<'CONF'
|
||||
127.0.0.1
|
||||
::1
|
||||
localhost
|
||||
CONF
|
||||
chown opendkim:opendkim /etc/opendkim/TrustedHosts
|
||||
chmod 640 /etc/opendkim/TrustedHosts
|
||||
|
||||
# ── Key-Verzeichnis für SYSMAIL_DOMAIN vorbereiten ───────────────────────────
|
||||
KEY_DIR="/etc/opendkim/keys/${SYSMAIL_DOMAIN}"
|
||||
KEY_PRIV="${KEY_DIR}/${DKIM_SELECTOR}.private"
|
||||
KEY_DNSTXT="${KEY_DIR}/${DKIM_SELECTOR}.txt"
|
||||
install -d -m 0750 -o opendkim -g opendkim "${KEY_DIR}"
|
||||
|
||||
# ── Key optional generieren (nur wenn gewünscht) ─────────────────────────────
|
||||
if [[ ! -s "${KEY_PRIV}" && "${DKIM_GENERATE}" = "1" ]]; then
|
||||
if command -v opendkim-genkey >/dev/null 2>&1; then
|
||||
opendkim-genkey -b 2048 -s "${DKIM_SELECTOR}" -d "${SYSMAIL_DOMAIN}" -D "${KEY_DIR}"
|
||||
chown opendkim:opendkim "${KEY_DIR}/${DKIM_SELECTOR}.private" || true
|
||||
chmod 600 "${KEY_DIR}/${DKIM_SELECTOR}.private" || true
|
||||
else
|
||||
echo "[!] opendkim-genkey fehlt – kann DKIM-Key nicht generieren."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Key-/SigningTable nur anlegen, nicht leeren ───────────────────────────────
|
||||
touch /etc/opendkim/KeyTable /etc/opendkim/SigningTable
|
||||
chown opendkim:opendkim /etc/opendkim/KeyTable /etc/opendkim/SigningTable
|
||||
chmod 640 /etc/opendkim/KeyTable /etc/opendkim/SigningTable
|
||||
|
||||
if [[ -s "${KEY_PRIV}" && "${BASE_DOMAIN}" != "example.com" ]]; then
|
||||
LINE_KT="${DKIM_SELECTOR}._domainkey.${SYSMAIL_DOMAIN} ${SYSMAIL_DOMAIN}:${DKIM_SELECTOR}:${KEY_PRIV}"
|
||||
LINE_ST="*@${SYSMAIL_DOMAIN} ${DKIM_SELECTOR}._domainkey.${SYSMAIL_DOMAIN}"
|
||||
grep -Fqx "$LINE_KT" /etc/opendkim/KeyTable || echo "$LINE_KT" >> /etc/opendkim/KeyTable
|
||||
grep -Fqx "$LINE_ST" /etc/opendkim/SigningTable || echo "$LINE_ST" >> /etc/opendkim/SigningTable
|
||||
else
|
||||
echo "[i] Kein Private Key unter ${KEY_PRIV} – App-Helper trägt später ein."
|
||||
fi
|
||||
|
||||
# ── Hauptkonfiguration ───────────────────────────────────────────────────────
|
||||
cat >/etc/opendkim.conf <<'CONF'
|
||||
Syslog yes
|
||||
UMask 002
|
||||
Mode sv
|
||||
Socket inet:8891@127.0.0.1
|
||||
PidFile /run/opendkim/opendkim.pid
|
||||
Canonicalization relaxed/simple
|
||||
|
||||
On-BadSignature accept
|
||||
On-Default accept
|
||||
On-KeyNotFound accept
|
||||
On-NoSignature accept
|
||||
|
||||
LogWhy yes
|
||||
OversignHeaders From
|
||||
|
||||
KeyTable /etc/opendkim/KeyTable
|
||||
SigningTable refile:/etc/opendkim/SigningTable
|
||||
ExternalIgnoreList /etc/opendkim/TrustedHosts
|
||||
InternalHosts /etc/opendkim/TrustedHosts
|
||||
|
||||
UserID opendkim:opendkim
|
||||
AutoRestart yes
|
||||
AutoRestartRate 10/1h
|
||||
Background yes
|
||||
DNSTimeout 5
|
||||
SignatureAlgorithm rsa-sha256
|
||||
SyslogSuccess yes
|
||||
CONF
|
||||
|
||||
# ── systemd Drop-in: /run/opendkim sicherstellen ─────────────────────────────
|
||||
install -d -m 0755 /etc/systemd/system/opendkim.service.d
|
||||
cat >/etc/systemd/system/opendkim.service.d/override.conf <<'EOF'
|
||||
[Service]
|
||||
RuntimeDirectory=opendkim
|
||||
RuntimeDirectoryMode=0755
|
||||
EOF
|
||||
|
||||
install -d -o opendkim -g opendkim -m 0755 /run/opendkim
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Root-Helper: DKIM installieren / entfernen + sudoers-Regel
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
install -d -m 0750 /usr/local/sbin
|
||||
|
||||
# --- mailwolt-install-dkim ------------------------------------
|
||||
cat > /usr/local/sbin/mailwolt-install-dkim <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="$1"
|
||||
SELECTOR="$2"
|
||||
SRC_PRIV="$3"
|
||||
SRC_TXT="${4:-}"
|
||||
|
||||
OKDIR="/etc/opendkim"
|
||||
KEYDIR="${OKDIR}/keys/${DOMAIN}"
|
||||
KEYPRI="${KEYDIR}/${SELECTOR}.private"
|
||||
|
||||
install -d -m 0750 -o opendkim -g opendkim "${KEYDIR}"
|
||||
install -m 0600 -o opendkim -g opendkim "${SRC_PRIV}" "${KEYPRI}"
|
||||
|
||||
KT="${OKDIR}/KeyTable"
|
||||
ST="${OKDIR}/SigningTable"
|
||||
touch "$KT" "$ST"
|
||||
chown opendkim:opendkim "$KT" "$ST"
|
||||
chmod 0640 "$KT" "$ST"
|
||||
|
||||
LINE_KT="${SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${SELECTOR}:${KEYPRI}"
|
||||
LINE_ST="*@${DOMAIN} ${SELECTOR}._domainkey.${DOMAIN}"
|
||||
|
||||
grep -Fqx "$LINE_KT" "$KT" || echo "$LINE_KT" >> "$KT"
|
||||
grep -Fqx "$LINE_ST" "$ST" || echo "$LINE_ST" >> "$ST"
|
||||
|
||||
if [[ -n "${SRC_TXT}" && -s "${SRC_TXT}" ]]; then
|
||||
install -d -m 0755 /etc/mailwolt/dns
|
||||
cp -f "${SRC_TXT}" "/etc/mailwolt/dns/dkim-${DOMAIN}.txt"
|
||||
fi
|
||||
|
||||
systemctl is-active --quiet opendkim && systemctl reload opendkim || true
|
||||
echo "OK"
|
||||
EOSH
|
||||
chmod 0750 /usr/local/sbin/mailwolt-install-dkim
|
||||
chown root:root /usr/local/sbin/mailwolt-install-dkim
|
||||
|
||||
# --- 2) mailwolt-remove-dkim ----------------------------------
|
||||
cat >/usr/local/sbin/mailwolt-remove-dkim <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN="$1" # z.B. kunden.tld oder sysmail.example.com
|
||||
SELECTOR="$2" # z.B. mwl1
|
||||
|
||||
OKDIR="/etc/opendkim"
|
||||
KEYDIR="${OKDIR}/keys/${DOMAIN}"
|
||||
KEYPRI="${KEYDIR}/${SELECTOR}.private"
|
||||
KT="${OKDIR}/KeyTable"
|
||||
ST="${OKDIR}/SigningTable"
|
||||
|
||||
# Key-Datei löschen (falls vorhanden)
|
||||
[[ -f "${KEYPRI}" ]] && rm -f "${KEYPRI}"
|
||||
|
||||
# Zeilen aus KeyTable und SigningTable entfernen
|
||||
if [[ -f "$KT" ]]; then
|
||||
tmp="$(mktemp)"; grep -v -F "${SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${SELECTOR}:" "$KT" >"$tmp" && mv "$tmp" "$KT"
|
||||
chown opendkim:opendkim "$KT"; chmod 0640 "$KT"
|
||||
fi
|
||||
if [[ -f "$ST" ]]; then
|
||||
tmp="$(mktemp)"; grep -v -F "*@${DOMAIN} ${SELECTOR}._domainkey.${DOMAIN}" "$ST" >"$tmp" && mv "$tmp" "$ST"
|
||||
chown opendkim:opendkim "$ST"; chmod 0640 "$ST"
|
||||
fi
|
||||
|
||||
# Verzeichnis ggf. aufräumen
|
||||
rmdir "${KEYDIR}" 2>/dev/null || true
|
||||
|
||||
# Dienst neu laden, falls aktiv
|
||||
if systemctl is-active --quiet opendkim; then
|
||||
systemctl reload opendkim || true
|
||||
fi
|
||||
|
||||
echo "OK"
|
||||
EOSH
|
||||
chown root:root /usr/local/sbin/mailwolt-remove-dkim
|
||||
chmod 0750 /usr/local/sbin/mailwolt-remove-dkim
|
||||
|
||||
# ── Dienst + Postfix-Milter aktivieren ─────────────────────────
|
||||
systemctl daemon-reload
|
||||
systemctl enable opendkim || true
|
||||
|
||||
touch /run/mailwolt.need-apply-milters || true
|
||||
|
||||
chgrp _rspamd /etc/rspamd/local.d/*.inc /etc/rspamd/local.d/*.conf || true
|
||||
chmod 0640 /etc/rspamd/local.d/*.inc /etc/rspamd/local.d/*.conf || true
|
||||
|
||||
#/usr/sbin/postconf -e "smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
#/usr/sbin/postconf -e "non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891"
|
||||
|
||||
log "[✓] Rspamd + OpenDKIM eingerichtet (läuft; signiert, sobald Keys vorhanden sind)."
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "OpenDMARC installieren/konfigurieren …"
|
||||
|
||||
# Flags laden
|
||||
set +u
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
set -u
|
||||
OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
|
||||
|
||||
# Paket sicherstellen
|
||||
if ! dpkg -s opendmarc >/dev/null 2>&1; then
|
||||
apt-get update -qq
|
||||
apt-get install -y opendmarc
|
||||
fi
|
||||
|
||||
# Config-Verzeichnisse
|
||||
install -d -m 0755 /etc/opendmarc
|
||||
install -d -m 0755 /run/opendmarc
|
||||
|
||||
# IgnoreHosts
|
||||
cat >/etc/opendmarc/ignore.hosts <<'EOF'
|
||||
127.0.0.1
|
||||
::1
|
||||
localhost
|
||||
EOF
|
||||
chmod 0644 /etc/opendmarc/ignore.hosts
|
||||
|
||||
# Hauptkonfiguration
|
||||
cat >/etc/opendmarc.conf <<'EOF'
|
||||
AuthservID mailwolt
|
||||
TrustedAuthservIDs mailwolt
|
||||
IgnoreHosts /etc/opendmarc/ignore.hosts
|
||||
Syslog true
|
||||
SoftwareHeader true
|
||||
Socket local:/run/opendmarc/opendmarc.sock
|
||||
RejectFailures false
|
||||
EOF
|
||||
chmod 0644 /etc/opendmarc.conf
|
||||
|
||||
# systemd Drop-in für RuntimeDirectory (robust nach Reboot)
|
||||
install -d -m 0755 /etc/systemd/system/opendmarc.service.d
|
||||
cat >/etc/systemd/system/opendmarc.service.d/override.conf <<'EOF'
|
||||
[Service]
|
||||
RuntimeDirectory=opendmarc
|
||||
RuntimeDirectoryMode=0755
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
# Dienst nach Flag
|
||||
if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
|
||||
systemctl enable --now opendmarc
|
||||
else
|
||||
systemctl disable --now opendmarc || true
|
||||
fi
|
||||
|
||||
# Postfix-Milter-Kette konsistent setzen (Rspamd + OpenDKIM + optional OpenDMARC)
|
||||
touch /run/mailwolt.need-apply-milters || true
|
||||
|
||||
log "[✓] OpenDMARC (ENABLE=${OPENDMARC_ENABLE}) bereit."
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "ClamAV (clamav-daemon) installieren/konfigurieren …"
|
||||
|
||||
# Flags laden
|
||||
set +u
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
set -u
|
||||
CLAMAV_ENABLE="${CLAMAV_ENABLE:-0}"
|
||||
|
||||
# Pakete
|
||||
if ! dpkg -s clamav-daemon >/dev/null 2>&1; then
|
||||
apt-get update -qq
|
||||
apt-get install -y clamav clamav-daemon
|
||||
fi
|
||||
|
||||
# Signaturen aktualisieren (erst Freshclam starten)
|
||||
systemctl stop clamav-freshclam 2>/dev/null || true
|
||||
freshclam || true
|
||||
systemctl start clamav-freshclam || true
|
||||
|
||||
# clamd LocalSocket setzen
|
||||
sed -i 's|^#\?LocalSocket .*|LocalSocket /run/clamav/clamd.ctl|' /etc/clamav/clamd.conf || true
|
||||
install -d -m 0755 /run/clamav
|
||||
chown clamav:clamav /run/clamav
|
||||
|
||||
# Dienst nach Flag
|
||||
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
|
||||
systemctl enable --now clamav-daemon
|
||||
else
|
||||
systemctl disable --now clamav-daemon || true
|
||||
fi
|
||||
|
||||
# Rspamd-Integration (nur wenn aktiv)
|
||||
AV_CONF="/etc/rspamd/local.d/antivirus.conf"
|
||||
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
|
||||
cat >"$AV_CONF" <<'EOF'
|
||||
clamav {
|
||||
symbol = "CLAM_VIRUS";
|
||||
type = "clamav";
|
||||
servers = "/run/clamav/clamd.ctl";
|
||||
scan_mime_parts = true;
|
||||
scan_text_mime = true;
|
||||
max_size = 50mb;
|
||||
log_clean = false;
|
||||
action = "reject";
|
||||
}
|
||||
EOF
|
||||
chown root:_rspamd "$AV_CONF" || true
|
||||
chmod 0640 "$AV_CONF" || true
|
||||
systemctl reload rspamd || systemctl restart rspamd
|
||||
else
|
||||
rm -f "$AV_CONF" || true
|
||||
systemctl reload rspamd || true
|
||||
fi
|
||||
|
||||
log "[✓] ClamAV (ENABLE=${CLAMAV_ENABLE}) konfiguriert."
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Fail2Ban installieren/konfigurieren …"
|
||||
|
||||
# Flags laden
|
||||
set +u
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
set -u
|
||||
FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
|
||||
# Paket
|
||||
if ! dpkg -s fail2ban >/dev/null 2>&1; then
|
||||
apt-get update -qq
|
||||
apt-get install -y fail2ban sqlite3
|
||||
fi
|
||||
|
||||
install -d -m 0755 /etc/fail2ban/jail.d
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Basis-Jails (praxisnah)
|
||||
# ---------------------------------------------------------------
|
||||
cat >/etc/fail2ban/jail.d/mailwolt.conf <<'EOF'
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
logpath = /var/log/auth.log
|
||||
|
||||
[postfix]
|
||||
enabled = true
|
||||
logpath = /var/log/mail.log
|
||||
port = smtp,ssmtp,submission,465
|
||||
|
||||
[dovecot]
|
||||
enabled = true
|
||||
logpath = /var/log/mail.log
|
||||
port = pop3,pop3s,imap,imaps,submission,465,587,993
|
||||
|
||||
[rspamd-controller]
|
||||
enabled = true
|
||||
port = 11334
|
||||
filter = rspamd
|
||||
logpath = /var/log/rspamd/rspamd.log
|
||||
maxretry = 5
|
||||
EOF
|
||||
|
||||
# einfacher Filter für Rspamd-Controller
|
||||
if [ ! -f /etc/fail2ban/filter.d/rspamd.conf ]; then
|
||||
cat >/etc/fail2ban/filter.d/rspamd.conf <<'EOF'
|
||||
[Definition]
|
||||
failregex = .*Authentication failed for user.* from <HOST>
|
||||
ignoreregex =
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Fail2Ban-Backend auf SQLite umstellen
|
||||
# ---------------------------------------------------------------
|
||||
log "SQLite-Backend aktivieren …"
|
||||
|
||||
cat >/etc/fail2ban/fail2ban.local <<'EOF'
|
||||
[Definition]
|
||||
loglevel = INFO
|
||||
logtarget = /var/log/fail2ban.log
|
||||
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
|
||||
dbpurgeage = 86400
|
||||
EOF
|
||||
|
||||
# Datenbankverzeichnis sicherstellen
|
||||
install -d -o fail2ban -g fail2ban -m 0750 /var/lib/fail2ban
|
||||
|
||||
# Falls DB nicht existiert, Dummy anlegen (wird vom Dienst erweitert)
|
||||
if [ ! -f /var/lib/fail2ban/fail2ban.sqlite3 ]; then
|
||||
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "VACUUM;"
|
||||
fi
|
||||
chown fail2ban:fail2ban /var/lib/fail2ban/fail2ban.sqlite3
|
||||
chmod 0640 /var/lib/fail2ban/fail2ban.sqlite3
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# sudoers für Web-UI
|
||||
# ---------------------------------------------------------------
|
||||
# Fail2Ban Blacklist-Jail
|
||||
cat >/etc/fail2ban/jail.d/mailwolt-blacklist.local <<'EOF'
|
||||
[mailwolt-blacklist]
|
||||
enabled = true
|
||||
filter = none
|
||||
port = anyport
|
||||
bantime = -1
|
||||
findtime = 1
|
||||
maxretry = 1
|
||||
EOF
|
||||
|
||||
cat >/etc/fail2ban/filter.d/none.conf <<'EOF'
|
||||
[Definition]
|
||||
failregex =
|
||||
ignoreregex =
|
||||
EOF
|
||||
|
||||
chmod 0640 /etc/fail2ban/filter.d/none.conf
|
||||
|
||||
|
||||
SUDOERS_F2B="/etc/sudoers.d/mailwolt-fail2ban"
|
||||
cat > "${SUDOERS_F2B}" <<'EOF'
|
||||
Defaults:www-data !requiretty
|
||||
www-data ALL=(root) NOPASSWD: \
|
||||
/usr/bin/fail2ban-client, \
|
||||
/usr/bin/fail2ban-client ping, \
|
||||
/usr/bin/fail2ban-client status, \
|
||||
/usr/bin/fail2ban-client status *, \
|
||||
/usr/bin/fail2ban-client get *, \
|
||||
/usr/bin/fail2ban-client set * banip *, \
|
||||
/usr/bin/fail2ban-client set * unbanip *, \
|
||||
/usr/bin/fail2ban-client reload, \
|
||||
/usr/bin/journalctl, \
|
||||
/bin/journalctl, \
|
||||
/usr/bin/zgrep, \
|
||||
/bin/zgrep, \
|
||||
/usr/bin/grep, \
|
||||
/bin/grep, \
|
||||
/usr/bin/tail, \
|
||||
/bin/tail, \
|
||||
/usr/bin/sqlite3, \
|
||||
/usr/bin/tee /etc/fail2ban/jail.d/*
|
||||
EOF
|
||||
chown root:root "${SUDOERS_F2B}"
|
||||
chmod 440 "${SUDOERS_F2B}"
|
||||
|
||||
if ! visudo -c -f "${SUDOERS_F2B}" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in ${SUDOERS_F2B} – entferne Datei."
|
||||
rm -f "${SUDOERS_F2B}"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Dienst aktivieren/deaktivieren
|
||||
# ---------------------------------------------------------------
|
||||
if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
|
||||
systemctl enable --now fail2ban
|
||||
else
|
||||
systemctl disable --now fail2ban || true
|
||||
fi
|
||||
|
||||
log "[✓] Fail2Ban (ENABLE=${FAIL2BAN_ENABLE}) bereit."
|
||||
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
#log "Fail2Ban installieren/konfigurieren …"
|
||||
#
|
||||
## Flags laden
|
||||
#set +u
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
#set -u
|
||||
#FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
#
|
||||
## Paket
|
||||
#if ! dpkg -s fail2ban >/dev/null 2>&1; then
|
||||
# apt-get update -qq
|
||||
# apt-get install -y fail2ban
|
||||
#fi
|
||||
#
|
||||
#install -d -m 0755 /etc/fail2ban/jail.d
|
||||
#
|
||||
## Basis-Jails (praxisnah)
|
||||
#cat >/etc/fail2ban/jail.d/mailwolt.conf <<'EOF'
|
||||
#[DEFAULT]
|
||||
#bantime = 1h
|
||||
#findtime = 10m
|
||||
#maxretry = 5
|
||||
#backend = auto
|
||||
#
|
||||
#[sshd]
|
||||
#enabled = true
|
||||
#port = ssh
|
||||
#logpath = /var/log/auth.log
|
||||
#
|
||||
#[postfix]
|
||||
#enabled = true
|
||||
#logpath = /var/log/mail.log
|
||||
#port = smtp,ssmtp,submission,465
|
||||
#
|
||||
#[dovecot]
|
||||
#enabled = true
|
||||
#logpath = /var/log/mail.log
|
||||
#port = pop3,pop3s,imap,imaps,submission,465,587,993
|
||||
#
|
||||
#[rspamd-controller]
|
||||
#enabled = true
|
||||
#port = 11334
|
||||
#filter = rspamd
|
||||
#logpath = /var/log/rspamd/rspamd.log
|
||||
#maxretry = 5
|
||||
#EOF
|
||||
#
|
||||
## einfacher Filter für Rspamd-Controller
|
||||
#if [ ! -f /etc/fail2ban/filter.d/rspamd.conf ]; then
|
||||
# cat >/etc/fail2ban/filter.d/rspamd.conf <<'EOF'
|
||||
#[Definition]
|
||||
#failregex = .*Authentication failed for user.* from <HOST>
|
||||
#ignoreregex =
|
||||
#EOF
|
||||
#fi
|
||||
#
|
||||
#SUDOERS_F2B="/etc/sudoers.d/mailwolt-fail2ban"
|
||||
#cat > "${SUDOERS_F2B}" <<'EOF'
|
||||
#www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status *
|
||||
#EOF
|
||||
#chown root:root "${SUDOERS_F2B}"
|
||||
#chmod 440 "${SUDOERS_F2B}"
|
||||
#
|
||||
#if ! visudo -c -f "${SUDOERS_F2B}" >/dev/null 2>&1; then
|
||||
# echo "[!] Ungültiger sudoers-Eintrag in ${SUDOERS_F2B} – entferne Datei."
|
||||
# rm -f "${SUDOERS_F2B}"
|
||||
#fi
|
||||
#
|
||||
#sudo tee /etc/sudoers.d/mailwolt-fail2ban >/dev/null <<'EOF'
|
||||
#www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status, /usr/bin/fail2ban-client status *
|
||||
#EOF
|
||||
#sudo visudo -cf /etc/sudoers.d/mailwolt-fail2ban
|
||||
#
|
||||
## Dienst nach Flag
|
||||
#if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
|
||||
# systemctl enable --now fail2ban
|
||||
#else
|
||||
# systemctl disable --now fail2ban || true
|
||||
#fi
|
||||
#
|
||||
#log "[✓] Fail2Ban (ENABLE=${FAIL2BAN_ENABLE}) bereit."
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
# nur ausführen, wenn vorherige Schritte das Flag gesetzt haben
|
||||
if [[ -f /run/mailwolt.need-apply-milters ]]; then
|
||||
if command -v /usr/local/sbin/mailwolt-apply-milters >/dev/null 2>&1; then
|
||||
log "Setze Postfix-Milter-Kette (Rspamd/OpenDKIM[/OpenDMARC]) …"
|
||||
/usr/local/sbin/mailwolt-apply-milters || true
|
||||
else
|
||||
# Fallback (ident wie im Tool)
|
||||
/usr/sbin/postconf -e "milter_default_action = accept"
|
||||
/usr/sbin/postconf -e "milter_protocol = 6"
|
||||
CHAIN="inet:127.0.0.1:11333, inet:127.0.0.1:8891"
|
||||
systemctl is-active --quiet opendmarc && CHAIN="$CHAIN, inet:127.0.0.1:8893" || true
|
||||
/usr/sbin/postconf -e "smtpd_milters = $CHAIN"
|
||||
/usr/sbin/postconf -e "non_smtpd_milters = $CHAIN"
|
||||
systemctl reload postfix || true
|
||||
fi
|
||||
rm -f /run/mailwolt.need-apply-milters || true
|
||||
log "[✓] Milter-Kette angewandt."
|
||||
else
|
||||
log "Milter-Kette: kein Bedarf (Flag nicht gesetzt) – überspringe."
|
||||
fi
|
||||
|
|
@ -0,0 +1,528 @@
|
|||
#!/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"
|
||||
#}
|
||||
|
||||
# $1=host, $2=outfile, $3=default_flag (default|nodefault)
|
||||
build_site_http_only(){
|
||||
local host="$1" outfile="$2" def_flag="${3:-default}"
|
||||
|
||||
local def=""
|
||||
if [[ "${DEV_MODE}" = "1" && "${def_flag}" = "default" ]]; then
|
||||
def=" default_server"
|
||||
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};
|
||||
|
||||
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 (npm run dev)
|
||||
location = /vite-hmr {
|
||||
proxy_pass http://127.0.0.1:5173/vite-hmr;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location ^~ /@vite/ { proxy_pass http://127.0.0.1:5173/@vite/; proxy_http_version 1.1; 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_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; }
|
||||
location ^~ /resources/ { proxy_pass http://127.0.0.1:5173/resources/; proxy_http_version 1.1; 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
|
||||
}
|
||||
|
||||
# ── Builder Webmail: nur /webmail/* erlaubt, root → redirect ───────────────
|
||||
build_webmail_http_only(){
|
||||
local host="$1" outfile="$2" def_flag="${3:-nodefault}"
|
||||
local def=""
|
||||
[[ "${DEV_MODE}" = "1" && "${def_flag}" = "default" ]] && def=" default_server"
|
||||
[[ -z "${host}" || "${host}" = "_" ]] && host="_"
|
||||
cat > "$outfile" <<CONF
|
||||
# --- ${host} : Webmail (domain-routing, volle Laravel-App) ---
|
||||
server {
|
||||
listen 80${def};
|
||||
listen [::]:80${def};
|
||||
server_name ${host};
|
||||
location ^~ /.well-known/acme-challenge/ { root ${ACME_ROOT}; allow all; }
|
||||
root ${APP_DIR}/public;
|
||||
index index.php;
|
||||
access_log /var/log/nginx/${host/_/__}_webmail_access.log;
|
||||
error_log /var/log/nginx/${host/_/__}_webmail_error.log;
|
||||
client_max_body_size 25m;
|
||||
location = / { return 301 http://\$host/login; }
|
||||
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 ~* \.(css|js|ico|svg|woff2?|ttf|jpg|jpeg|png|gif)\$ { expires 30d; access_log off; }
|
||||
}
|
||||
CONF
|
||||
}
|
||||
|
||||
build_webmail_tls(){
|
||||
local host="$1" cert_dir="$2" outfile="$3"
|
||||
cat > "$outfile" <<CONF
|
||||
# --- ${host} : Webmail TLS (domain-routing, volle Laravel-App) ---
|
||||
server {
|
||||
listen 80; listen [::]:80; server_name ${host};
|
||||
location ^~ /.well-known/acme-challenge/ { root ${ACME_ROOT}; allow all; }
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
server {
|
||||
listen 443 ssl${NGINX_HTTP2_SUFFIX}; listen [::]:443 ssl${NGINX_HTTP2_SUFFIX};
|
||||
server_name ${host};
|
||||
ssl_certificate ${cert_dir}/fullchain.pem;
|
||||
ssl_certificate_key ${cert_dir}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
location ^~ /.well-known/acme-challenge/ { root ${ACME_ROOT}; allow all; }
|
||||
root ${APP_DIR}/public;
|
||||
index index.php;
|
||||
access_log /var/log/nginx/${host}_webmail_ssl_access.log;
|
||||
error_log /var/log/nginx/${host}_webmail_ssl_error.log;
|
||||
client_max_body_size 25m;
|
||||
location = / { return 301 https://\$host/login; }
|
||||
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 ~* \.(css|js|ico|svg|woff2?|ttf|jpg|jpeg|png|gif)\$ { expires 30d; access_log off; }
|
||||
}
|
||||
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
|
||||
build_site_http_only "_" "$UI_SITE" "default"
|
||||
build_webmail_http_only "_" "$WEBMAIL_SITE" "nodefault"
|
||||
else
|
||||
if [[ "${PROXY_MODE:-0}" -eq 1 ]]; then
|
||||
build_site_http_only "$UI_HOST" "$UI_SITE"
|
||||
build_webmail_http_only "$WEBMAIL_HOST" "$WEBMAIL_SITE"
|
||||
else
|
||||
build_site_tls "$UI_HOST" "/etc/ssl/ui" "$UI_SITE"
|
||||
build_webmail_tls "$WEBMAIL_HOST" "/etc/ssl/webmail" "$WEBMAIL_SITE"
|
||||
fi
|
||||
fi
|
||||
#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
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
ACME_WEBROOT="/var/www/letsencrypt"
|
||||
install -d -m 0755 "${ACME_WEBROOT}/.well-known/acme-challenge"
|
||||
|
||||
# Staging optional (verbraucht kein Live-Limit)
|
||||
CERTBOT_EXTRA=()
|
||||
LE_STAGING="${LE_STAGING:-0}"
|
||||
[[ "$LE_STAGING" = "1" ]] && CERTBOT_EXTRA+=(--test-cert)
|
||||
|
||||
# Einheitliche LE-Mail (Fallback)
|
||||
LE_MAIL="${LE_EMAIL:-admin@${BASE_DOMAIN}}"
|
||||
|
||||
resolve_ok() {
|
||||
local host="$1"
|
||||
local pats=()
|
||||
[[ -n "${SERVER_PUBLIC_IPV4:-}" ]] && pats+=("${SERVER_PUBLIC_IPV4//./\\.}")
|
||||
[[ -n "${SERVER_PUBLIC_IPV6:-}" ]] && pats+=("${SERVER_PUBLIC_IPV6//:/\\:}")
|
||||
[[ ${#pats[@]} -eq 0 ]] && return 0
|
||||
getent ahosts "$host" | awk '{print $1}' | sort -u \
|
||||
| grep -Eq "^($(IFS='|'; echo "${pats[*]}"))$"
|
||||
}
|
||||
|
||||
probe_http() {
|
||||
local host="$1"
|
||||
echo test > "${ACME_WEBROOT}/.well-known/acme-challenge/_probe"
|
||||
curl -fsS --max-time 5 -4 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null \
|
||||
|| curl -fsS --max-time 5 -6 "http://${host}/.well-known/acme-challenge/_probe" >/dev/null
|
||||
}
|
||||
|
||||
issue() {
|
||||
local host="${1:-}"
|
||||
[[ -z "$host" ]] && return 0
|
||||
|
||||
echo "[i] Versuche LE für ${host} …"
|
||||
|
||||
if ! resolve_ok "$host"; then
|
||||
echo "[!] DNS zeigt (noch) nicht hierher – überspringe: ${host}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! probe_http "$host"; then
|
||||
echo "[!] ACME-HTTP-Check für ${host} fehlgeschlagen (Port 80/IPv6/Firewall/Nginx prüfen)."
|
||||
# wir versuchen trotzdem – Certbot meldet sich, falls es scheitert
|
||||
fi
|
||||
|
||||
EXTRA_ARGS=()
|
||||
# Für MX den Key wiederverwenden → stabiler TLSA (3 1 1)
|
||||
[[ "$host" == "${MAIL_HOSTNAME}" ]] && EXTRA_ARGS+=(--reuse-key)
|
||||
|
||||
# WICHTIG: Deploy-Wrapper anhängen, damit Symlinks/Nginx gesetzt werden
|
||||
certbot certonly \
|
||||
--agree-tos -m "${LE_MAIL}" --non-interactive \
|
||||
--webroot -w "${ACME_WEBROOT}" -d "${host}" \
|
||||
--deploy-hook /usr/local/sbin/mailwolt-deploy.sh \
|
||||
"${EXTRA_ARGS[@]}" "${CERTBOT_EXTRA[@]}" || true
|
||||
}
|
||||
|
||||
if [[ "${BASE_DOMAIN}" != "example.com" ]]; then
|
||||
issue "${UI_HOST:-}"
|
||||
issue "${WEBMAIL_HOST:-}"
|
||||
issue "${MAIL_HOSTNAME:-}"
|
||||
|
||||
# Nginx nur neu laden, wenn aktiv
|
||||
if systemctl is-active --quiet nginx; then
|
||||
systemctl reload nginx || true
|
||||
fi
|
||||
else
|
||||
echo "[i] BASE_DOMAIN=example.com – LE wird übersprungen."
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# FIX: Validierung & Reparatur des Mail-Zertifikats
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
MAIL_SSL_DIR="/etc/ssl/mail"
|
||||
install -d -m 0755 "$MAIL_SSL_DIR"
|
||||
|
||||
MAIL_CERT="${MAIL_SSL_DIR}/fullchain.pem"
|
||||
MAIL_KEY="${MAIL_SSL_DIR}/privkey.pem"
|
||||
|
||||
HOST="${MAIL_HOSTNAME:-}"
|
||||
LE_DIR=""
|
||||
[[ -n "$HOST" ]] && LE_DIR="/etc/letsencrypt/live/${HOST}"
|
||||
|
||||
need_fix=0
|
||||
|
||||
# Ist der vorhandene Key gültig? (leer/nicht vorhanden/ungültig -> fix)
|
||||
if [[ ! -s "$MAIL_KEY" ]] || ! openssl pkey -in "$MAIL_KEY" -noout >/dev/null 2>&1; then
|
||||
need_fix=1
|
||||
fi
|
||||
|
||||
# Wenn Fix nötig: aus Let's Encrypt Live kopieren
|
||||
if [[ $need_fix -eq 1 ]]; then
|
||||
echo "[!] Ungültiger oder fehlender Mail-Private-Key – versuche Reparatur …"
|
||||
if [[ -n "$LE_DIR" && -r "${LE_DIR}/privkey.pem" && -r "${LE_DIR}/fullchain.pem" ]]; then
|
||||
cp -f "${LE_DIR}/privkey.pem" "$MAIL_KEY"
|
||||
cp -f "${LE_DIR}/fullchain.pem" "$MAIL_CERT"
|
||||
chown root:root "$MAIL_CERT" "$MAIL_KEY"
|
||||
chmod 600 "$MAIL_KEY"
|
||||
chmod 644 "$MAIL_CERT"
|
||||
echo "[+] Zertifikate neu kopiert aus ${LE_DIR}."
|
||||
# Reload NICHT sofort – flaggen für 90-services
|
||||
touch /run/mailwolt.need-dovecot-reload
|
||||
else
|
||||
echo "[!] Konnte ${LE_DIR}/privkey.pem oder fullchain.pem nicht lesen – bitte prüfen."
|
||||
fi
|
||||
else
|
||||
echo "[✓] Mail-Zertifikat & -Key sind gültig."
|
||||
fi
|
||||
|
||||
# Optionaler Live-Check (nur wenn Host gesetzt)
|
||||
if [[ -n "$HOST" ]]; then
|
||||
if openssl s_client -connect "${HOST}:993" -servername "${HOST}" </dev/null 2>/dev/null \
|
||||
| grep -q "Verify return code: 0"; then
|
||||
echo "[✓] TLS-Handshake erfolgreich auf imaps://${HOST}:993."
|
||||
else
|
||||
echo "[!] TLS-Handshake auf imaps://${HOST}:993 fehlgeschlagen (Dovecot Reload folgt in 90-services, falls Flag gesetzt)."
|
||||
fi
|
||||
fi
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
#!/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_TIMEZONE "${APP_TZ:-UTC}"
|
||||
|
||||
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 "false" # wird nach SSL-Setup auf true gesetzt
|
||||
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
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Update-Wrapper & Sudoers …"
|
||||
|
||||
WRAPPER="/usr/local/sbin/mailwolt-update"
|
||||
LOGFILE="/var/log/mailwolt-update.log"
|
||||
STATEDIR="/var/lib/mailwolt/update"
|
||||
SUDOERS="/etc/sudoers.d/mailwolt-update"
|
||||
VERSION_FILE="/var/lib/mailwolt/version"
|
||||
SUDOERS_SERVICES="/etc/sudoers.d/mailwolt-services"
|
||||
SUDOERS_ARTISAN="/etc/sudoers.d/mailwolt-artisan"
|
||||
|
||||
# Kandidaten: wo liegt update.sh?
|
||||
CANDIDATES=(
|
||||
/opt/mailwolt-installer/scripts/update.sh
|
||||
/mailwolt-installer/scripts/update.sh
|
||||
/usr/local/lib/mailwolt/update.sh
|
||||
)
|
||||
|
||||
# State/Log vorbereiten
|
||||
install -d -m 0755 "$(dirname "$LOGFILE")"
|
||||
install -d -m 0755 "$STATEDIR"
|
||||
: > "$LOGFILE" || true
|
||||
chmod 0644 "$LOGFILE"
|
||||
|
||||
# Wrapper erzeugen
|
||||
cat > "$WRAPPER" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
LOG="/var/log/mailwolt-update.log"
|
||||
STATE_DIR="/var/lib/mailwolt/update"
|
||||
APP_DIR="/var/www/mailwolt"
|
||||
WEB_USER="www-data"
|
||||
|
||||
CANDIDATES=(
|
||||
/opt/mailwolt-installer/scripts/update.sh
|
||||
/mailwolt-installer/scripts/update.sh
|
||||
/usr/local/lib/mailwolt/update.sh
|
||||
)
|
||||
|
||||
install -d -m 0755 "$(dirname "$LOG")" "$STATE_DIR" /var/lib/mailwolt
|
||||
: > "$LOG" || true
|
||||
chmod 0644 "$LOG"
|
||||
|
||||
echo "running" > "$STATE_DIR/state"
|
||||
|
||||
{
|
||||
echo "===== $(date -Is) :: Update gestartet ====="
|
||||
|
||||
# --- Update-Script finden --------------------------------------------------
|
||||
SCRIPT=""
|
||||
for p in "${CANDIDATES[@]}"; do
|
||||
if [[ -x "$p" ]]; then SCRIPT="$p"; break; fi
|
||||
if [[ -f "$p" && -r "$p" ]]; then SCRIPT="$p"; break; fi
|
||||
done
|
||||
|
||||
if [[ -z "$SCRIPT" ]]; then
|
||||
echo "[!] update.sh nicht gefunden (versucht: ${CANDIDATES[*]})"
|
||||
rc=127
|
||||
else
|
||||
echo "[i] benutze: $SCRIPT"
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "[!] Bitte als root ausführen"
|
||||
rc=1
|
||||
else
|
||||
if [[ -x "$SCRIPT" ]]; then
|
||||
ALLOW_DIRTY=1 "$SCRIPT"
|
||||
else
|
||||
ALLOW_DIRTY=1 bash "$SCRIPT"
|
||||
fi
|
||||
rc=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "===== $(date -Is) :: Update-Script beendet (rc=$rc) ====="
|
||||
|
||||
# --- Nach dem Update: Assets neu bauen & Laravel optimieren ---------------
|
||||
if [ -d "$APP_DIR" ]; then
|
||||
cd "$APP_DIR" || exit 1
|
||||
|
||||
echo "[i] Führe Composer aus (falls vorhanden) ..."
|
||||
if [ -f composer.json ]; then
|
||||
sudo -u "$WEB_USER" composer install --no-dev --prefer-dist --no-interaction -q || true
|
||||
fi
|
||||
|
||||
echo "[i] Baue Frontend-Assets neu ..."
|
||||
if command -v npm >/dev/null 2>&1 && [ -f package.json ]; then
|
||||
sudo -u "$WEB_USER" npm ci --silent || true
|
||||
sudo -u "$WEB_USER" npm run build --silent || true
|
||||
fi
|
||||
|
||||
echo "[i] Führe Migrationen & Cache-Optimierungen durch ..."
|
||||
sudo -u "$WEB_USER" php artisan migrate --force || true
|
||||
sudo -u "$WEB_USER" php artisan config:cache || true
|
||||
sudo -u "$WEB_USER" php artisan optimize:clear || true
|
||||
sudo -u "$WEB_USER" php artisan route:cache || true
|
||||
sudo -u "$WEB_USER" php artisan view:cache || true
|
||||
|
||||
echo "[i] Hebe Wartungsmodus auf ..."
|
||||
sudo -u "$WEB_USER" php artisan up >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# --- Version aktualisieren -------------------------------------------------
|
||||
echo "[i] Aktualisiere Version ..."
|
||||
if command -v git >/dev/null 2>&1; then
|
||||
SRC="/var/www/mailwolt"
|
||||
if [ ! -d "$SRC/.git" ]; then
|
||||
SRC="/opt/mailwolt-installer"
|
||||
fi
|
||||
|
||||
git config --global --add safe.directory "$SRC" || true
|
||||
|
||||
if [ -f "$SRC/.git/shallow" ]; then
|
||||
git -C "$SRC" fetch --unshallow --quiet || true
|
||||
fi
|
||||
git -C "$SRC" fetch --tags --quiet origin || true
|
||||
|
||||
raw="$(git -C "$SRC" describe --tags --always --dirty 2>/dev/null || echo "unknown")"
|
||||
norm="$(printf '%s' "$raw" | sed -E 's/^[vV]//; s/-.*$//')"
|
||||
|
||||
printf '%s\n' "$raw" > /var/lib/mailwolt/version_raw
|
||||
printf '%s\n' "$norm" > /var/lib/mailwolt/version
|
||||
chmod 0644 /var/lib/mailwolt/version_raw /var/lib/mailwolt/version
|
||||
|
||||
echo "[i] Version aktualisiert: raw=$raw norm=$norm (Quelle: $SRC)"
|
||||
else
|
||||
echo "unknown" > /var/lib/mailwolt/version_raw
|
||||
echo "0.0.0" > /var/lib/mailwolt/version
|
||||
chmod 0644 /var/lib/mailwolt/version_raw /var/lib/mailwolt/version
|
||||
fi
|
||||
|
||||
# --- Services neu starten --------------------------------------------------
|
||||
echo "[i] Starte MailWolt-Dienste neu ..."
|
||||
sudo -u "$WEB_USER" php artisan mailwolt:restart-services || true
|
||||
|
||||
# --- Abschluss -------------------------------------------------------------
|
||||
printf '%s\n' "$rc" > "$STATE_DIR/rc"
|
||||
echo "done" > "$STATE_DIR/state"
|
||||
echo "===== $(date -Is) :: Update beendet ====="
|
||||
exit "$rc"
|
||||
|
||||
} | tee -a "$LOG"
|
||||
EOF
|
||||
|
||||
chmod 0755 "$WRAPPER"
|
||||
chown root:root "$WRAPPER"
|
||||
|
||||
# Sudoers: www-data (Laravel) & mailwolt dürfen den Wrapper laufen lassen
|
||||
cat > "$SUDOERS" <<'EOF'
|
||||
Defaults!/usr/local/sbin/mailwolt-update !requiretty
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update
|
||||
EOF
|
||||
|
||||
chown root:root "$SUDOERS"
|
||||
chmod 440 "$SUDOERS"
|
||||
|
||||
if ! visudo -c -f "$SUDOERS" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in $SUDOERS – entferne Datei."
|
||||
rm -f "$SUDOERS"
|
||||
fi
|
||||
|
||||
cat > "$SUDOERS_SERVICES" <<'EOF'
|
||||
Defaults!/usr/bin/systemctl !requiretty
|
||||
|
||||
Cmnd_Alias MW_SERVICES = \
|
||||
/usr/bin/systemctl reload nginx.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart nginx.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart postfix.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart dovecot.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart rspamd.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart opendkim.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart opendmarc.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart clamav-daemon.service, \
|
||||
/usr/bin/systemctl try-reload-or-restart redis-server.service
|
||||
|
||||
www-data ALL=(root) NOPASSWD: MW_SERVICES
|
||||
EOF
|
||||
|
||||
chmod 440 "$SUDOERS_SERVICES"
|
||||
chown root:root "$SUDOERS_SERVICES"
|
||||
|
||||
# Prüfen, ob Syntax gültig ist
|
||||
if ! visudo -c -f "$SUDOERS_SERVICES" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in $SUDOERS_SERVICES – entferne Datei."
|
||||
rm -f "$SUDOERS_SERVICES"
|
||||
else
|
||||
echo "[✓] Sudoers für Dienststeuerung angelegt: $SUDOERS_SERVICES"
|
||||
fi
|
||||
|
||||
# Version-File initial anlegen, falls nicht existiert
|
||||
if [[ ! -f "$VERSION_FILE" ]]; then
|
||||
echo "unknown" > "$VERSION_FILE"
|
||||
chmod 0644 "$VERSION_FILE"
|
||||
fi
|
||||
|
||||
cat > "$SUDOERS_ARTISAN" <<'EOF'
|
||||
# mailwolt darf artisan im App-Verzeichnis als www-data ausführen (ohne Passwort)
|
||||
mailwolt ALL=(www-data) NOPASSWD: /usr/bin/php /var/www/mailwolt/artisan *
|
||||
EOF
|
||||
|
||||
chown root:root "$SUDOERS_ARTISAN"
|
||||
chmod 440 "$SUDOERS_ARTISAN"
|
||||
|
||||
if ! visudo -c -f "$SUDOERS_ARTISAN" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in $SUDOERS_ARTISAN – entferne Datei."
|
||||
rm -f "$SUDOERS_ARTISAN"
|
||||
else
|
||||
echo "[✓] Sudoers für Artisan-Kommandos angelegt: $SUDOERS_ARTISAN"
|
||||
fi
|
||||
|
||||
log "[✓] Update-Wrapper bereit: $WRAPPER"
|
||||
log "[✓] Version wird unter $VERSION_FILE gespeichert"
|
||||
|
||||
# ─── Installer-Wrapper ────────────────────────────────────────────────────────
|
||||
INSTALL_WRAPPER="/usr/local/sbin/mailwolt-install"
|
||||
INSTALL_SUDOERS="/etc/sudoers.d/mailwolt-install"
|
||||
INSTALL_STATE_DIR="/var/lib/mailwolt/install"
|
||||
INSTALL_LOG="/var/log/mailwolt-install.log"
|
||||
|
||||
# State/Log vorbereiten
|
||||
install -d -m 0755 "$INSTALL_STATE_DIR"
|
||||
: > "$INSTALL_LOG" || true
|
||||
chmod 0644 "$INSTALL_LOG"
|
||||
|
||||
# Installer-Wrapper aus scripts/install-wrapper.sh kopieren
|
||||
INSTALL_SRC=""
|
||||
for candidate in \
|
||||
"$(dirname "$0")/install-wrapper.sh" \
|
||||
/opt/mailwolt-installer/scripts/install-wrapper.sh \
|
||||
/var/www/mailwolt/mailwolt-installer/scripts/install-wrapper.sh; do
|
||||
[[ -f "$candidate" ]] && INSTALL_SRC="$candidate" && break
|
||||
done
|
||||
|
||||
if [[ -n "$INSTALL_SRC" ]]; then
|
||||
cp "$INSTALL_SRC" "$INSTALL_WRAPPER"
|
||||
chmod 0755 "$INSTALL_WRAPPER"
|
||||
chown root:root "$INSTALL_WRAPPER"
|
||||
echo "[✓] Installer-Wrapper angelegt: $INSTALL_WRAPPER"
|
||||
else
|
||||
echo "[!] install-wrapper.sh nicht gefunden – Installer-Wrapper wird übersprungen."
|
||||
fi
|
||||
|
||||
# Sudoers: www-data & mailwolt dürfen den Installer-Wrapper laufen lassen
|
||||
cat > "$INSTALL_SUDOERS" <<'SUDOEOF'
|
||||
Defaults!/usr/local/sbin/mailwolt-install !requiretty
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install *
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-install *
|
||||
SUDOEOF
|
||||
|
||||
chown root:root "$INSTALL_SUDOERS"
|
||||
chmod 440 "$INSTALL_SUDOERS"
|
||||
|
||||
if ! visudo -c -f "$INSTALL_SUDOERS" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in $INSTALL_SUDOERS – entferne Datei."
|
||||
rm -f "$INSTALL_SUDOERS"
|
||||
else
|
||||
echo "[✓] Sudoers für Installer angelegt: $INSTALL_SUDOERS"
|
||||
fi
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "systemd Units (Reverb / Scheduler / Queue / Mail) …"
|
||||
|
||||
cat > /etc/systemd/system/${APP_USER}-ws.service <<EOF
|
||||
[Unit]
|
||||
Description=${APP_NAME} WebSocket Backend
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=NODE_ENV=production WS_PORT=8080
|
||||
User=${APP_USER}
|
||||
Group=${APP_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -f .env'
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -d vendor'
|
||||
ExecStart=/usr/bin/php artisan reverb:start --host=127.0.0.1 --port=8080 --no-interaction
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
StandardOutput=append:/var/log/${APP_USER}-ws.log
|
||||
StandardError=append:/var/log/${APP_USER}-ws.log
|
||||
KillSignal=SIGINT
|
||||
TimeoutStopSec=15
|
||||
UMask=0002
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/${APP_USER}-schedule.service <<EOF
|
||||
[Unit]
|
||||
Description=${APP_NAME} Laravel Scheduler
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${APP_USER}
|
||||
Group=${APP_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -f .env'
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -d vendor'
|
||||
ExecStart=/usr/bin/php artisan schedule:work
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
StandardOutput=append:/var/log/${APP_USER}-schedule.log
|
||||
StandardError=append:/var/log/${APP_USER}-schedule.log
|
||||
KillSignal=SIGINT
|
||||
TimeoutStopSec=15
|
||||
UMask=0002
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/${APP_USER}-queue.service <<EOF
|
||||
[Unit]
|
||||
Description=${APP_NAME} Queue Worker
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${APP_USER}
|
||||
Group=${APP_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -f .env'
|
||||
ExecStartPre=/usr/bin/bash -lc 'test -d vendor'
|
||||
ExecStart=/usr/bin/php artisan queue:work --queue=default,notify --tries=1
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
StandardOutput=append:/var/log/${APP_USER}-queue.log
|
||||
StandardError=append:/var/log/${APP_USER}-queue.log
|
||||
KillSignal=SIGINT
|
||||
TimeoutStopSec=15
|
||||
UMask=0002
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
chown root:root /etc/systemd/system/${APP_USER}-*.service
|
||||
chmod 644 /etc/systemd/system/${APP_USER}-*.service
|
||||
touch /var/log/${APP_USER}-ws.log /var/log/${APP_USER}-schedule.log /var/log/${APP_USER}-queue.log
|
||||
chown ${APP_USER}:${APP_GROUP} /var/log/${APP_USER}-*.log
|
||||
chmod 664 /var/log/${APP_USER}-*.log
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
# App-Dienste
|
||||
if sudo -u "$APP_USER" -H bash -lc "cd ${APP_DIR} && php artisan list --no-ansi | grep -qE '(^| )reverb:start( |$)'"; then
|
||||
systemctl enable --now ${APP_USER}-ws
|
||||
else
|
||||
systemctl disable --now ${APP_USER}-ws >/dev/null 2>&1 || true
|
||||
fi
|
||||
systemctl enable --now ${APP_USER}-schedule
|
||||
systemctl enable --now ${APP_USER}-queue
|
||||
|
||||
# Mail-Dienste starten
|
||||
systemctl enable --now rspamd opendkim postfix dovecot || true
|
||||
|
||||
# PHP-FPM: Unit erkennen, enable + (re)load
|
||||
enable_and_touch_php_fpm() {
|
||||
for u in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm php7.4-fpm php-fpm; do
|
||||
if systemctl list-unit-files | grep -q "^${u}\.service"; then
|
||||
systemctl enable --now "$u" || true
|
||||
systemctl reload "$u" || systemctl restart "$u" || true
|
||||
echo "[i] PHP-FPM unit: $u"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "[!] Keine passende php-fpm Unit gefunden."
|
||||
}
|
||||
enable_and_touch_php_fpm
|
||||
|
||||
# Falls in 80-app.sh DKIM installiert wurde: jetzt einmal reloaden
|
||||
if [[ -e /run/mailwolt.need-opendkim-reload ]]; then
|
||||
systemctl reload opendkim || true
|
||||
rm -f /run/mailwolt.need-opendkim-reload || true
|
||||
fi
|
||||
|
||||
# Falls Zert-Fix markiert ist: Dovecot neu laden
|
||||
if [[ -e /run/mailwolt.need-dovecot-reload ]]; then
|
||||
systemctl reload dovecot || true
|
||||
rm -f /run/mailwolt.need-dovecot-reload || true
|
||||
fi
|
||||
|
||||
# Falls DB-Migration schon durch: einmal reload
|
||||
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
|
||||
fi
|
||||
|
||||
# Mini-Portcheck (hilft beim Installer-Output)
|
||||
echo "Listening (25/465/587):"
|
||||
ss -ltnp | awk '$4 ~ /:(25|465|587)$/ {print " " $0}'
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Sudoers: npm-Build ohne Passwort für user 'mailwolt' …"
|
||||
|
||||
# 1) npm-Binary ermitteln (normal: /usr/bin/npm)
|
||||
NPM_BIN="$(command -v npm || true)"
|
||||
|
||||
if [[ -z "$NPM_BIN" ]]; then
|
||||
warn "npm wurde nicht gefunden – sudoers wird vorbereitet, aber ohne Validierung. Stelle sicher, dass Node/npm installiert ist."
|
||||
# Fallback – die meisten Distros legen hier an
|
||||
NPM_BIN="/usr/bin/npm"
|
||||
fi
|
||||
|
||||
SUDOERS_FILE="/etc/sudoers.d/mailwolt-npm"
|
||||
|
||||
# 2) Sudoers-Datei schreiben
|
||||
cat > "$SUDOERS_FILE" <<EOF
|
||||
Defaults!${NPM_BIN} !requiretty
|
||||
mailwolt ALL=(root) NOPASSWD: ${NPM_BIN}
|
||||
EOF
|
||||
|
||||
chown root:root "$SUDOERS_FILE"
|
||||
chmod 440 "$SUDOERS_FILE"
|
||||
|
||||
# 3) Validieren
|
||||
if visudo -c -f "$SUDOERS_FILE" >/dev/null 2>&1; then
|
||||
log "[✓] sudoers OK: ${SUDOERS_FILE} erlaubt 'mailwolt' → ${NPM_BIN} ohne Passwort."
|
||||
else
|
||||
echo "[!] Ungültiger sudoers-Eintrag in ${SUDOERS_FILE} – entferne Datei."
|
||||
rm -f "$SUDOERS_FILE"
|
||||
fi
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "Backup/Restore – Tools, Config & Timer (installer.env) …"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 1) installer.env laden (ENV > installer.env > Defaults)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
if [[ -f /etc/mailwolt/installer.env ]]; then
|
||||
# automatisch exportieren, damit ${VAR} später überall wirkt
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/mailwolt/installer.env
|
||||
set +a
|
||||
else
|
||||
log "[i] /etc/mailwolt/installer.env nicht gefunden – nutze Defaults."
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 2) Pfade & Defaults (werden durch ENV/installer.env überschrieben)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
CONF_DIR="/etc/mailwolt"
|
||||
CONF_FILE="${CONF_DIR}/backup.conf"
|
||||
BIN_DIR="/usr/local/sbin"
|
||||
UNIT_DIR="/etc/systemd/system"
|
||||
|
||||
APP_DIR="${APP_DIR:-/var/www/mailwolt}"
|
||||
|
||||
# DB-Parameter aus installer.env (bzw. ENV) oder Fallbacks
|
||||
DB_HOST="${DB_HOST:-127.0.0.1}"
|
||||
DB_NAME="${DB_NAME:-mailwolt}"
|
||||
DB_USER="${DB_USER:-mailwolt}"
|
||||
DB_PASS="${DB_PASS:-}"
|
||||
|
||||
# Backup-Settings aus installer.env (bzw. ENV)
|
||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/mailwolt}"
|
||||
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
|
||||
BACKUP_USE_ZSTD="${BACKUP_USE_ZSTD:-1}"
|
||||
BACKUP_ENABLED="${BACKUP_ENABLED:-0}" # 0|1
|
||||
BACKUP_INTERVAL="${BACKUP_INTERVAL:-daily}" # daily|weekly|monthly
|
||||
|
||||
install -d -m 0755 "$CONF_DIR" "$BACKUP_DIR"
|
||||
|
||||
|
||||
SUDOERS_BACKUP_FILE="/etc/sudoers.d/mailwolt-backup"
|
||||
# 2) Sudoers-Datei schreiben
|
||||
cat > "${SUDOERS_BACKUP_FILE} " <<EOF
|
||||
Defaults!/usr/local/sbin/mailwolt-backup !requiretty
|
||||
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-backup
|
||||
mailwolt ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-backup
|
||||
EOF
|
||||
|
||||
chown root:root "${SUDOERS_BACKUP_FILE}"
|
||||
chmod 440 "${SUDOERS_BACKUP_FILE}"
|
||||
if ! visudo -c -f "${SUDOERS_BACKUP_FILE}" >/dev/null 2>&1; then
|
||||
echo "[!] Ungültiger sudoers-Eintrag in ${SUDOERS_BACKUP_FILE} – entferne Datei."
|
||||
rm -f "${SUDOERS_BACKUP_FILE}"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 3) /etc/mailwolt/backup.conf (von UI/APP überschreibbar)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
cat > "$CONF_FILE" <<EOF
|
||||
# MailWolt Backup Konfiguration (UI kann überschreiben)
|
||||
APP_DIR="$APP_DIR"
|
||||
BACKUP_DIR="$BACKUP_DIR"
|
||||
RETENTION_DAYS="$BACKUP_RETENTION_DAYS"
|
||||
USE_ZSTD="$BACKUP_USE_ZSTD"
|
||||
|
||||
# DB-Parameter
|
||||
MYSQL_DB="$DB_NAME"
|
||||
MYSQL_USER="$DB_USER"
|
||||
MYSQL_PASS="$DB_PASS"
|
||||
MYSQL_HOST="$DB_HOST"
|
||||
MYSQL_PORT="3306"
|
||||
EOF
|
||||
|
||||
chmod 0644 "$CONF_FILE"
|
||||
log "[✓] config geschrieben: $CONF_FILE"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 4) /usr/local/sbin/mailwolt-backup (schreibt backup.status)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
cat > "${BIN_DIR}/mailwolt-backup" <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
log(){ echo "[$(date -Is)] $*"; }
|
||||
|
||||
# Konfiguration laden (ENV > Datei)
|
||||
CONF="/etc/mailwolt/backup.conf"
|
||||
[[ -f "$CONF" ]] && # shellcheck disable=SC1090
|
||||
source "$CONF"
|
||||
|
||||
APP_DIR="${APP_DIR:-/var/www/mailwolt}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/mailwolt}"
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-7}"
|
||||
USE_ZSTD="${USE_ZSTD:-1}"
|
||||
|
||||
MYSQL_DB="${MYSQL_DB:-mailwolt}"
|
||||
MYSQL_USER="${MYSQL_USER:-mailwolt}"
|
||||
MYSQL_PASS="${MYSQL_PASS:-}"
|
||||
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
|
||||
MYSQL_PORT="${MYSQL_PORT:-3306}"
|
||||
|
||||
STATE_DIR="/var/lib/mailwolt"
|
||||
STATUS_FILE="${STATE_DIR}/backup.status"
|
||||
install -d -m 0755 "$STATE_DIR" "$BACKUP_DIR"
|
||||
|
||||
START_TS="$(date +%s)"
|
||||
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
TMP="$(mktemp -d /tmp/mwbackup.XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
fail(){
|
||||
local msg="${1:-backup failed}"
|
||||
local now="$(date -Is)"
|
||||
{
|
||||
echo "time=${now}"
|
||||
echo "size=0"
|
||||
echo "dur=$(( $(date +%s) - START_TS ))s"
|
||||
echo "ok=0"
|
||||
echo "error=${msg}"
|
||||
} > "$STATUS_FILE"
|
||||
echo "[$now] ${msg}" >&2
|
||||
exit 1
|
||||
}
|
||||
trap 'fail "unexpected error (exit $?)"' ERR
|
||||
|
||||
OUT="${BACKUP_DIR}/mailwolt-${TS}.tar"
|
||||
log "⇒ starte Backup in $OUT …"
|
||||
|
||||
# 1) DB
|
||||
log " • mysqldump …"
|
||||
MYSQL_PWD="$MYSQL_PASS" mysqldump \
|
||||
-h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" \
|
||||
--single-transaction --routines --events --triggers \
|
||||
"$MYSQL_DB" > "$TMP/mysql.sql"
|
||||
|
||||
# 2) Maildir
|
||||
log " • Maildir …"
|
||||
tar -C / -cf "$TMP/mail.tar" var/mail/vhosts 2>/dev/null || true
|
||||
|
||||
# 3) App (ohne heavy dirs)
|
||||
log " • App …"
|
||||
tar -C / -cf "$TMP/app.tar" \
|
||||
--exclude='var/www/mailwolt/vendor' \
|
||||
--exclude='var/www/mailwolt/node_modules' \
|
||||
--exclude='var/www/mailwolt/public/build' \
|
||||
var/www/mailwolt
|
||||
|
||||
# 4) Configs
|
||||
log " • Configs …"
|
||||
mkdir -p "$TMP/files"
|
||||
cp -a /etc/mailwolt "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/postfix "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/dovecot "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/opendkim "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/opendmarc "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/rspamd "$TMP/files/" 2>/dev/null || true
|
||||
cp -a /etc/ssl/ui "$TMP/files/" 2>/dev/null || true
|
||||
tar -C "$TMP" -cf "$TMP/files.tar" files
|
||||
|
||||
# 5) Paket
|
||||
log " • Archiviere …"
|
||||
tar -C "$TMP" -cf "$OUT" mysql.sql mail.tar app.tar files.tar
|
||||
|
||||
# 6) Komprimieren (optional)
|
||||
if [[ "${USE_ZSTD:-1}" = "1" ]] && command -v zstd >/dev/null 2>&1; then
|
||||
log " • komprimiere (zstd) …"
|
||||
zstd -f --rm -19 "$OUT"
|
||||
OUT="${OUT}.zst"
|
||||
fi
|
||||
|
||||
# 7) Retention
|
||||
if [[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]]; then
|
||||
log " • Retention: lösche älter als ${RETENTION_DAYS} Tage …"
|
||||
find "$BACKUP_DIR" -type f -mtime +"$RETENTION_DAYS" -name 'mailwolt-*' -delete || true
|
||||
fi
|
||||
|
||||
# 8) Statusfile fürs UI
|
||||
SIZE_BYTES="$(stat -c '%s' "$OUT" 2>/dev/null || echo 0)"
|
||||
{
|
||||
echo "time=$(date -Is)"
|
||||
echo "size=${SIZE_BYTES}"
|
||||
echo "dur=$(( $(date +%s) - START_TS ))s"
|
||||
echo "ok=1"
|
||||
echo "file=${OUT}"
|
||||
} > "$STATUS_FILE"
|
||||
chmod 0644 "$STATUS_FILE" 2>/dev/null || true
|
||||
|
||||
log "[✓] Backup fertig: $OUT"
|
||||
EOSH
|
||||
chmod 0755 "${BIN_DIR}/mailwolt-backup"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 5) /usr/local/sbin/mailwolt-restore
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
cat > "${BIN_DIR}/mailwolt-restore" <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log(){ echo "[$(date -Is)] $*"; }
|
||||
|
||||
ARCHIVE="${1:-}"
|
||||
[[ -n "$ARCHIVE" ]] || { echo "Usage: mailwolt-restore <backup.tar[.zst]>"; exit 1; }
|
||||
[[ -f "$ARCHIVE" ]] || { echo "Backup nicht gefunden: $ARCHIVE"; exit 1; }
|
||||
|
||||
TMP="$(mktemp -d /tmp/mwrestore.XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
case "$ARCHIVE" in
|
||||
*.zst) zstd -d -c "$ARCHIVE" > "$TMP/backup.tar" ;;
|
||||
*) cp -a "$ARCHIVE" "$TMP/backup.tar" ;;
|
||||
esac
|
||||
|
||||
log "⇒ entpacke …"
|
||||
tar -C "$TMP" -xf "$TMP/backup.tar"
|
||||
|
||||
# Reihenfolge: DB → App → Mail → Config
|
||||
if [[ -f "$TMP/mysql.sql" ]]; then
|
||||
log " • MySQL wiederherstellen …"
|
||||
mysql < "$TMP/mysql.sql"
|
||||
fi
|
||||
|
||||
if [[ -f "$TMP/app.tar" ]]; then
|
||||
log " • App → /var/www/mailwolt …"
|
||||
tar -C / -xf "$TMP/app.tar"
|
||||
fi
|
||||
|
||||
if [[ -f "$TMP/mail.tar" ]]; then
|
||||
log " • Maildir → /var/mail/vhosts …"
|
||||
tar -C / -xf "$TMP/mail.tar"
|
||||
fi
|
||||
|
||||
if [[ -f "$TMP/files.tar" ]]; then
|
||||
log " • Configs → /etc/* …"
|
||||
tar -C / -xf "$TMP/files.tar"
|
||||
fi
|
||||
|
||||
log "[✓] Restore abgeschlossen."
|
||||
EOSH
|
||||
chmod 0755 "${BIN_DIR}/mailwolt-restore"
|
||||
|
||||
log "[✓] Tools installiert: ${BIN_DIR}/mailwolt-backup, mailwolt-restore"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 6) systemd Service + Timer (Timer default via installer.env)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
cat > "${UNIT_DIR}/mailwolt-backup.service" <<'EOSVC'
|
||||
[Unit]
|
||||
Description=MailWolt Backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/mailwolt-backup
|
||||
Nice=10
|
||||
IOSchedulingClass=best-effort
|
||||
IOSchedulingPriority=7
|
||||
EOSVC
|
||||
|
||||
cat > "${UNIT_DIR}/mailwolt-backup.timer" <<EOTIM
|
||||
[Unit]
|
||||
Description=MailWolt Backup Timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=${BACKUP_ONCALENDAR:-*-*-* 03:00:00}
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOTIM
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
if [[ "${BACKUP_ENABLED}" = "1" ]]; then
|
||||
log "Aktiviere Backup-Timer (${BACKUP_ONCALENDAR}) …"
|
||||
systemctl enable --now mailwolt-backup.timer
|
||||
else
|
||||
log "Timer bleibt deaktiviert (BACKUP_ENABLED=0)."
|
||||
systemctl disable --now mailwolt-backup.timer >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
log "[✓] Backup-Setup abgeschlossen."
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#!/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 /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
|
||||
|
||||
check process mariadb with pidfile /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 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
|
||||
EOF
|
||||
chmod 600 /etc/monit/monitrc
|
||||
monit -t && systemctl enable --now monit
|
||||
monit reload || true
|
||||
|
||||
log "[✓] Monit konfiguriert und gestartet"
|
||||
|
||||
# ── mailwolt-update ins System kopieren ─────────────────────────────
|
||||
install -m 0750 -o root -g root scripts/update.sh /usr/local/sbin/mailwolt-update
|
||||
log "[✓] mailwolt-update installiert → ausführbar via 'sudo mailwolt-update'"
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "WoltGuard (Monit + Self-Heal) einrichten …"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Env nur nachladen, wenn Flags nicht bereits exportiert sind
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
: "${CLAMAV_ENABLE:=}" ; : "${OPENDMARC_ENABLE:=}" ; : "${FAIL2BAN_ENABLE:=}"
|
||||
if [[ -z "${CLAMAV_ENABLE}${OPENDMARC_ENABLE}${FAIL2BAN_ENABLE}" && -r "$INSTALLER_ENV" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$INSTALLER_ENV"
|
||||
fi
|
||||
CLAMAV_ENABLE="${CLAMAV_ENABLE:-0}"
|
||||
OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-0}"
|
||||
FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Monit installieren & aktivieren
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
command -v monit >/dev/null || { apt-get update -qq; apt-get install -y monit; }
|
||||
systemctl enable --now monit
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Helper-Skripte (laufen später eigenständig → Env selbst laden)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
install -d -m 0755 /usr/local/sbin
|
||||
|
||||
# Redis-Ping (nimmt REDIS_PASSWORD aus installer.env oder .env)
|
||||
cat >/usr/local/sbin/mailwolt-redis-ping.sh <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
APP_ENV="/var/www/mailwolt/.env"
|
||||
|
||||
# Defaults
|
||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
||||
REDIS_PASS="${REDIS_PASS:-}" # Legacy
|
||||
|
||||
# Installer-Env (falls vorhanden)
|
||||
[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV" || true
|
||||
|
||||
# Falls .env existiert: Werte ergänzen, die noch leer sind
|
||||
if [[ -r "$APP_ENV" ]]; then
|
||||
[[ -z "${REDIS_HOST}" ]] && REDIS_HOST="$(grep -m1 -E '^REDIS_HOST=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
[[ -z "${REDIS_PORT}" ]] && REDIS_PORT="$(grep -m1 -E '^REDIS_PORT=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
[[ -z "${REDIS_PASSWORD}" ]] && REDIS_PASSWORD="$(grep -m1 -E '^REDIS_PASSWORD=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
fi
|
||||
|
||||
# Legacy-Fallback: wenn PASSWORD leer, aber PASS gesetzt → übernehmen
|
||||
[[ -z "${REDIS_PASSWORD}" && -n "${REDIS_PASS}" ]] && REDIS_PASSWORD="$REDIS_PASS"
|
||||
|
||||
# Quotes strippen
|
||||
strip(){ printf '%s' "$1" | sed -E 's/^"(.*)"$/\1/; s/^'\''(.*)'\''$/\1/'; }
|
||||
REDIS_HOST="$(strip "${REDIS_HOST:-}")"
|
||||
REDIS_PORT="$(strip "${REDIS_PORT:-}")"
|
||||
REDIS_PASSWORD="$(strip "${REDIS_PASSWORD:-}")"
|
||||
|
||||
# redis-cli muss vorhanden sein
|
||||
command -v redis-cli >/dev/null 2>&1 || exit 1
|
||||
|
||||
BASE=(timeout 2 redis-cli --no-auth-warning --raw -h "$REDIS_HOST" -p "$REDIS_PORT")
|
||||
if [[ -n "$REDIS_PASSWORD" ]]; then
|
||||
CMD=("${BASE[@]}" -a "$REDIS_PASSWORD" ping)
|
||||
else
|
||||
CMD=("${BASE[@]}" ping)
|
||||
fi
|
||||
|
||||
# Erfolgreich nur bei exakt "PONG"
|
||||
[[ "$("${CMD[@]}" 2>/dev/null || true)" == "PONG" ]]
|
||||
EOSH
|
||||
chmod 0755 /usr/local/sbin/mailwolt-redis-ping.sh
|
||||
|
||||
# Rspamd-Heal (setzt Laufzeitverzeichnis, leert alte Socke, restarts rspamd)
|
||||
cat >/usr/local/sbin/mailwolt-rspamd-heal.sh <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
APP_ENV="/var/www/mailwolt/.env"
|
||||
|
||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
||||
|
||||
[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV"
|
||||
if [[ -z "${REDIS_PASSWORD}" && -r "$APP_ENV" ]]; then
|
||||
REDIS_PASSWORD="$(grep -E '^REDIS_PASSWORD=' "$APP_ENV" | head -n1 | cut -d= -f2- || true)"
|
||||
fi
|
||||
|
||||
# Rspamd Runtime fixen
|
||||
install -d -m 0755 -o _rspamd -g _rspamd /run/rspamd || true
|
||||
[[ -S /var/lib/rspamd/rspamd.sock ]] && rm -f /var/lib/rspamd/rspamd.sock || true
|
||||
|
||||
# Neustart
|
||||
systemctl restart rspamd
|
||||
|
||||
# Mini-Healthcheck
|
||||
sleep 2
|
||||
ss -tln | grep -q ':11334' || echo "[WARN] Rspamd Controller Port 11334 nicht sichtbar"
|
||||
|
||||
exit 0
|
||||
EOSH
|
||||
chmod 0755 /usr/local/sbin/mailwolt-rspamd-heal.sh
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# WoltGuard Wrapper + Unit
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
cat >/usr/local/bin/woltguard <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
case "${1:-status}" in
|
||||
start) systemctl enable --now monit ;;
|
||||
stop) systemctl stop monit ;;
|
||||
status) monit summary || systemctl status monit || true ;;
|
||||
heal) monit reload || true; sleep 1; monit restart all || true ;;
|
||||
monitor) monit monitor all || true ;;
|
||||
unmonitor) monit unmonitor all || true ;;
|
||||
*) echo "Usage: woltguard {start|stop|status|heal|monitor|unmonitor}"; exit 2;;
|
||||
esac
|
||||
EOSH
|
||||
chmod 0755 /usr/local/bin/woltguard
|
||||
|
||||
cat >/etc/systemd/system/woltguard.service <<'EOF'
|
||||
[Unit]
|
||||
Description=WoltGuard – Self-Healing Monitor for MailWolt
|
||||
After=network.target
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/woltguard start
|
||||
ExecStop=/usr/local/bin/woltguard stop
|
||||
RemainAfterExit=yes
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now woltguard
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Monit Basis + includes
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
sed -i 's/^set daemon .*/set daemon 30/' /etc/monit/monitrc || true
|
||||
grep -q 'include /etc/monit/conf.d/*' /etc/monit/monitrc || echo 'include /etc/monit/conf.d/*' >>/etc/monit/monitrc
|
||||
install -d -m 0755 /etc/monit/conf.d
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Monit Checks
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 10 – Redis zuerst (abhängig für rspamd)
|
||||
cat >/etc/monit/conf.d/10-redis.conf <<'EOF'
|
||||
check process redis with pidfile /run/redis/redis-server.pid
|
||||
start program = "/bin/systemctl start redis-server"
|
||||
stop program = "/bin/systemctl stop redis-server"
|
||||
if failed host 127.0.0.1 port 6379 for 2 cycles then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
|
||||
check program redis_ping path "/usr/local/sbin/mailwolt-redis-ping.sh"
|
||||
if status != 0 for 2 cycles then exec "/bin/systemctl restart redis-server"
|
||||
EOF
|
||||
|
||||
# 20 – Rspamd (hängt von Redis ab), robust über process-matching
|
||||
cat >/etc/monit/conf.d/20-rspamd.conf <<'EOF'
|
||||
check process rspamd matching "/usr/bin/rspamd"
|
||||
start program = "/bin/systemctl start rspamd"
|
||||
stop program = "/bin/systemctl stop rspamd"
|
||||
depends on redis
|
||||
if failed host 127.0.0.1 port 11333 for 2 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
if failed host 127.0.0.1 port 11334 for 2 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# 30 – Maildienste
|
||||
cat >/etc/monit/conf.d/30-postfix.conf <<'EOF'
|
||||
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 tcpssl then restart
|
||||
if failed port 587 type tcp then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
cat >/etc/monit/conf.d/30-dovecot.conf <<'EOF'
|
||||
check process dovecot with pidfile /run/dovecot/master.pid
|
||||
start program = "/bin/systemctl start dovecot"
|
||||
stop program = "/bin/systemctl stop dovecot"
|
||||
if failed port 993 type tcpssl for 2 cycles then restart
|
||||
if failed port 24 protocol lmtp for 2 cycles then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# 40 – Web/PHP
|
||||
cat >/etc/monit/conf.d/40-nginx.conf <<'EOF'
|
||||
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 tcpssl then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# 50 – DKIM/DMARC
|
||||
cat >/etc/monit/conf.d/50-opendkim.conf <<'EOF'
|
||||
check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||||
start program = "/bin/systemctl start opendkim"
|
||||
stop program = "/bin/systemctl stop opendkim"
|
||||
if failed host 127.0.0.1 port 8891 type tcp for 2 cycles then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# optional: OpenDMARC
|
||||
if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
|
||||
cat >/etc/monit/conf.d/55-opendmarc.conf <<'EOF'
|
||||
check process opendmarc with pidfile /run/opendmarc/opendmarc.pid
|
||||
start program = "/bin/systemctl start opendmarc"
|
||||
stop program = "/bin/systemctl stop opendmarc"
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
else
|
||||
rm -f /etc/monit/conf.d/55-opendmarc.conf || true
|
||||
fi
|
||||
|
||||
# 60 – optional: ClamAV
|
||||
if [[ "$CLAMAV_ENABLE" = "1" ]]; then
|
||||
cat >/etc/monit/conf.d/60-clamav.conf <<'EOF'
|
||||
check process clamd with pidfile /run/clamav/clamd.pid
|
||||
start program = "/bin/systemctl start clamav-daemon"
|
||||
stop program = "/bin/systemctl stop clamav-daemon"
|
||||
if failed unixsocket /run/clamav/clamd.ctl for 3 cycles then restart
|
||||
if 5 restarts within 5 cycles then timeout
|
||||
EOF
|
||||
else
|
||||
rm -f /etc/monit/conf.d/60-clamav.conf || true
|
||||
fi
|
||||
|
||||
# 70 – Fail2Ban (optional, standardmäßig aktiv)
|
||||
if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
|
||||
cat >/etc/monit/conf.d/70-fail2ban.conf <<'EOF'
|
||||
check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||||
start program = "/bin/systemctl start fail2ban"
|
||||
stop program = "/bin/systemctl stop fail2ban"
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
else
|
||||
rm -f /etc/monit/conf.d/70-fail2ban.conf || true
|
||||
fi
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Monit neu laden
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
monit -t
|
||||
systemctl reload monit || systemctl restart monit
|
||||
systemctl status monit --no-pager || true
|
||||
log "[✓] WoltGuard aktiv."
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#source ./lib.sh
|
||||
#
|
||||
#log "WoltGuard (Monit + Self-Heal) einrichten …"
|
||||
#
|
||||
#set +u
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
#set -u
|
||||
#CLAMAV_ENABLE="${CLAMAV_ENABLE:-0}"
|
||||
#OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-0}"
|
||||
#FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
#
|
||||
## Pakete sicherstellen
|
||||
#command -v monit >/dev/null || { apt-get update -qq; apt-get install -y monit; }
|
||||
#systemctl enable --now monit
|
||||
#
|
||||
## Helper-Skripte
|
||||
#install -d -m 0755 /usr/local/sbin
|
||||
#cat >/usr/local/sbin/mailwolt-redis-ping.sh <<'EOSH'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#PASS=""
|
||||
#[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env || true
|
||||
#if command -v redis-cli >/dev/null 2>&1; then
|
||||
# [[ -n "${REDIS_PASS:-}" ]] \
|
||||
# && redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASS" ping | grep -q PONG \
|
||||
# || redis-cli -h 127.0.0.1 -p 6379 ping | grep -q PONG
|
||||
#else
|
||||
# exit 1
|
||||
#fi
|
||||
#EOSH
|
||||
#chmod 0755 /usr/local/sbin/mailwolt-redis-ping.sh
|
||||
#
|
||||
#cat >/usr/local/sbin/mailwolt-rspamd-heal.sh <<'EOSH'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
#REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
#REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
#REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
||||
#
|
||||
#INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
#APP_ENV="/var/www/mailwolt/.env"
|
||||
#REDIS_CLI="$(command -v redis-cli || true)"
|
||||
#SYSTEMCTL="$(command -v systemctl || true)"
|
||||
#RSPAMD_SERVICE="rspamd"
|
||||
#
|
||||
#if [ -r "$INSTALLER_ENV" ]; then . "$INSTALLER_ENV"; fi
|
||||
#if [ -z "${REDIS_PASSWORD}" ] && [ -r "$APP_ENV" ]; then
|
||||
# REDIS_PASSWORD="$(grep -E '^REDIS_PASSWORD=' "$APP_ENV" | head -n1 | cut -d= -f2- || true)"
|
||||
#fi
|
||||
#
|
||||
#if [ -n "$REDIS_CLI" ]; then
|
||||
# echo "[INFO] Prüfe Redis Verbindung..."
|
||||
# if [ -n "${REDIS_PASSWORD}" ]; then
|
||||
# if ! "$REDIS_CLI" -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" ping | grep -q '^PONG$'; then
|
||||
# echo "[WARN] Redis antwortet nicht oder Passwort falsch!"
|
||||
# else
|
||||
# echo "[OK] Redis antwortet (auth ok)."
|
||||
# fi
|
||||
# else
|
||||
# if ! "$REDIS_CLI" -h "$REDIS_HOST" -p "$REDIS_PORT" ping | grep -q '^PONG$'; then
|
||||
# echo "[WARN] Redis antwortet nicht (ohne Passwort)."
|
||||
# else
|
||||
# echo "[OK] Redis antwortet (kein Passwort)."
|
||||
# fi
|
||||
# fi
|
||||
#else
|
||||
# echo "[WARN] redis-cli nicht gefunden – überspringe Test."
|
||||
#fi
|
||||
#
|
||||
#echo "[INFO] Prüfe Rspamd Socket & Verzeichnis..."
|
||||
#install -d -m 0755 -o _rspamd -g _rspamd /run/rspamd || true
|
||||
#[ -S /var/lib/rspamd/rspamd.sock ] && rm -f /var/lib/rspamd/rspamd.sock || true
|
||||
#
|
||||
#echo "[INFO] Starte Rspamd neu..."
|
||||
#if [ -n "$SYSTEMCTL" ]; then
|
||||
# "$SYSTEMCTL" restart "$RSPAMD_SERVICE"
|
||||
# echo "[OK] Rspamd erfolgreich neu gestartet."
|
||||
#else
|
||||
# echo "[ERROR] systemctl nicht gefunden – kein Neustart möglich."
|
||||
# exit 1
|
||||
#fi
|
||||
#
|
||||
#echo "[INFO] Healthcheck (Port 11334)..."
|
||||
#sleep 3
|
||||
#if ss -tln | grep -q ':11334'; then
|
||||
# echo "[OK] Rspamd Controller läuft auf Port 11334."
|
||||
#else
|
||||
# echo "[WARN] Rspamd Controller Port 11334 nicht erreichbar."
|
||||
#fi
|
||||
#
|
||||
#echo "[DONE] Mailwolt Rspamd-Heal abgeschlossen."
|
||||
#exit 0
|
||||
#EOSH
|
||||
#chmod 0755 /usr/local/sbin/mailwolt-rspamd-heal.sh
|
||||
#
|
||||
## WoltGuard Wrapper + Unit
|
||||
#cat >/usr/local/bin/woltguard <<'EOSH'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#case "${1:-status}" in
|
||||
# start) systemctl enable --now monit ;;
|
||||
# stop) systemctl stop monit ;;
|
||||
# status) monit summary || systemctl status monit || true ;;
|
||||
# heal) monit reload || true; sleep 1; monit restart all || true ;;
|
||||
# monitor) monit monitor all || true ;;
|
||||
# unmonitor) monit unmonitor all || true ;;
|
||||
# *) echo "Usage: woltguard {start|stop|status|heal|monitor|unmonitor}"; exit 2;;
|
||||
#esac
|
||||
#EOSH
|
||||
#chmod 0755 /usr/local/bin/woltguard
|
||||
#
|
||||
#cat >/etc/systemd/system/woltguard.service <<'EOF'
|
||||
#[Unit]
|
||||
#Description=WoltGuard – Self-Healing Monitor for MailWolt
|
||||
#After=network.target
|
||||
#[Service]
|
||||
#Type=oneshot
|
||||
#ExecStart=/usr/local/bin/woltguard start
|
||||
#ExecStop=/usr/local/bin/woltguard stop
|
||||
#RemainAfterExit=yes
|
||||
#[Install]
|
||||
#WantedBy=multi-user.target
|
||||
#EOF
|
||||
#systemctl daemon-reload
|
||||
#systemctl enable --now woltguard
|
||||
#
|
||||
## Monit Basis + include
|
||||
#sed -i 's/^set daemon .*/set daemon 30/' /etc/monit/monitrc || true
|
||||
#grep -q 'include /etc/monit/conf.d/*' /etc/monit/monitrc || echo 'include /etc/monit/conf.d/*' >>/etc/monit/monitrc
|
||||
#install -d -m 0755 /etc/monit/conf.d
|
||||
#
|
||||
## Checks
|
||||
#cat >/etc/monit/conf.d/postfix.conf <<'EOF'
|
||||
#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 tcpssl then restart
|
||||
# if failed port 587 type tcp then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
#cat >/etc/monit/conf.d/dovecot.conf <<'EOF'
|
||||
#check process dovecot with pidfile /run/dovecot/master.pid
|
||||
# start program = "/bin/systemctl start dovecot"
|
||||
# stop program = "/bin/systemctl stop dovecot"
|
||||
# if failed port 993 type tcpssl for 2 cycles then restart
|
||||
# if failed port 24 protocol lmtp for 2 cycles then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
#cat >/etc/monit/conf.d/nginx.conf <<'EOF'
|
||||
#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 tcpssl then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
#cat >/etc/monit/conf.d/redis.conf <<'EOF'
|
||||
#check process redis with pidfile /run/redis/redis-server.pid
|
||||
# start program = "/bin/systemctl start redis-server"
|
||||
# stop program = "/bin/systemctl stop redis-server"
|
||||
# if failed host 127.0.0.1 port 6379 for 2 cycles then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#
|
||||
#check program redis_ping path "/usr/local/sbin/mailwolt-redis-ping.sh"
|
||||
# if status != 0 for 2 cycles then exec "/bin/systemctl restart redis-server"
|
||||
#EOF
|
||||
#
|
||||
#cat >/etc/monit/conf.d/rspamd.conf <<'EOF'
|
||||
#check process rspamd with pidfile /run/rspamd/rspamd.pid
|
||||
# start program = "/bin/systemctl start rspamd"
|
||||
# stop program = "/bin/systemctl stop rspamd"
|
||||
# if failed port 11333 for 2 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
# if failed port 11334 for 2 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
#cat >/etc/monit/conf.d/opendkim.conf <<'EOF'
|
||||
#check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||||
# start program = "/bin/systemctl start opendkim"
|
||||
# stop program = "/bin/systemctl stop opendkim"
|
||||
# if failed host 127.0.0.1 port 8891 type tcp for 2 cycles then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
## optional: OpenDMARC
|
||||
#if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/conf.d/opendmarc.conf <<'EOF'
|
||||
#check process opendmarc with pidfile /run/opendmarc/opendmarc.pid
|
||||
# start program = "/bin/systemctl start opendmarc"
|
||||
# stop program = "/bin/systemctl stop opendmarc"
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/conf.d/opendmarc.conf || true
|
||||
#fi
|
||||
#
|
||||
## optional: ClamAV
|
||||
#if [[ "$CLAMAV_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/conf.d/clamav.conf <<'EOF'
|
||||
#check process clamd with pidfile /run/clamav/clamd.pid
|
||||
# start program = "/bin/systemctl start clamav-daemon"
|
||||
# stop program = "/bin/systemctl stop clamav-daemon"
|
||||
# if failed unixsocket /run/clamav/clamd.ctl then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/conf.d/clamav.conf || true
|
||||
#fi
|
||||
#
|
||||
## optional: Fail2Ban
|
||||
#if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/conf.d/fail2ban.conf <<'EOF'
|
||||
#check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||||
# start program = "/bin/systemctl start fail2ban"
|
||||
# stop program = "/bin/systemctl stop fail2ban"
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/conf.d/fail2ban.conf || true
|
||||
#fi
|
||||
#
|
||||
#monit -t
|
||||
#systemctl reload monit || systemctl restart monit
|
||||
#systemctl status monit --no-pager || true
|
||||
#log "[✓] WoltGuard aktiv."
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Flags laden (falls vorhanden)
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
: "${CLAMAV_ENABLE:=}"; : "${OPENDMARC_ENABLE:=}"; : "${FAIL2BAN_ENABLE:=}"; : "${MONIT_HTTP:=}"
|
||||
if [[ -z "${CLAMAV_ENABLE}${OPENDMARC_ENABLE}${FAIL2BAN_ENABLE}" && -r "$INSTALLER_ENV" ]]; then
|
||||
. "$INSTALLER_ENV"
|
||||
fi
|
||||
CLAMAV_ENABLE="${CLAMAV_ENABLE:-1}"
|
||||
OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
|
||||
FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
MONIT_HTTP="${MONIT_HTTP:-1}"
|
||||
|
||||
# ── Monit so konfigurieren, dass NUR monitrc.d/* geladen wird ────────────────
|
||||
install -d -m 0755 /etc/monit/monitrc.d
|
||||
install -d -m 0755 /etc/monit/conf.d # passiver Ablageort (NICHT includiert)
|
||||
|
||||
# Poll-Intervall (30s)
|
||||
sed -i 's/^set daemon .*/set daemon 30/' /etc/monit/monitrc || true
|
||||
# alle alten include-Zeilen raus und monitrc.d setzen
|
||||
sed -i 's|^#\?\s*include .*$||g' /etc/monit/monitrc
|
||||
grep -q '^include /etc/monit/monitrc.d/\*' /etc/monit/monitrc \
|
||||
|| echo 'include /etc/monit/monitrc.d/*' >> /etc/monit/monitrc
|
||||
|
||||
# Optional: HTTP-UI nur einschalten, wenn explizit gewünscht
|
||||
if [[ "$MONIT_HTTP" = "1" ]]; then
|
||||
grep -q '^set httpd port 2812' /etc/monit/monitrc || cat >>/etc/monit/monitrc <<'HTTP'
|
||||
set httpd port 2812 and
|
||||
use address localhost
|
||||
allow localhost
|
||||
HTTP
|
||||
fi
|
||||
|
||||
# KEIN Löschen mehr der Dateien – wir verschieben je nach Status
|
||||
# (vorher stand hier rm -rf /etc/monit/monitrc.d/* und rm -f /etc/monit/conf.d/*.conf)
|
||||
|
||||
# ── Helper-Skripte ──────────────────────────────────────────────────────────
|
||||
install -d -m 0755 /usr/local/sbin
|
||||
|
||||
# Redis-Ping (Password: REDIS_PASSWORD aus installer.env oder .env)
|
||||
cat >/usr/local/sbin/mailwolt-redis-ping.sh <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
APP_ENV="/var/www/mailwolt/.env"
|
||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
||||
REDIS_PASS="${REDIS_PASS:-}"
|
||||
|
||||
[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV" || true
|
||||
if [[ -r "$APP_ENV" ]]; then
|
||||
[[ -z "${REDIS_HOST}" ]] && REDIS_HOST="$(grep -m1 '^REDIS_HOST=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
[[ -z "${REDIS_PORT}" ]] && REDIS_PORT="$(grep -m1 '^REDIS_PORT=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
[[ -z "${REDIS_PASSWORD}" ]] && REDIS_PASSWORD="$(grep -m1 '^REDIS_PASSWORD=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
fi
|
||||
[[ -z "${REDIS_PASSWORD}" && -n "${REDIS_PASS}" ]] && REDIS_PASSWORD="$REDIS_PASS"
|
||||
|
||||
strip(){ printf '%s' "$1" | sed -E 's/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/'; }
|
||||
REDIS_HOST="$(strip "${REDIS_HOST:-}")"
|
||||
REDIS_PORT="$(strip "${REDIS_PORT:-}")"
|
||||
REDIS_PASSWORD="$(strip "${REDIS_PASSWORD:-}")"
|
||||
|
||||
command -v redis-cli >/dev/null 2>&1 || exit 1
|
||||
BASE=(timeout 2 redis-cli --no-auth-warning --raw -h "$REDIS_HOST" -p "$REDIS_PORT")
|
||||
[[ -n "$REDIS_PASSWORD" ]] && CMD=("${BASE[@]}" -a "$REDIS_PASSWORD" ping) || CMD=("${BASE[@]}" ping)
|
||||
[[ "$("${CMD[@]}" 2>/dev/null || true)" == "PONG" ]]
|
||||
EOSH
|
||||
chmod 0755 /usr/local/sbin/mailwolt-redis-ping.sh
|
||||
|
||||
# Rspamd-Heal (Socke aufräumen, restart, Mini-Port-Check)
|
||||
cat >/usr/local/sbin/mailwolt-rspamd-heal.sh <<'EOSH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
APP_ENV="/var/www/mailwolt/.env"
|
||||
|
||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
REDIS_PASS="${REDIS_PASS:-}"
|
||||
|
||||
[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV"
|
||||
if [[ -z "${REDIS_PASS}" && -r "$APP_ENV" ]]; then
|
||||
REDIS_PASS="$(grep -E '^REDIS_PASS=' "$APP_ENV" | head -n1 | cut -d= -f2- || true)"
|
||||
fi
|
||||
|
||||
# Rspamd Runtime fixen
|
||||
install -d -m 0755 -o _rspamd -g _rspamd /run/rspamd || true
|
||||
[[ -S /var/lib/rspamd/rspamd.sock ]] && rm -f /var/lib/rspamd/rspamd.sock || true
|
||||
|
||||
echo "$(date '+%F %T') heal run" >> /var/log/rspamd-heal.log
|
||||
|
||||
# Neustart
|
||||
systemctl restart rspamd
|
||||
|
||||
# Mini-Healthcheck
|
||||
sleep 2
|
||||
ss -tln | grep -q ':11334' || echo "[WARN] Rspamd Controller Port 11334 nicht sichtbar"
|
||||
|
||||
exit 0
|
||||
EOSH
|
||||
chmod 0755 /usr/local/sbin/mailwolt-rspamd-heal.sh
|
||||
|
||||
# ── Monit-Checks (nummeriert) – fixe Dienste immer aktiv ────────────────────
|
||||
# 10 – Redis
|
||||
cat >/etc/monit/monitrc.d/10-redis.conf <<'EOF'
|
||||
check process redis with pidfile /run/redis/redis-server.pid
|
||||
start program = "/bin/systemctl start redis-server"
|
||||
stop program = "/bin/systemctl stop redis-server"
|
||||
if failed host 127.0.0.1 port 6379 for 2 cycles then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
|
||||
check program redis_ping path "/usr/local/sbin/mailwolt-redis-ping.sh"
|
||||
if status != 0 for 2 cycles then exec "/bin/systemctl restart redis-server"
|
||||
EOF
|
||||
|
||||
# 20 – Rspamd (robust via process-matching + Heal)
|
||||
cat >/etc/monit/monitrc.d/20-rspamd.conf <<'EOF'
|
||||
check process rspamd matching "rspamd: main process"
|
||||
start program = "/bin/systemctl start rspamd" with timeout 120 seconds
|
||||
stop program = "/bin/systemctl stop rspamd"
|
||||
depends on redis
|
||||
if failed host 127.0.0.1 port 11333 for 3 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
if failed host 127.0.0.1 port 11334 for 3 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
if does not exist for 2 cycles then restart
|
||||
if 5 restarts within 10 cycles then unmonitor
|
||||
EOF
|
||||
|
||||
# 30 – Postfix
|
||||
cat >/etc/monit/monitrc.d/30-postfix.conf <<'EOF'
|
||||
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 host 127.0.0.1 port 25 type tcp with timeout 15 seconds for 3 cycles then restart
|
||||
if failed host 127.0.0.1 port 465 type tcpssl with timeout 10 seconds then restart
|
||||
if failed host 127.0.0.1 port 587 type tcp with timeout 10 seconds then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# 30 – Dovecot (IMAPS; LMTP oft Unix-Socket → kein TCP-Fehlalarm)
|
||||
cat >/etc/monit/monitrc.d/30-dovecot.conf <<'EOF'
|
||||
check process dovecot with pidfile /run/dovecot/master.pid
|
||||
start program = "/bin/systemctl start dovecot"
|
||||
stop program = "/bin/systemctl stop dovecot"
|
||||
if failed port 993 type tcpssl for 3 cycles then restart
|
||||
if 5 restarts within 10 cycles then alert
|
||||
EOF
|
||||
|
||||
# 40 – Nginx
|
||||
cat >/etc/monit/monitrc.d/40-nginx.conf <<'EOF'
|
||||
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 tcpssl then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# 50 – OpenDKIM
|
||||
cat >/etc/monit/monitrc.d/50-opendkim.conf <<'EOF'
|
||||
check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||||
start program = "/bin/systemctl start opendkim"
|
||||
stop program = "/bin/systemctl stop opendkim"
|
||||
if failed host 127.0.0.1 port 8891 type tcp for 2 cycles then restart
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
move_monit_conf() {
|
||||
local name="$1" # z.B. 55-opendmarc
|
||||
local enabled="$2" # "0" oder "1"
|
||||
local src="/etc/monit/conf.d/${name}.conf"
|
||||
local dst="/etc/monit/monitrc.d/${name}.conf"
|
||||
|
||||
mkdir -p /etc/monit/conf.d /etc/monit/monitrc.d
|
||||
|
||||
# Falls Datei nirgends existiert → in conf.d anlegen (lesbare Quelle)
|
||||
if [[ ! -f "$src" && ! -f "$dst" ]]; then
|
||||
cat >"$src" <<'EOF_PAYLOAD'
|
||||
__PAYLOAD__
|
||||
EOF_PAYLOAD
|
||||
fi
|
||||
|
||||
if [[ "$enabled" = "1" ]]; then
|
||||
# Aktiv: in monitrc.d haben
|
||||
if [[ -f "$src" && ! -f "$dst" ]]; then
|
||||
mv -f "$src" "$dst"
|
||||
fi
|
||||
else
|
||||
# Inaktiv: in conf.d haben
|
||||
if [[ -f "$dst" && ! -f "$src" ]]; then
|
||||
mv -f "$dst" "$src"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
move_monit_conf "55-opendmarc" "${OPENDMARC_ENABLE:-0}" <<'EOF'
|
||||
check process opendmarc with pidfile /run/opendmarc/opendmarc.pid
|
||||
start program = "/bin/systemctl start opendmarc"
|
||||
stop program = "/bin/systemctl stop opendmarc"
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
move_monit_conf "60-clamav" "${CLAMAV_ENABLE:-0}" <<'EOF'
|
||||
check process clamd matching "clamd"
|
||||
start program = "/bin/systemctl start clamav-daemon"
|
||||
stop program = "/bin/systemctl stop clamav-daemon"
|
||||
if failed unixsocket /run/clamav/clamd.ctl for 3 cycles then restart
|
||||
if 5 restarts within 10 cycles then unmonitor
|
||||
EOF
|
||||
|
||||
move_monit_conf "70-fail2ban" "${FAIL2BAN_ENABLE:-0}" <<'EOF'
|
||||
check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||||
start program = "/bin/systemctl start fail2ban"
|
||||
stop program = "/bin/systemctl stop fail2ban"
|
||||
if 5 restarts within 5 cycles then alert
|
||||
EOF
|
||||
|
||||
# ── Monit neu laden ─────────────────────────────────────────────────────────
|
||||
monit -t
|
||||
systemctl reload monit || systemctl restart monit
|
||||
|
||||
# Optionaler Sichttest (CLI funktioniert auch ohne HTTP-UI)
|
||||
# sleep 2
|
||||
# monit summary || true
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
## Flags laden (falls vorhanden)
|
||||
#INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
#: "${CLAMAV_ENABLE:=}"; : "${OPENDMARC_ENABLE:=}"; : "${FAIL2BAN_ENABLE:=}"; : "${MONIT_HTTP:=}"
|
||||
#if [[ -z "${CLAMAV_ENABLE}${OPENDMARC_ENABLE}${FAIL2BAN_ENABLE}" && -r "$INSTALLER_ENV" ]]; then
|
||||
# . "$INSTALLER_ENV"
|
||||
#fi
|
||||
#CLAMAV_ENABLE="${CLAMAV_ENABLE:-1}"
|
||||
#OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
|
||||
#FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
#MONIT_HTTP="${MONIT_HTTP:-1}"
|
||||
#
|
||||
## ── Monit so konfigurieren, dass NUR monitrc.d/* geladen wird ────────────────
|
||||
#install -d -m 0755 /etc/monit/monitrc.d
|
||||
## Poll-Intervall (30s)
|
||||
#sed -i 's/^set daemon .*/set daemon 30/' /etc/monit/monitrc || true
|
||||
## alle alten include-Zeilen raus und monitrc.d setzen
|
||||
#sed -i 's|^#\?\s*include .*$||g' /etc/monit/monitrc
|
||||
#grep -q '^include /etc/monit/monitrc.d/\*' /etc/monit/monitrc \
|
||||
# || echo 'include /etc/monit/monitrc.d/*' >> /etc/monit/monitrc
|
||||
#
|
||||
## Optional: HTTP-UI nur einschalten, wenn explizit gewünscht
|
||||
#if [[ "$MONIT_HTTP" = "1" ]]; then
|
||||
# grep -q '^set httpd port 2812' /etc/monit/monitrc || cat >>/etc/monit/monitrc <<'HTTP'
|
||||
#set httpd port 2812 and
|
||||
# use address localhost
|
||||
# allow localhost
|
||||
#HTTP
|
||||
#fi
|
||||
#
|
||||
#sudo mkdir -p /etc/monit/monitrc.d
|
||||
#sudo rm -rf /etc/monit/monitrc.d/* 2>/dev/null || true
|
||||
#sudo rm -f /etc/monit/conf.d/*.conf 2>/dev/null || true
|
||||
#
|
||||
## ── Helper-Skripte ──────────────────────────────────────────────────────────
|
||||
#install -d -m 0755 /usr/local/sbin
|
||||
#
|
||||
## Redis-Ping (Password: REDIS_PASSWORD aus installer.env oder .env)
|
||||
#cat >/usr/local/sbin/mailwolt-redis-ping.sh <<'EOSH'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
#APP_ENV="/var/www/mailwolt/.env"
|
||||
#REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
#REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
#REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
||||
#REDIS_PASS="${REDIS_PASS:-}"
|
||||
#
|
||||
#[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV" || true
|
||||
#if [[ -r "$APP_ENV" ]]; then
|
||||
# [[ -z "${REDIS_HOST}" ]] && REDIS_HOST="$(grep -m1 '^REDIS_HOST=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
# [[ -z "${REDIS_PORT}" ]] && REDIS_PORT="$(grep -m1 '^REDIS_PORT=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
# [[ -z "${REDIS_PASSWORD}" ]] && REDIS_PASSWORD="$(grep -m1 '^REDIS_PASSWORD=' "$APP_ENV" | cut -d= -f2- || true)"
|
||||
#fi
|
||||
#[[ -z "${REDIS_PASSWORD}" && -n "${REDIS_PASS}" ]] && REDIS_PASSWORD="$REDIS_PASS"
|
||||
#
|
||||
#strip(){ printf '%s' "$1" | sed -E 's/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/'; }
|
||||
#REDIS_HOST="$(strip "${REDIS_HOST:-}")"
|
||||
#REDIS_PORT="$(strip "${REDIS_PORT:-}")"
|
||||
#REDIS_PASSWORD="$(strip "${REDIS_PASSWORD:-}")"
|
||||
#
|
||||
#command -v redis-cli >/dev/null 2>&1 || exit 1
|
||||
#BASE=(timeout 2 redis-cli --no-auth-warning --raw -h "$REDIS_HOST" -p "$REDIS_PORT")
|
||||
#[[ -n "$REDIS_PASSWORD" ]] && CMD=("${BASE[@]}" -a "$REDIS_PASSWORD" ping) || CMD=("${BASE[@]}" ping)
|
||||
#[[ "$("${CMD[@]}" 2>/dev/null || true)" == "PONG" ]]
|
||||
#EOSH
|
||||
#chmod 0755 /usr/local/sbin/mailwolt-redis-ping.sh
|
||||
#
|
||||
## Rspamd-Heal (Socke aufräumen, restart, Mini-Port-Check)
|
||||
#cat >/usr/local/sbin/mailwolt-rspamd-heal.sh <<'EOSH'
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
#INSTALLER_ENV="/etc/mailwolt/installer.env"
|
||||
#APP_ENV="/var/www/mailwolt/.env"
|
||||
#
|
||||
#REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
||||
#REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
#REDIS_PASS="${REDIS_PASS:-}"
|
||||
#
|
||||
#[[ -r "$INSTALLER_ENV" ]] && . "$INSTALLER_ENV"
|
||||
#if [[ -z "${REDIS_PASS}" && -r "$APP_ENV" ]]; then
|
||||
# REDIS_PASS="$(grep -E '^REDIS_PASS=' "$APP_ENV" | head -n1 | cut -d= -f2- || true)"
|
||||
#fi
|
||||
#
|
||||
## Rspamd Runtime fixen
|
||||
#install -d -m 0755 -o _rspamd -g _rspamd /run/rspamd || true
|
||||
#[[ -S /var/lib/rspamd/rspamd.sock ]] && rm -f /var/lib/rspamd/rspamd.sock || true
|
||||
#
|
||||
#echo "$(date '+%F %T') heal run" >> /var/log/rspamd-heal.log
|
||||
#
|
||||
## Neustart
|
||||
#systemctl restart rspamd
|
||||
#
|
||||
## Mini-Healthcheck
|
||||
#sleep 2
|
||||
#ss -tln | grep -q ':11334' || echo "[WARN] Rspamd Controller Port 11334 nicht sichtbar"
|
||||
#
|
||||
#exit 0
|
||||
#EOSH
|
||||
#chmod 0755 /usr/local/sbin/mailwolt-rspamd-heal.sh
|
||||
#
|
||||
## ── Monit-Checks (nummeriert) ───────────────────────────────────────────────
|
||||
## 10 – Redis
|
||||
#cat >/etc/monit/monitrc.d/10-redis.conf <<'EOF'
|
||||
#check process redis with pidfile /run/redis/redis-server.pid
|
||||
# start program = "/bin/systemctl start redis-server"
|
||||
# stop program = "/bin/systemctl stop redis-server"
|
||||
# if failed host 127.0.0.1 port 6379 for 2 cycles then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#
|
||||
#check program redis_ping path "/usr/local/sbin/mailwolt-redis-ping.sh"
|
||||
# if status != 0 for 2 cycles then exec "/bin/systemctl restart redis-server"
|
||||
#EOF
|
||||
#
|
||||
## 20 – Rspamd (robust via process-matching + Heal)
|
||||
#cat >/etc/monit/monitrc.d/20-rspamd.conf <<'EOF'
|
||||
#check process rspamd matching "rspamd: main process"
|
||||
# start program = "/bin/systemctl start rspamd" with timeout 120 seconds
|
||||
# stop program = "/bin/systemctl stop rspamd"
|
||||
# depends on redis
|
||||
# if failed host 127.0.0.1 port 11333 for 3 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
# if failed host 127.0.0.1 port 11334 for 3 cycles then exec "/usr/local/sbin/mailwolt-rspamd-heal.sh"
|
||||
# if does not exist for 2 cycles then restart
|
||||
# if 5 restarts within 10 cycles then unmonitor
|
||||
#EOF
|
||||
#
|
||||
## 30 – Postfix
|
||||
#cat >/etc/monit/monitrc.d/30-postfix.conf <<'EOF'
|
||||
#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 host 127.0.0.1 port 25 type tcp with timeout 15 seconds for 3 cycles then restart
|
||||
# if failed host 127.0.0.1 port 465 type tcpssl with timeout 10 seconds then restart
|
||||
# if failed host 127.0.0.1 port 587 type tcp with timeout 10 seconds then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
## 30 – Dovecot (IMAPS; LMTP oft Unix-Socket → kein TCP-Fehlalarm)
|
||||
#cat >/etc/monit/monitrc.d/30-dovecot.conf <<'EOF'
|
||||
#check process dovecot with pidfile /run/dovecot/master.pid
|
||||
# start program = "/bin/systemctl start dovecot"
|
||||
# stop program = "/bin/systemctl stop dovecot"
|
||||
# if failed port 993 type tcpssl for 3 cycles then restart
|
||||
# if 5 restarts within 10 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
## 40 – Nginx
|
||||
#cat >/etc/monit/monitrc.d/40-nginx.conf <<'EOF'
|
||||
#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 tcpssl then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
## 50 – OpenDKIM
|
||||
#cat >/etc/monit/monitrc.d/50-opendkim.conf <<'EOF'
|
||||
#check process opendkim with pidfile /run/opendkim/opendkim.pid
|
||||
# start program = "/bin/systemctl start opendkim"
|
||||
# stop program = "/bin/systemctl stop opendkim"
|
||||
# if failed host 127.0.0.1 port 8891 type tcp for 2 cycles then restart
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#
|
||||
## 55 – OpenDMARC (optional)
|
||||
#if [[ "$OPENDMARC_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/monitrc.d/55-opendmarc.conf <<'EOF'
|
||||
#check process opendmarc with pidfile /run/opendmarc/opendmarc.pid
|
||||
# start program = "/bin/systemctl start opendmarc"
|
||||
# stop program = "/bin/systemctl stop opendmarc"
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/monitrc.d/55-opendmarc.conf || true
|
||||
#fi
|
||||
#
|
||||
## 60 – ClamAV (über Socket)
|
||||
#if [[ "$CLAMAV_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/monitrc.d/60-clamav.conf <<'EOF'
|
||||
#check process clamd matching "clamd"
|
||||
# start program = "/bin/systemctl start clamav-daemon"
|
||||
# stop program = "/bin/systemctl stop clamav-daemon"
|
||||
# if failed unixsocket /run/clamav/clamd.ctl for 3 cycles then restart
|
||||
# if 5 restarts within 10 cycles then unmonitor
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/monitrc.d/60-clamav.conf || true
|
||||
#fi
|
||||
#
|
||||
## 70 – Fail2Ban (optional)
|
||||
#if [[ "$FAIL2BAN_ENABLE" = "1" ]]; then
|
||||
# cat >/etc/monit/monitrc.d/70-fail2ban.conf <<'EOF'
|
||||
#check process fail2ban with pidfile /run/fail2ban/fail2ban.pid
|
||||
# start program = "/bin/systemctl start fail2ban"
|
||||
# stop program = "/bin/systemctl stop fail2ban"
|
||||
# if 5 restarts within 5 cycles then alert
|
||||
#EOF
|
||||
#else
|
||||
# rm -f /etc/monit/monitrc.d/70-fail2ban.conf || true
|
||||
#fi
|
||||
#
|
||||
## ── Monit neu laden ─────────────────────────────────────────────────────────
|
||||
#monit -t
|
||||
#systemctl reload monit || systemctl restart monit
|
||||
#
|
||||
## Optionaler Sichttest (CLI funktioniert auch ohne HTTP-UI)
|
||||
##sleep 2
|
||||
##monit summary || true
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
log "MOTD installieren …"
|
||||
install -d /usr/local/bin
|
||||
|
||||
cat >/usr/local/bin/mw-motd <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
# MOTD – MailWolt
|
||||
# bewusst KEIN "set -e" und KEIN pipefail; MOTD darf nie hart abbrechen
|
||||
set -u
|
||||
|
||||
# ---------- Farben ----------
|
||||
NC="\033[0m"; WH="\033[1;37m"; CY="\033[1;36m"; GY="\033[0;90m"
|
||||
GR="\033[1;32m"; YE="\033[1;33m"; RD="\033[1;31m"
|
||||
|
||||
# ---------- Breite / Zentrierung ----------
|
||||
W=110
|
||||
term_cols=$(tput cols 2>/dev/null || echo $W)
|
||||
[ "$term_cols" -gt "$W" ] && pad=$(( (term_cols - W)/2 )) || pad=0
|
||||
sp(){ [ "$1" -gt 0 ] && printf "%${1}s" " " || true; }
|
||||
center() { local s="$1"; local n=$(( (W - ${#s})/2 )); sp $((pad+n)); printf "%s\n" "$s"; }
|
||||
rule(){ sp "$pad"; printf "%0.s=" $(seq 1 "$W"); printf "\n"; }
|
||||
title(){ sp "$pad"; local t="$1"; local lf=$(( (W - ${#t} - 2)/2 )); local rf=$(( W - ${#t} - 2 - lf )); \
|
||||
printf "%s" "$(printf '─%.0s' $(seq 1 $lf))"; printf " %s " "$t"; printf "%s\n" "$(printf '─%.0s' $(seq 1 $rf))"; }
|
||||
kv(){ sp "$pad"; printf "%-12s: %s\n" "$1" "$2"; }
|
||||
|
||||
# ---------- Installer-/App-Variablen ----------
|
||||
UI_HOST=""; WEBMAIL_HOST=""; MAIL_HOSTNAME=""
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env || true
|
||||
|
||||
# ---------- Systemdaten ----------
|
||||
now="$(date '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo '-')"
|
||||
upt="$(uptime -p 2>/dev/null || echo '-')"
|
||||
cores="$(nproc 2>/dev/null || echo 1)"
|
||||
load_raw="$(awk '{printf "%s / %s / %s",$1,$2,$3}' /proc/loadavg 2>/dev/null || echo '0.00 / 0.00 / 0.00')"
|
||||
load1="$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo 0)"
|
||||
|
||||
# RAM/SWAP
|
||||
mem_total="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
mem_avail="$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
mem_used=$(( mem_total - mem_avail ))
|
||||
swap_total="$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
swap_free="$(awk '/SwapFree/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)"
|
||||
swap_used=$(( swap_total - swap_free ))
|
||||
|
||||
pct(){ local u="$1" t="$2"; [ "$t" -gt 0 ] || { echo 0; return; }; awk -v u="$u" -v t="$t" 'BEGIN{printf "%d",(u*100)/t}' ; }
|
||||
ram_pct=$(pct "$mem_used" "$mem_total")
|
||||
swap_pct=$(pct "$swap_used" "$swap_total")
|
||||
|
||||
# Disks
|
||||
df_line(){ df -hP "$1" 2>/dev/null | awk 'NR==2{printf "%s / %s (%s)",$3,$2,$5}'; }
|
||||
df_pct(){ df -P "$1" 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5+0}'; }
|
||||
disk_root="$(df_line /)"; pct_root="$(df_pct /)"
|
||||
disk_var="$(df_line /var 2>/dev/null)"; [ -n "$disk_var" ] || disk_var="-"
|
||||
pct_var="$(df_pct /var 2>/dev/null)"; [ -n "$pct_var" ] || pct_var=0
|
||||
|
||||
# IPs (int/ext)
|
||||
ipv4_int="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i!~/:/){print $i;exit}}')"
|
||||
ipv6_int="$(hostname -I 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i~/:/){print $i;exit}}')"
|
||||
ipv4_ext="$(curl -4fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
|
||||
ipv6_ext="$(curl -6fsS --max-time 1 https://ifconfig.me 2>/dev/null || true)"
|
||||
|
||||
# ---------- Status-Farben ----------
|
||||
mark(){ # value thresholdY thresholdR
|
||||
local v="$1" y="$2" r="$3"
|
||||
if [ "$v" -ge "$r" ]; then printf "${RD}[HIGH]${NC}"
|
||||
elif [ "$v" -ge "$y" ]; then printf "${YE}[WARN]${NC}"
|
||||
else printf "${GR}[OK]${NC}"
|
||||
fi
|
||||
}
|
||||
# Load/CPU-Schwellen (pro Core)
|
||||
load_pct=$(awk -v l="$load1" -v c="$cores" 'BEGIN{if(c<1)c=1; printf "%d", (l/c)*100}')
|
||||
m_load="$(mark "$load_pct" 70 100)"
|
||||
m_ram="$(mark "$ram_pct" 75 90)"
|
||||
m_swap="$(mark "$swap_pct" 10 50)"
|
||||
m_root="$(mark "$pct_root" 75 90)"
|
||||
m_var="$(mark "$pct_var" 75 90)"
|
||||
|
||||
# ---------- Header ----------
|
||||
rule
|
||||
center ""
|
||||
center ":::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::"
|
||||
center ":+:+:+ :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: "
|
||||
center ":+: +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ "
|
||||
center "+#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+ "
|
||||
center "+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+ "
|
||||
center "#+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+# "
|
||||
center "### ### ### ### ########### ########## ### ### ######## ########## ### "
|
||||
center ""
|
||||
rule
|
||||
|
||||
# ---------- System ----------
|
||||
kv "Date / Time" "${YE}${now}${NC}"
|
||||
sp "$pad"; printf "%-12s: int %-40s ext %s\n" "IPv4" "${ipv4_int:--}" "${ipv4_ext:--}"
|
||||
sp "$pad"; printf "%-12s: int %-40s ext %s\n" "IPv6" "${ipv6_int:--}" "${ipv6_ext:--}"
|
||||
kv "Uptime" "$upt"
|
||||
sp "$pad"; printf "%-12s: %s cores, load %s %b\n" "CPU" "$cores" "$load_raw" "$m_load"
|
||||
sp "$pad"; printf "%-12s: %s MiB / %s MiB (%d%%) %b %-5s %s MiB / %s MiB (%d%%) %b\n" \
|
||||
"RAM" "$mem_used" "$mem_total" "$ram_pct" "$m_ram" "SWAP:" "$swap_used" "$swap_total" "$swap_pct" "$m_swap"
|
||||
sp "$pad"; printf "%-12s: / %s %b %-5s %s %b\n" \
|
||||
"Disk" "$disk_root" "$m_root" "/var:" "$disk_var" "$m_var"
|
||||
echo
|
||||
|
||||
# ---------- Domains ----------
|
||||
title "Domains"
|
||||
[ -n "${UI_HOST:-}" ] && kv "UI" "${UI_HOST}"
|
||||
[ -n "${WEBMAIL_HOST:-}" ] && kv "Webmail" "${WEBMAIL_HOST}"
|
||||
[ -n "${MAIL_HOSTNAME:-}" ]&& kv "MX" "${MAIL_HOSTNAME}"
|
||||
echo
|
||||
|
||||
# ---------- Services (4 Spalten, bündig) ----------
|
||||
title "Services"
|
||||
svc_state(){ systemctl is-active --quiet "$1" && printf "${GR}[OK]${NC}" || printf "${RD}[FAIL]${NC}"; }
|
||||
SVC=( nginx mariadb redis-server postfix dovecot rspamd opendkim opendmarc clamav-daemon fail2ban mailwolt-ws mailwolt-queue mailwolt-schedule )
|
||||
|
||||
i=0; line=""
|
||||
for s in "${SVC[@]}"; do
|
||||
st="$(svc_state "$s")"
|
||||
seg="$(printf "%-18s %-7s" "$s" "$st")"
|
||||
line="$line$seg"
|
||||
i=$((i+1))
|
||||
if [ $((i%4)) -eq 0 ]; then sp "$pad"; echo "$line"; line=""; else line="$line "; fi
|
||||
done
|
||||
[ -n "$line" ] && { sp "$pad"; echo "$line"; }
|
||||
echo
|
||||
|
||||
exit 0
|
||||
SH
|
||||
|
||||
chmod 755 /usr/local/bin/mw-motd
|
||||
|
||||
# update-motd Hook
|
||||
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
|
||||
else
|
||||
# Fallback für Systeme ohne dynamic MOTD
|
||||
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
|
||||
log "[✓] MOTD installiert."
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source ./lib.sh
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# MailWolt – Abschluss / Summary (Dienste, Zertifikate, Smoke-Test)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Farben & Deko
|
||||
NC="\033[0m"; BOLD="\033[1m"; DIM="\033[2m"
|
||||
GREEN="\033[1;32m"; RED="\033[1;31m"; YELLOW="\033[1;33m"; CYAN="\033[1;36m"; GREY="\033[0;90m"
|
||||
OKS="${GREEN}OK${NC}"; FAILS="${RED}FAIL${NC}"
|
||||
bar(){ printf "${CYAN}%s${NC}\n" "──────────────────────────────────────────────────────────────────────────────"; }
|
||||
ok(){ printf " [${OKS}]\n"; }
|
||||
fail(){ printf " [${FAILS}]\n"; }
|
||||
|
||||
# Installer-Variablen laden (falls vorhanden)
|
||||
set +u
|
||||
[ -r /etc/mailwolt/installer.env ] && . /etc/mailwolt/installer.env
|
||||
set -u
|
||||
|
||||
# Defaults / Umgebung
|
||||
APP_USER="${APP_USER:-mailwolt}"
|
||||
APP_GROUP="${APP_GROUP:-www-data}"
|
||||
APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
|
||||
|
||||
BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
|
||||
UI_HOST="${UI_HOST:-}"
|
||||
WEBMAIL_HOST="${WEBMAIL_HOST:-}"
|
||||
MAIL_HOSTNAME="${MAIL_HOSTNAME:-}"
|
||||
|
||||
APP_ENV="${APP_ENV:-production}"
|
||||
PROXY_MODE="${PROXY_MODE:-}" # leer = nicht anzeigen; "1"=Proxy, "dev"=Dev, sonst "nein"
|
||||
NPM_IP="${NPM_IP:-}"
|
||||
|
||||
LE_EMAIL="${LE_EMAIL:-admin@${BASE_DOMAIN}}"
|
||||
ACME_WEBROOT="/var/www/letsencrypt"
|
||||
|
||||
# Zert-Pfade (werden via Hook nach /etc/ssl/* verlinkt)
|
||||
UI_CERT="/etc/ssl/ui/fullchain.pem"
|
||||
UI_KEY="/etc/ssl/ui/privkey.pem"
|
||||
WEBMAIL_CERT="/etc/ssl/webmail/fullchain.pem"
|
||||
MAIL_CERT="/etc/ssl/mail/fullchain.pem"
|
||||
|
||||
# IPs (aus lib.sh)
|
||||
SERVER_PUBLIC_IPV4="${SERVER_PUBLIC_IPV4:-$(detect_ip)}"
|
||||
SERVER_PUBLIC_IPV6="${SERVER_PUBLIC_IPV6:-$(detect_ipv6)}"
|
||||
|
||||
# URLs (https nur, wenn UI-Cert+Key vorhanden)
|
||||
SCHEME="http"
|
||||
[[ -s "$UI_CERT" && -s "$UI_KEY" ]] && SCHEME="https"
|
||||
APP_URL="${SCHEME}://${UI_HOST:-$SERVER_PUBLIC_IPV4}"
|
||||
WEBMAIL_URL="${SCHEME}://${WEBMAIL_HOST:-$SERVER_PUBLIC_IPV4}"
|
||||
|
||||
# Ziel eines Symlinks auflösen
|
||||
real_target(){ readlink -f -- "$1" 2>/dev/null || true; }
|
||||
|
||||
# "LE" werten, wenn live/* ODER archive/* (auch fullchainN.pem) getroffen wird
|
||||
is_le_path(){
|
||||
local p="$1"
|
||||
[[ "$p" == /etc/letsencrypt/live/*/fullchain.pem || "$p" == /etc/letsencrypt/archive/*/fullchain*.pem ]]
|
||||
}
|
||||
|
||||
UI_CERT_TARGET="$(real_target "$UI_CERT")"
|
||||
WEBMAIL_CERT_TARGET="$(real_target "$WEBMAIL_CERT")"
|
||||
MAIL_CERT_TARGET="$(real_target "$MAIL_CERT")"
|
||||
|
||||
is_le_path() {
|
||||
case "$1" in
|
||||
/etc/letsencrypt/live/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# robust gegen set -u: immer ${var:-}
|
||||
UI_LE="self-signed/none"
|
||||
if [ -s "${UI_CERT:-}" ] && [ -n "${UI_CERT_TARGET:-}" ] && is_le_path "${UI_CERT_TARGET:-}"; then
|
||||
UI_LE="LE"
|
||||
fi
|
||||
|
||||
WEBMAIL_LE="self-signed/none"
|
||||
if [ -s "${WEBMAIL_CERT:-}" ] && [ -n "${WEBMAIL_CERT_TARGET:-}" ] && is_le_path "${WEBMAIL_CERT_TARGET:-}"; then
|
||||
WEBMAIL_LE="LE"
|
||||
fi
|
||||
|
||||
MAIL_LE="self-signed/none"
|
||||
if [ -s "${MAIL_CERT:-}" ] && [ -n "${MAIL_CERT_TARGET:-}" ] && is_le_path "${MAIL_CERT_TARGET:-}"; then
|
||||
MAIL_LE="LE"
|
||||
fi
|
||||
|
||||
echo
|
||||
bar
|
||||
printf " %s\n" "✔ MailWolt Bootstrap fertig"
|
||||
bar
|
||||
|
||||
# Kopf-Infos
|
||||
printf " %-14s %s\n" "Aufruf UI:" "${APP_URL}"
|
||||
printf " %-14s %s\n" "Webmail:" "${WEBMAIL_URL}"
|
||||
printf " %-14s %s\n" "App Root:" "${APP_DIR}"
|
||||
printf " %-14s %s\n" "Mail-FQDN:" "${MAIL_HOSTNAME:-$SERVER_PUBLIC_IPV4}"
|
||||
printf " %-14s %s\n" "BASE_DOMAIN:" "${BASE_DOMAIN}"
|
||||
printf " %-14s %s\n" "LE-Email:" "${LE_EMAIL}"
|
||||
printf " %-14s %s\n" "APP_ENV:" "${APP_ENV}"
|
||||
# Proxy-Block nur anzeigen, wenn Variable gesetzt ist
|
||||
if [[ -n "$PROXY_MODE" ]]; then
|
||||
if [[ "$PROXY_MODE" == "1" ]]; then
|
||||
printf " %-14s %s\n" "Proxy-Mode:" "ja (NPM: ${NPM_IP:-unbekannt})"
|
||||
elif [[ "$PROXY_MODE" == "dev" ]]; then
|
||||
printf " %-14s %s\n" "Proxy-Mode:" "Entwicklungsmodus"
|
||||
else
|
||||
printf " %-14s %s\n" "Proxy-Mode:" "nein"
|
||||
fi
|
||||
fi
|
||||
printf " %-14s %s\n" "Server IPv6:" "${SERVER_PUBLIC_IPV6:-–}"
|
||||
printf " %-14s %s\n" "ACME Webroot:" "${ACME_WEBROOT}"
|
||||
|
||||
echo
|
||||
printf " %-14s UI=%s, Webmail=%s, MX=%s\n" "Zertifikate:" "$UI_LE" "$WEBMAIL_LE" "$MAIL_LE"
|
||||
echo
|
||||
|
||||
echo " Anmeldung: Keine vordefinierten Admin-Daten."
|
||||
echo " Bitte zuerst registrieren (Erst-User wird Admin, danach"
|
||||
echo " wird die Registrierung automatisch gesperrt)."
|
||||
echo
|
||||
|
||||
# ── Dienste ────────────────────────────────────────────────────────────────
|
||||
bar
|
||||
echo " Services"
|
||||
bar
|
||||
|
||||
OK_LIST=()
|
||||
FAIL_LIST=()
|
||||
|
||||
svc(){
|
||||
local unit="$1" label="${2:-$1}"
|
||||
printf " • %-18s … " "$label"
|
||||
if systemctl is-active --quiet "$unit"; then
|
||||
ok
|
||||
OK_LIST+=("$label")
|
||||
else
|
||||
fail
|
||||
FAIL_LIST+=("$label")
|
||||
fi
|
||||
}
|
||||
|
||||
# Kern-Services
|
||||
svc nginx
|
||||
svc mariadb
|
||||
svc redis-server
|
||||
svc postfix
|
||||
svc dovecot
|
||||
# App-Worker (tolerant)
|
||||
svc "${APP_USER}-ws" "mailwolt-ws" || true
|
||||
svc "${APP_USER}-schedule" "mailwolt-schedule" || true
|
||||
svc "${APP_USER}-queue" "mailwolt-queue" || true
|
||||
|
||||
echo
|
||||
if ((${#OK_LIST[@]})); then
|
||||
printf " ${GREEN}OK:${NC} %s\n" "$(IFS=', '; echo "${OK_LIST[*]}")"
|
||||
fi
|
||||
if ((${#FAIL_LIST[@]})); then
|
||||
printf " ${RED}FAIL:${NC} %s\n" "$(IFS=', '; echo "${FAIL_LIST[*]}")"
|
||||
echo " ${YELLOW}Hinweis:${NC} Details mit: journalctl -u <dienst> -b --no-pager"
|
||||
fi
|
||||
echo
|
||||
|
||||
# ── Smoke-Test ─────────────────────────────────────────────────────────────
|
||||
bar
|
||||
echo " Smoke-Test (SMTP/IMAP/POP3 mit/ohne TLS)"
|
||||
bar
|
||||
|
||||
check_port(){
|
||||
local tag="$1" cmd="$2" desc="$3"
|
||||
printf " [%-3s] %-35s … " "$tag" "$desc"
|
||||
if timeout 8s bash -lc "$cmd" >/dev/null 2>&1; then ok; else fail; fi
|
||||
}
|
||||
|
||||
# kleines Delay nach Erststart
|
||||
sleep 6 || true
|
||||
|
||||
# SMTP
|
||||
check_port "25" 'printf "EHLO x\r\nQUIT\r\n" | nc -w 3 127.0.0.1 25' \
|
||||
"SMTP (EHLO)"
|
||||
check_port "465" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:465 -quiet -ign_eof' \
|
||||
"SMTPS (TLS + EHLO)"
|
||||
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' \
|
||||
"Submission (STARTTLS)"
|
||||
|
||||
# POP/IMAP
|
||||
check_port "110" 'printf "QUIT\r\n" | nc -w 3 127.0.0.1 110' \
|
||||
"POP3 (QUIT)"
|
||||
check_port "995" 'printf "QUIT\r\n" | openssl s_client -connect 127.0.0.1:995 -quiet -ign_eof' \
|
||||
"POP3S (TLS + QUIT)"
|
||||
check_port "143" 'printf ". CAPABILITY\r\n. LOGOUT\r\n" | nc -w 3 127.0.0.1 143' \
|
||||
"IMAP (CAPABILITY/LOGOUT)"
|
||||
check_port "993" 'printf ". CAPABILITY\r\n. LOGOUT\r\n" | openssl s_client -connect 127.0.0.1:993 -quiet -ign_eof' \
|
||||
"IMAPS (TLS + CAPABILITY/LOGOUT)"
|
||||
|
||||
echo
|
||||
|
||||
# Hinweise nur ausgeben, wenn wirklich kein LE für UI/Webmail
|
||||
if [[ "$UI_LE" != "LE" || "$WEBMAIL_LE" != "LE" ]]; then
|
||||
echo -e " ${YELLOW}Hinweis:${NC} UI/Webmail verwenden noch kein Let's-Encrypt-Zertifikat."
|
||||
echo -e " Prüfe Symlinks unter /etc/ssl/{ui,webmail} und den LE-Hook (21/75-Skripte)."
|
||||
echo
|
||||
fi
|
||||
|
||||
# Proxy-Info (optional)
|
||||
if [[ "$PROXY_MODE" == "1" ]]; then
|
||||
echo -e " ${GREY}Proxy-Hinweis:${NC} App erwartet TLS am Proxy (Backend ohne https-Redirects)."
|
||||
echo
|
||||
fi
|
||||
|
|
@ -0,0 +1,633 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# --- Farbschema für whiptail (libnewt) – hohe Lesbarkeit (dunkler Input, schwarze Schrift) ---
|
||||
export NEWT_COLORS='
|
||||
root=,blue
|
||||
border=black,lightgray
|
||||
window=black,lightgray
|
||||
textbox=black,lightgray
|
||||
label=black,lightgray
|
||||
entry=black,cyan
|
||||
button=black,cyan
|
||||
actlistbox=black,cyan
|
||||
actsellistbox=black,cyan
|
||||
'
|
||||
|
||||
# optionales Backtitle (erscheint oben)
|
||||
export DIALOGOPTS="--backtitle MailWolt Setup"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# MailWolt – Interaktiver Bootstrap (whiptail + Fallback)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
DEV_MODE=0
|
||||
PROXY_MODE=0
|
||||
NPM_IP=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-dev) DEV_MODE=1 ;;
|
||||
-proxy) PROXY_MODE=1; NPM_IP="${2:-}"; shift ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
|
||||
APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
|
||||
export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
|
||||
|
||||
DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
|
||||
REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
|
||||
export DB_PASS REDIS_PASS
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
source ./lib.sh
|
||||
require_root
|
||||
header
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────
|
||||
APP_NAME="${APP_NAME:-MailWolt}"
|
||||
APP_USER="${APP_USER:-mailwolt}"
|
||||
APP_GROUP="${APP_GROUP:-www-data}"
|
||||
APP_USER_PREFIX="${APP_USER_PREFIX:-mw}"
|
||||
APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
|
||||
|
||||
BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
|
||||
UI_SUB="${UI_SUB:-ui}"
|
||||
WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}"
|
||||
MTA_SUB="${MTA_SUB:-mx}"
|
||||
|
||||
DB_NAME="${DB_NAME:-${APP_USER}}"
|
||||
DB_USER="${DB_USER:-${APP_USER}}"
|
||||
|
||||
SERVER_PUBLIC_IPV4="$(detect_ip)"
|
||||
SERVER_PUBLIC_IPV6="$(detect_ipv6)"
|
||||
DEFAULT_TZ="$(detect_timezone)"
|
||||
DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
|
||||
|
||||
echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-–}${NC}"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────
|
||||
have_whiptail(){ command -v whiptail >/dev/null 2>&1; }
|
||||
|
||||
#valid_fqdn(){
|
||||
# [[ "$1" =~ ^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,}$ ]]
|
||||
#}
|
||||
|
||||
# ── Host-Validierung & DEV-Erkennung ────────────────────────────────────────
|
||||
valid_fqdn_prod(){ [[ "$1" =~ ^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+[a-z]{2,}$ ]]; }
|
||||
valid_host_dev(){
|
||||
# erlaubt: single-label (ui, webmail), FQDNs, IPv4
|
||||
[[ "$1" =~ ^([a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9-]+)*$ ]] || [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
|
||||
}
|
||||
is_local_like(){
|
||||
local h="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
|
||||
[[ "$h" =~ \.local$ || "$h" =~ \.loc$ || "$h" =~ \.dev$ || "$h" =~ \.test$ || "$h" = "localhost" ]] && return 0
|
||||
[[ "$h" =~ ^10\. || "$h" =~ ^192\.168\. || "$h" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. || "$h" =~ ^127\. ]] && return 0
|
||||
return 1
|
||||
}
|
||||
normalize_host(){
|
||||
# $1=input $2=default (nutzt DEV_MODE für die passende Prüflogik)
|
||||
local inp="$1" def="$2"
|
||||
if [[ "${DEV_MODE}" = "1" ]]; then
|
||||
valid_host_dev "$inp" && { echo "$inp"; return; }
|
||||
else
|
||||
valid_fqdn_prod "$inp" && { echo "$inp"; return; }
|
||||
fi
|
||||
echo "$def"
|
||||
}
|
||||
|
||||
ask_tty_domain(){
|
||||
local label="$1" example="$2" def="$3" outvar="$4" inp
|
||||
echo -e "${CYAN}${label}${NC}"
|
||||
echo -e " z.B. ${YELLOW}${example}${NC}"
|
||||
echo -e " Default: ${GREY}${def}${NC}"
|
||||
read -r -p " Eingabe (Enter=Default): " inp || true
|
||||
inp="${inp:-$def}"
|
||||
if ! valid_fqdn "$inp"; then
|
||||
echo -e "${YELLOW}[!] Ungültiger FQDN, nehme Default: ${def}${NC}"
|
||||
inp="$def"
|
||||
fi
|
||||
eval "$outvar='$inp'"
|
||||
}
|
||||
|
||||
# ── Interaktive Eingaben (whiptail oder Fallback) ─────────────
|
||||
MTA_DEFAULT="${MTA_SUB}.${BASE_DOMAIN}"
|
||||
UI_DEFAULT="${UI_SUB}.${BASE_DOMAIN}"
|
||||
WEBMAIL_DEFAULT="${WEBMAIL_SUB}.${BASE_DOMAIN}"
|
||||
|
||||
CLAMAV_ENABLE=1
|
||||
OPENDMARC_ENABLE=1
|
||||
FAIL2BAN_ENABLE=1
|
||||
|
||||
if command -v whiptail >/dev/null 2>&1; then
|
||||
TITLE="MailWolt Setup"
|
||||
|
||||
# Hinweise zu erlaubten DEV-Hosts
|
||||
MSG_SUFFIX="\n\nHinweis: Im DEV-Modus sind auch single-label Hosts (z.B. ui, webmail), *.local/*.dev und IPs erlaubt."
|
||||
|
||||
_mta_in="$(whiptail --title "$TITLE" --inputbox "Mailserver-Host (MX)\nBeispiele: mx.domain.tld | mx.local | 10.0.0.10${MSG_SUFFIX}" 13 70 "$MTA_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
_ui_in="$(whiptail --title "$TITLE" --inputbox "UI / Admin-Panel Host\nBeispiele: ui.domain.tld | ui.local | 10.0.0.10${MSG_SUFFIX}" 13 70 "$UI_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
_wm_in="$(whiptail --title "$TITLE" --inputbox "Webmail Host\nBeispiele: webmail.domain.tld | web.local | 10.0.0.10${MSG_SUFFIX}" 13 70 "$WEBMAIL_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
|
||||
# ZUERST provisorisch prüfen, ob „lokal“ → DEV erzwingen
|
||||
if is_local_like "$_mta_in" || is_local_like "$_ui_in" || is_local_like "$_wm_in"; then
|
||||
DEV_MODE=1; APP_ENV="local"; APP_DEBUG="true"
|
||||
fi
|
||||
export DEV_MODE APP_ENV APP_DEBUG
|
||||
|
||||
# Jetzt mit passender Logik normalisieren
|
||||
MTA_FQDN="$(normalize_host "$_mta_in" "$MTA_DEFAULT")"
|
||||
UI_FQDN="$(normalize_host "$_ui_in" "$UI_DEFAULT")"
|
||||
WEBMAIL_FQDN="$(normalize_host "$_wm_in" "$WEBMAIL_DEFAULT")"
|
||||
|
||||
CHOICES="$(whiptail --title "$TITLE" --checklist "Optionale Dienste aktivieren" 15 70 6 \
|
||||
"ClamAV" "Virenscan (clamd/clamav-daemon)" ON \
|
||||
"OpenDMARC" "DMARC-Auswertung" ON \
|
||||
"Fail2Ban" "Brute-Force-Schutz" ON \
|
||||
3>&1 1>&2 2>&3)" || true
|
||||
CLAMAV_ENABLE=0; [[ "$CHOICES" == *"ClamAV"* ]] && CLAMAV_ENABLE=1
|
||||
OPENDMARC_ENABLE=0; [[ "$CHOICES" == *"OpenDMARC"* ]] && OPENDMARC_ENABLE=1
|
||||
FAIL2BAN_ENABLE=0; [[ "$CHOICES" == *"Fail2Ban"* ]] && FAIL2BAN_ENABLE=1
|
||||
|
||||
else
|
||||
echo -e "${GREY}[i] whiptail nicht gefunden – TTY-Fallback.${NC}\n"
|
||||
read -r -p "Mailserver-Host (MX) [${MTA_DEFAULT}]: " _mta_in; _mta_in="${_mta_in:-$MTA_DEFAULT}"
|
||||
read -r -p "UI / Admin-Panel Host [${UI_DEFAULT}]: " _ui_in; _ui_in="${_ui_in:-$UI_DEFAULT}"
|
||||
read -r -p "Webmail Host [${WEBMAIL_DEFAULT}]: " _wm_in; _wm_in="${_wm_in:-$WEBMAIL_DEFAULT}"
|
||||
|
||||
if is_local_like "$_mta_in" || is_local_like "$_ui_in" || is_local_like "$_wm_in"; then
|
||||
DEV_MODE=1; APP_ENV="local"; APP_DEBUG="true"
|
||||
fi
|
||||
export DEV_MODE APP_ENV APP_DEBUG
|
||||
|
||||
MTA_FQDN="$(normalize_host "$_mta_in" "$MTA_DEFAULT")"
|
||||
UI_FQDN="$(normalize_host "$_ui_in" "$UI_DEFAULT")"
|
||||
WEBMAIL_FQDN="$(normalize_host "$_wm_in" "$WEBMAIL_DEFAULT")"
|
||||
|
||||
read -r -p "ClamAV aktivieren? (1/0, Enter=1): " CLAMAV_ENABLE; CLAMAV_ENABLE="${CLAMAV_ENABLE:-1}"
|
||||
read -r -p "OpenDMARC aktivieren? (1/0, Enter=1): " OPENDMARC_ENABLE; OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
|
||||
read -r -p "Fail2Ban aktivieren? (1/0, Enter=1): " FAIL2BAN_ENABLE; FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
fi
|
||||
|
||||
#if have_whiptail; then
|
||||
# TITLE="MailWolt Setup"
|
||||
#
|
||||
# MTA_FQDN="$(whiptail --title "$TITLE" --inputbox "Mailserver-FQDN (MX)\nBeispiel: mx.domain.tld" 11 70 "$MTA_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
# valid_fqdn "$MTA_FQDN" || MTA_FQDN="$MTA_DEFAULT"
|
||||
#
|
||||
# UI_FQDN="$(whiptail --title "$TITLE" --inputbox "UI / Admin-Panel FQDN\nBeispiel: ui.domain.tld" 11 70 "$UI_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
# valid_fqdn "$UI_FQDN" || UI_FQDN="$UI_DEFAULT"
|
||||
#
|
||||
# WEBMAIL_FQDN="$(whiptail --title "$TITLE" --inputbox "Webmail FQDN\nBeispiel: webmail.domain.tld" 11 70 "$WEBMAIL_DEFAULT" 3>&1 1>&2 2>&3)" || exit 1
|
||||
# valid_fqdn "$WEBMAIL_FQDN" || WEBMAIL_FQDN="$WEBMAIL_DEFAULT"
|
||||
#
|
||||
# CHOICES="$(whiptail --title "$TITLE" --checklist "Optionale Dienste aktivieren" 15 70 6 \
|
||||
# "ClamAV" "Virenscan (clamd/clamav-daemon)" ON \
|
||||
# "OpenDMARC" "DMARC-Auswertung" ON \
|
||||
# "Fail2Ban" "Brute-Force-Schutz" ON \
|
||||
# 3>&1 1>&2 2>&3)" || true
|
||||
#
|
||||
# CLAMAV_ENABLE=0; [[ "$CHOICES" == *"ClamAV"* ]] && CLAMAV_ENABLE=1
|
||||
# OPENDMARC_ENABLE=0; [[ "$CHOICES" == *"OpenDMARC"* ]] && OPENDMARC_ENABLE=1
|
||||
# FAIL2BAN_ENABLE=0; [[ "$CHOICES" == *"Fail2Ban"* ]] && FAIL2BAN_ENABLE=1
|
||||
#
|
||||
# whiptail --title "$TITLE" --msgbox "Zusammenfassung:
|
||||
#
|
||||
#MX : $MTA_FQDN
|
||||
#UI : $UI_FQDN
|
||||
#Webmail : $WEBMAIL_FQDN
|
||||
#
|
||||
#ClamAV : $([[ $CLAMAV_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
|
||||
#OpenDMARC : $([[ $OPENDMARC_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
|
||||
#Fail2Ban : $([[ $FAIL2BAN_ENABLE -eq 1 ]] && echo Aktiv || echo Deaktiv)
|
||||
#" 16 70
|
||||
#
|
||||
#else
|
||||
# echo -e "${GREY}[i] whiptail nicht gefunden – nutze TTY-Prompts.${NC}\n"
|
||||
# ask_tty_domain "Mailserver-FQDN (MX)" "mx.domain.tld" "$MTA_DEFAULT" MTA_FQDN
|
||||
# ask_tty_domain "UI / Admin-Panel FQDN" "ui.domain.tld" "$UI_DEFAULT" UI_FQDN
|
||||
# ask_tty_domain "Webmail FQDN" "webmail.domain.tld" "$WEBMAIL_DEFAULT" WEBMAIL_FQDN
|
||||
#
|
||||
# read -r -p "ClamAV aktivieren? (1/0, Enter=1): " CLAMAV_ENABLE; CLAMAV_ENABLE="${CLAMAV_ENABLE:-1}"
|
||||
# read -r -p "OpenDMARC aktivieren? (1/0, Enter=1): " OPENDMARC_ENABLE; OPENDMARC_ENABLE="${OPENDMARC_ENABLE:-1}"
|
||||
# read -r -p "Fail2Ban aktivieren? (1/0, Enter=1): " FAIL2BAN_ENABLE; FAIL2BAN_ENABLE="${FAIL2BAN_ENABLE:-1}"
|
||||
#fi
|
||||
|
||||
# ── Defaults/Kompatibilität ──────────────────────────────────
|
||||
MTA_FQDN="${MTA_FQDN:-${MTA_DEFAULT}}"
|
||||
UI_FQDN="${UI_FQDN:-${UI_DEFAULT}}"
|
||||
WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_DEFAULT}}"
|
||||
DKIM_ENABLE="${DKIM_ENABLE:-1}"
|
||||
DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
|
||||
DKIM_GENERATE="${DKIM_GENERATE:-1}"
|
||||
|
||||
# BASE_DOMAIN/Subs aus FQDNs ableiten
|
||||
if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then MTA_SUB="${BASH_REMATCH[1]}"; BASE_DOMAIN="${BASH_REMATCH[2]}"; fi
|
||||
if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then UI_SUB="${BASH_REMATCH[1]}"; fi
|
||||
if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then WEBMAIL_SUB="${BASH_REMATCH[1]}"; fi
|
||||
|
||||
SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
|
||||
SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
|
||||
|
||||
MAIL_HOSTNAME="${MTA_FQDN}"
|
||||
UI_HOST="${UI_FQDN}"
|
||||
WEBMAIL_HOST="${WEBMAIL_FQDN}"
|
||||
|
||||
APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
|
||||
APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
|
||||
|
||||
# ── Export & persist ─────────────────────────────────────────
|
||||
export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
|
||||
export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
|
||||
export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
|
||||
export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
|
||||
export DB_NAME DB_USER
|
||||
export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
|
||||
export CLAMAV_ENABLE OPENDMARC_ENABLE FAIL2BAN_ENABLE
|
||||
|
||||
install -d -m 0755 /etc/mailwolt
|
||||
cat >/etc/mailwolt/installer.env <<EOF
|
||||
SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
|
||||
SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
|
||||
APP_TZ=${APP_TZ}
|
||||
APP_LOCALE=${APP_LOCALE}
|
||||
BASE_DOMAIN=${BASE_DOMAIN}
|
||||
MTA_SUB=${MTA_SUB}
|
||||
UI_SUB=${UI_SUB}
|
||||
WEBMAIL_SUB=${WEBMAIL_SUB}
|
||||
|
||||
MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
UI_HOST=${UI_HOST}
|
||||
WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
|
||||
LE_EMAIL=${LE_EMAIL:-admin@${BASE_DOMAIN}}
|
||||
SYSMAIL_SUB=${SYSMAIL_SUB}
|
||||
SYSMAIL_DOMAIN=${SYSMAIL_DOMAIN}
|
||||
|
||||
DKIM_ENABLE=${DKIM_ENABLE}
|
||||
DKIM_SELECTOR=${DKIM_SELECTOR}
|
||||
DKIM_GENERATE=${DKIM_GENERATE}
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_NAME=${DB_NAME}
|
||||
DB_USER=${DB_USER}
|
||||
DB_PASS=${DB_PASS}
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASS=${REDIS_PASS}
|
||||
|
||||
SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
|
||||
SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
|
||||
APP_ENV=${APP_ENV}
|
||||
|
||||
CLAMAV_ENABLE=${CLAMAV_ENABLE}
|
||||
OPENDMARC_ENABLE=${OPENDMARC_ENABLE}
|
||||
FAIL2BAN_ENABLE=${FAIL2BAN_ENABLE}
|
||||
|
||||
BACKUP_ONCALENDAR="${BACKUP_ONCALENDAR:-*-*-* 03:00:00}"
|
||||
BACKUP_ENABLED=0
|
||||
BACKUP_INTERVAL=daily
|
||||
BACKUP_RETENTION_DAYS=7
|
||||
BACKUP_DIR=/var/backups/mailwolt
|
||||
BACKUP_USE_ZSTD=1
|
||||
EOF
|
||||
chmod 600 /etc/mailwolt/installer.env
|
||||
|
||||
# ── Installer-Sequenz ────────────────────────────────────────
|
||||
for STEP in \
|
||||
10-provision \
|
||||
20-ssl 21-le-deploy-hook 22-dkim-helper \
|
||||
30-db 40-postfix 50-dovecot \
|
||||
60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban 64-apply-milters \
|
||||
70-nginx 75-le-issue 80-app 88-update-wrapper 90-services \
|
||||
92-sudoers-npm 93-backup-tools 95-woltguard 98-motd 99-summary
|
||||
do
|
||||
log ">>> Running ${STEP}.sh"
|
||||
bash "./${STEP}.sh"
|
||||
done
|
||||
|
||||
##!/usr/bin/env bash
|
||||
#set -euo pipefail
|
||||
#
|
||||
## --- Flags / Modi ---
|
||||
#DEV_MODE=0
|
||||
#PROXY_MODE=0
|
||||
#NPM_IP=""
|
||||
#
|
||||
#while [[ $# -gt 0 ]]; do
|
||||
# case "$1" in
|
||||
# -dev) DEV_MODE=1 ;;
|
||||
# -proxy) PROXY_MODE=1; NPM_IP="${2:-}"; shift ;;
|
||||
# esac
|
||||
# shift
|
||||
#done
|
||||
#
|
||||
#APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
|
||||
#APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
|
||||
#export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
|
||||
#
|
||||
#DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
|
||||
#REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
|
||||
#
|
||||
#export DB_PASS REDIS_PASS
|
||||
#
|
||||
#cd "$(dirname "$0")"
|
||||
#source ./lib.sh
|
||||
#require_root
|
||||
#header
|
||||
#
|
||||
## ── Defaults ────────────────────────────────────────────────────────────────
|
||||
#APP_NAME="${APP_NAME:-MailWolt}"
|
||||
#APP_USER="${APP_USER:-mailwolt}"
|
||||
#APP_GROUP="${APP_GROUP:-www-data}"
|
||||
#APP_USER_PREFIX="${APP_USER_PREFIX:-mw}"
|
||||
#APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
|
||||
#
|
||||
#BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
|
||||
#UI_SUB="${UI_SUB:-ui}"
|
||||
#WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}"
|
||||
#MTA_SUB="${MTA_SUB:-mx}"
|
||||
#
|
||||
#DB_NAME="${DB_NAME:-${APP_USER}}"
|
||||
#DB_USER="${DB_USER:-${APP_USER}}"
|
||||
#
|
||||
#SERVER_PUBLIC_IPV4="$(detect_ip)"
|
||||
#SERVER_PUBLIC_IPV6="$(detect_ipv6)"
|
||||
#DEFAULT_TZ="$(detect_timezone)"
|
||||
#DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
|
||||
#
|
||||
#echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-–}${NC}"
|
||||
#
|
||||
## ── Schöne, farbige Abfragen ────────────────────────────────────────────────
|
||||
#echo -e "${CYAN}"
|
||||
#echo "──────────────────────────────────────────────"
|
||||
#echo -e " 📧 MailWolt Setup – Domain Konfiguration"
|
||||
#echo "──────────────────────────────────────────────"
|
||||
#echo -e "${NC}"
|
||||
#
|
||||
#MTA_DEFAULT="${MTA_SUB}.${BASE_DOMAIN}"
|
||||
#UI_DEFAULT="${UI_SUB}.${BASE_DOMAIN}"
|
||||
#WEBMAIL_DEFAULT="${WEBMAIL_SUB}.${BASE_DOMAIN}"
|
||||
#
|
||||
#ask_domain() {
|
||||
# local __outvar="$1" label="$2" example="$3" defval="$4" input=""
|
||||
# echo -e "${GREEN}[?]${NC} ${label}"
|
||||
# echo -e " z.B. ${YELLOW}${example}${NC}"
|
||||
# echo -e " Default: ${CYAN}${defval}${NC}"
|
||||
# echo -ne " → Eingabe: ${CYAN}"
|
||||
# read -r input
|
||||
# echo -e "${NC}"
|
||||
# if [[ -z "$input" ]]; then
|
||||
# eval "$__outvar='$defval'"
|
||||
# else
|
||||
# eval "$__outvar='$input'"
|
||||
# fi
|
||||
#}
|
||||
#
|
||||
#ask_toggle() {
|
||||
# local __outvar="$1" label="$2" defval="${3:-1}" input=""
|
||||
# echo -ne "${GREEN}[?]${NC} ${label} (${CYAN}1${NC}=Ja / ${YELLOW}0${NC}=Nein) [Enter=${defval}]: "
|
||||
# read -r input
|
||||
# input="${input:-$defval}"
|
||||
# case "$input" in
|
||||
# 1|0) ;;
|
||||
# *) echo -e "${YELLOW}Ungültig, nehme Default=${defval}.${NC}"; input="$defval" ;;
|
||||
# esac
|
||||
# eval "$__outvar='$input'"
|
||||
#}
|
||||
#
|
||||
#ask_domain "MTA_FQDN" "Mailserver-FQDN (MX)" "mx.domain.tld" "$MTA_DEFAULT"
|
||||
#ask_domain "UI_FQDN" "UI / Admin-Panel" "ui.domain.tld" "$UI_DEFAULT"
|
||||
#ask_domain "WEBMAIL_FQDN" "Webmail-FQDN" "webmail.domain.tld" "$WEBMAIL_DEFAULT"
|
||||
#
|
||||
#echo -e "${CYAN}"
|
||||
#echo "──────────────────────────────────────────────"
|
||||
#echo -e " 🛡 Optionale Dienste"
|
||||
#echo "──────────────────────────────────────────────"
|
||||
#echo -e "${NC}"
|
||||
#
|
||||
#ask_toggle "CLAMAV_ENABLE" "ClamAV Virenscan aktivieren?" 1
|
||||
#ask_toggle "OPENDMARC_ENABLE" "OpenDMARC auswerten?" 1
|
||||
#ask_toggle "FAIL2BAN_ENABLE" "Fail2Ban aktivieren?" 1
|
||||
#echo
|
||||
#
|
||||
## Defaults, wenn Enter gedrückt (Abwärtskompatibilität)
|
||||
#MTA_FQDN="${MTA_FQDN:-${MTA_SUB}.${BASE_DOMAIN}}"
|
||||
#UI_FQDN="${UI_FQDN:-${UI_SUB}.${BASE_DOMAIN}}"
|
||||
#WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_SUB}.${BASE_DOMAIN}}"
|
||||
#DKIM_ENABLE="${DKIM_ENABLE:-1}"
|
||||
#DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
|
||||
#DKIM_GENERATE="${DKIM_GENERATE:-1}"
|
||||
#
|
||||
## BASE_DOMAIN und Sub-Labels aus MTA/UI/WEBMAIL ableiten (robust)
|
||||
#if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
# MTA_SUB="${BASH_REMATCH[1]}"
|
||||
# BASE_DOMAIN="${BASH_REMATCH[2]}"
|
||||
#fi
|
||||
#if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
# UI_SUB="${BASH_REMATCH[1]}"
|
||||
#fi
|
||||
#if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
# WEBMAIL_SUB="${BASH_REMATCH[1]}"
|
||||
#fi
|
||||
#
|
||||
#SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
|
||||
#SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
|
||||
## Kanonische Host-Variablen (NIE wieder zusammenbauen – nimm die FQDNs)
|
||||
#MAIL_HOSTNAME="${MTA_FQDN}"
|
||||
#UI_HOST="${UI_FQDN}"
|
||||
#WEBMAIL_HOST="${WEBMAIL_FQDN}"
|
||||
#
|
||||
## Zeitzone/Locale sinnvoll setzen
|
||||
#APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
|
||||
#APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
|
||||
#
|
||||
## ── Variablen exportieren ───────────────────────────────────────────────────
|
||||
#export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
|
||||
#export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
|
||||
#export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
|
||||
#export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
|
||||
#export DB_NAME DB_USER
|
||||
#export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
|
||||
#export CLAMAV_ENABLE OPENDMARC_ENABLE FAIL2BAN_ENABLE
|
||||
#
|
||||
#install -d -m 0755 /etc/mailwolt
|
||||
#cat >/etc/mailwolt/installer.env <<EOF
|
||||
#BASE_DOMAIN=${BASE_DOMAIN}
|
||||
#MTA_SUB=${MTA_SUB}
|
||||
#UI_SUB=${UI_SUB}
|
||||
#WEBMAIL_SUB=${WEBMAIL_SUB}
|
||||
#
|
||||
#MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
#UI_HOST=${UI_HOST}
|
||||
#WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
#
|
||||
#SYSMAIL_SUB=${SYSMAIL_SUB}
|
||||
#SYSMAIL_DOMAIN=${SYSMAIL_DOMAIN}
|
||||
#
|
||||
#DKIM_ENABLE=${DKIM_ENABLE}
|
||||
#DKIM_SELECTOR=${DKIM_SELECTOR}
|
||||
#DKIM_GENERATE=${DKIM_GENERATE}
|
||||
#
|
||||
#DB_HOST=127.0.0.1
|
||||
#DB_NAME=${DB_NAME}
|
||||
#DB_USER=${DB_USER}
|
||||
#DB_PASS=${DB_PASS}
|
||||
#REDIS_PASS=${REDIS_PASS}
|
||||
#
|
||||
#SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
|
||||
#SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
|
||||
#APP_ENV=${APP_ENV}
|
||||
#
|
||||
#CLAMAV_ENABLE=${CLAMAV_ENABLE}
|
||||
#OPENDMARC_ENABLE=${OPENDMARC_ENABLE}
|
||||
#FAIL2BAN_ENABLE=${FAIL2BAN_ENABLE}
|
||||
#EOF
|
||||
#
|
||||
#chmod 600 /etc/mailwolt/installer.env
|
||||
#
|
||||
## ── Sequenz ────────────────────────────────────────────────────────────────
|
||||
#for STEP in 10-provision 20-ssl 21-le-deploy-hook 22-dkim-helper 30-db 40-postfix 50-dovecot 60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban 70-nginx 75-le-issue 80-app 90-services 95-woltguard 98-motd 99-summary
|
||||
#do
|
||||
# log ">>> Running ${STEP}.sh"
|
||||
# bash "./${STEP}.sh"
|
||||
#done
|
||||
###!/usr/bin/env bash
|
||||
##set -euo pipefail
|
||||
##
|
||||
### --- Flags / Modi ---
|
||||
##DEV_MODE=0
|
||||
##PROXY_MODE=0
|
||||
##NPM_IP=""
|
||||
##
|
||||
##while [[ $# -gt 0 ]]; do
|
||||
## case "$1" in
|
||||
## -dev) DEV_MODE=1 ;;
|
||||
## -proxy) PROXY_MODE=1; NPM_IP="${2:-}"; shift ;;
|
||||
## esac
|
||||
## shift
|
||||
##done
|
||||
##
|
||||
##APP_ENV="${APP_ENV:-$([[ $DEV_MODE -eq 1 ]] && echo local || echo production)}"
|
||||
##APP_DEBUG="${APP_DEBUG:-$([[ $DEV_MODE -eq 1 ]] && echo true || echo false)}"
|
||||
##export DEV_MODE PROXY_MODE NPM_IP APP_ENV APP_DEBUG
|
||||
##
|
||||
##DB_PASS="${DB_PASS:-$(openssl rand -hex 16)}"
|
||||
##REDIS_PASS="${REDIS_PASS:-$(openssl rand -hex 16)}"
|
||||
##
|
||||
##export DB_PASS REDIS_PASS
|
||||
##
|
||||
##cd "$(dirname "$0")"
|
||||
##source ./lib.sh
|
||||
##require_root
|
||||
##header
|
||||
##
|
||||
### ── Defaults ────────────────────────────────────────────────────────────────
|
||||
##APP_NAME="${APP_NAME:-MailWolt}"
|
||||
##APP_USER="${APP_USER:-mailwolt}"
|
||||
##APP_GROUP="${APP_GROUP:-www-data}"
|
||||
##APP_USER_PREFIX="${APP_USER_PREFIX:-mw}"
|
||||
##APP_DIR="${APP_DIR:-/var/www/${APP_USER}}"
|
||||
##
|
||||
##BASE_DOMAIN="${BASE_DOMAIN:-example.com}"
|
||||
##UI_SUB="${UI_SUB:-ui}"
|
||||
##WEBMAIL_SUB="${WEBMAIL_SUB:-webmail}"
|
||||
##MTA_SUB="${MTA_SUB:-mx}"
|
||||
##
|
||||
##DB_NAME="${DB_NAME:-${APP_USER}}"
|
||||
##DB_USER="${DB_USER:-${APP_USER}}"
|
||||
##
|
||||
##SERVER_PUBLIC_IPV4="$(detect_ip)"
|
||||
##SERVER_PUBLIC_IPV6="$(detect_ipv6)"
|
||||
##DEFAULT_TZ="$(detect_timezone)"
|
||||
##DEFAULT_LOCALE="$(guess_locale_from_tz "$DEFAULT_TZ")"
|
||||
##
|
||||
##echo -e "${GREY}Erkannte IP (v4): ${SERVER_PUBLIC_IPV4} v6: ${SERVER_PUBLIC_IPV6:-–}${NC}"
|
||||
##
|
||||
### ── FQDNs abfragen ───────────────────────────────────────────────────────────
|
||||
##read -r -p "Mailserver FQDN (MX, z.B. mx.domain.tld) [Enter=${MTA_SUB}.${BASE_DOMAIN}]: " MTA_FQDN
|
||||
##read -r -p "UI / Admin-Panel FQDN (z.B. ui.domain.tld) [Enter=${UI_SUB}.${BASE_DOMAIN}]: " UI_FQDN
|
||||
##read -r -p "Webmail FQDN (z.B. webmail.domain.tld) [Enter=${WEBMAIL_SUB}.${BASE_DOMAIN}]: " WEBMAIL_FQDN
|
||||
##
|
||||
### Defaults, wenn Enter gedrückt
|
||||
##MTA_FQDN="${MTA_FQDN:-${MTA_SUB}.${BASE_DOMAIN}}"
|
||||
##UI_FQDN="${UI_FQDN:-${UI_SUB}.${BASE_DOMAIN}}"
|
||||
##WEBMAIL_FQDN="${WEBMAIL_FQDN:-${WEBMAIL_SUB}.${BASE_DOMAIN}}"
|
||||
##DKIM_ENABLE="${DKIM_ENABLE:-1}"
|
||||
##DKIM_SELECTOR="${DKIM_SELECTOR:-mwl1}"
|
||||
##DKIM_GENERATE="${DKIM_GENERATE:-1}"
|
||||
##
|
||||
### BASE_DOMAIN und Sub-Labels aus MTA/UI/WEBMAIL ableiten (robust)
|
||||
##if [[ "$MTA_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
## MTA_SUB="${BASH_REMATCH[1]}"
|
||||
## BASE_DOMAIN="${BASH_REMATCH[2]}"
|
||||
##fi
|
||||
##if [[ "$UI_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
## UI_SUB="${BASH_REMATCH[1]}"
|
||||
##fi
|
||||
##if [[ "$WEBMAIL_FQDN" =~ ^([^.]+)\.(.+)$ ]]; then
|
||||
## WEBMAIL_SUB="${BASH_REMATCH[1]}"
|
||||
##fi
|
||||
##
|
||||
##SYSMAIL_SUB="${SYSMAIL_SUB:-sysmail}"
|
||||
##SYSMAIL_DOMAIN="${SYSMAIL_SUB}.${BASE_DOMAIN}"
|
||||
### Kanonische Host-Variablen (NIE wieder zusammenbauen – nimm die FQDNs)
|
||||
##MAIL_HOSTNAME="${MTA_FQDN}"
|
||||
##UI_HOST="${UI_FQDN}"
|
||||
##WEBMAIL_HOST="${WEBMAIL_FQDN}"
|
||||
##
|
||||
### Zeitzone/Locale sinnvoll setzen (könntest du auch noch abfragen)
|
||||
##APP_TZ="${APP_TZ:-$DEFAULT_TZ}"
|
||||
##APP_LOCALE="${APP_LOCALE:-$DEFAULT_LOCALE}"
|
||||
##
|
||||
### ── Variablen exportieren ───────────────────────────────────────────────────
|
||||
##export APP_NAME APP_USER APP_GROUP APP_USER_PREFIX APP_DIR
|
||||
##export BASE_DOMAIN UI_SUB WEBMAIL_SUB MTA_SUB
|
||||
##export SYSMAIL_SUB SYSMAIL_DOMAIN DKIM_ENABLE DKIM_SELECTOR DKIM_GENERATE
|
||||
##export UI_HOST WEBMAIL_HOST MAIL_HOSTNAME
|
||||
##export DB_NAME DB_USER
|
||||
##export SERVER_PUBLIC_IPV4 SERVER_PUBLIC_IPV6 APP_TZ APP_LOCALE
|
||||
##
|
||||
##install -d -m 0755 /etc/mailwolt
|
||||
##cat >/etc/mailwolt/installer.env <<EOF
|
||||
##BASE_DOMAIN=${BASE_DOMAIN}
|
||||
##MTA_SUB=${MTA_SUB}
|
||||
##UI_SUB=${UI_SUB}
|
||||
##WEBMAIL_SUB=${WEBMAIL_SUB}
|
||||
##
|
||||
##MAIL_HOSTNAME=${MAIL_HOSTNAME}
|
||||
##UI_HOST=${UI_HOST}
|
||||
##WEBMAIL_HOST=${WEBMAIL_HOST}
|
||||
##
|
||||
##SYSMAIL_SUB=${SYSMAIL_SUB}
|
||||
##SYSMAIL_DOMAIN=${SYSMAIL_DOMAIN}
|
||||
##
|
||||
##DKIM_ENABLE=${DKIM_ENABLE}
|
||||
##DKIM_SELECTOR=${DKIM_SELECTOR}
|
||||
##DKIM_GENERATE=${DKIM_GENERATE}
|
||||
##
|
||||
##DB_HOST=127.0.0.1
|
||||
##DB_NAME=${DB_NAME}
|
||||
##DB_USER=${DB_USER}
|
||||
##DB_PASS=${DB_PASS}
|
||||
##REDIS_PASS=${REDIS_PASS}
|
||||
##
|
||||
##SERVER_PUBLIC_IPV4=${SERVER_PUBLIC_IPV4}
|
||||
##SERVER_PUBLIC_IPV6=${SERVER_PUBLIC_IPV6}
|
||||
##APP_ENV=${APP_ENV}
|
||||
##
|
||||
##CLAMAV_ENABLE=1
|
||||
##OPENDMARC_ENABLE=1
|
||||
##FAIL2BAN_ENABLE=1
|
||||
##EOF
|
||||
##
|
||||
##chmod 600 /etc/mailwolt/installer.env
|
||||
##
|
||||
### ── Sequenz ────────────────────────────────────────────────────────────────
|
||||
##for STEP in 10-provision 20-ssl 21-le-deploy-hook 22-dkim-helper 30-db 40-postfix 50-dovecot 60-rspamd-opendkim 61-opendmarc 62-clamav 63-fail2ban 70-nginx 75-le-issue 80-app 90-services 95-woltguard 98-motd 99-summary
|
||||
##do
|
||||
## log ">>> Running ${STEP}.sh"
|
||||
## bash "./${STEP}.sh"
|
||||
##done
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env bash
|
||||
# Mailwolt Installer-Wrapper
|
||||
# Deploy to: /usr/local/sbin/mailwolt-install
|
||||
# Permissions: chmod 0755, chown root:root
|
||||
set -euo pipefail
|
||||
|
||||
LOG="/var/log/mailwolt-install.log"
|
||||
STATE_DIR="/var/lib/mailwolt/install"
|
||||
INSTALLER_SCRIPT="/var/www/mailwolt/mailwolt-installer/install.sh"
|
||||
APP_DIR="/var/www/mailwolt"
|
||||
|
||||
install -d -m 0755 "$STATE_DIR" /var/lib/mailwolt /var/lib/mailwolt/wizard
|
||||
chown www-data:www-data /var/lib/mailwolt/wizard
|
||||
: > "$LOG"
|
||||
chmod 0644 "$LOG"
|
||||
|
||||
echo "running" > "$STATE_DIR/state"
|
||||
: > "$STATE_DIR/rc"
|
||||
|
||||
{
|
||||
echo "===== $(date -Is) :: Installation gestartet ====="
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "[!] Muss als root laufen"
|
||||
printf '1\n' > "$STATE_DIR/rc"
|
||||
echo "done" > "$STATE_DIR/state"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Komponente aus $1, falls übergeben (z.B. "nginx", "postfix", "dovecot", "all")
|
||||
COMPONENT="${1:-all}"
|
||||
echo "[i] Komponente: $COMPONENT"
|
||||
|
||||
RC=0
|
||||
if [[ -f "$INSTALLER_SCRIPT" ]]; then
|
||||
APP_DIR="$APP_DIR" COMPONENT="$COMPONENT" bash "$INSTALLER_SCRIPT" || RC=$?
|
||||
else
|
||||
echo "[!] installer script nicht gefunden: $INSTALLER_SCRIPT"
|
||||
RC=127
|
||||
fi
|
||||
|
||||
echo "===== $(date -Is) :: Installation beendet (rc=$RC) ====="
|
||||
printf '%s\n' "$RC" > "$STATE_DIR/rc"
|
||||
echo "done" > "$STATE_DIR/state"
|
||||
exit "$RC"
|
||||
} 2>&1 | tee -a "$LOG"
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ -f /etc/mailwolt/installer.env ]; then
|
||||
set -a
|
||||
. /etc/mailwolt/installer.env
|
||||
set +a
|
||||
fi
|
||||
# ── 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="──────────────────────────────────────────────────────────────────────────────"
|
||||
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."; }
|
||||
|
||||
# --- Defaults, nur wenn noch nicht gesetzt ---------------------------------
|
||||
: "${APP_USER:=mailwolt}"
|
||||
: "${APP_GROUP:=www-data}"
|
||||
: "${APP_DIR:=/var/www/${APP_USER}}"
|
||||
|
||||
: "${APP_NAME:=MailWolt}"
|
||||
|
||||
: "${BASE_DOMAIN:=example.com}"
|
||||
: "${UI_SUB:=ui}"
|
||||
: "${WEBMAIL_SUB:=webmail}"
|
||||
: "${MTA_SUB:=mx}"
|
||||
|
||||
# DB / Redis (werden später durch .env überschrieben)
|
||||
: "${DB_NAME:=${APP_USER}}"
|
||||
: "${DB_USER:=${APP_USER}}"
|
||||
: "${DB_PASS:=}"
|
||||
: "${REDIS_PASS:=}"
|
||||
|
||||
# Stabile Zert-Pfade (UI/WEBMAIL/MX → symlinked via 20-ssl.sh)
|
||||
: "${MAIL_SSL_DIR:=/etc/ssl/mail}"
|
||||
: "${UI_SSL_DIR:=/etc/ssl/ui}"
|
||||
: "${WEBMAIL_SSL_DIR:=/etc/ssl/webmail}"
|
||||
: "${UI_CERT:=${UI_SSL_DIR}/fullchain.pem}"
|
||||
: "${UI_KEY:=${UI_SSL_DIR}/privkey.pem}"
|
||||
|
||||
# Optional: E-Mail für LE
|
||||
: "${LE_EMAIL:=admin@${BASE_DOMAIN}}"
|
||||
|
||||
load_env_file(){
|
||||
local f="$1"
|
||||
[[ -f "$f" ]] || return 0
|
||||
while IFS='=' read -r k v; do
|
||||
[[ "$k" =~ ^[A-Z0-9_]+$ ]] || continue
|
||||
export "$k=$v"
|
||||
done < <(grep -E '^[A-Z0-9_]+=' "$f")
|
||||
}
|
||||
|
||||
header(){ echo -e "${CYAN}${BAR}${NC}
|
||||
${CYAN} :::: :::: ::: ::::::::::: ::: ::: ::: :::::::: ::: :::::::::::
|
||||
${CYAN} +:+:+: :+:+:+ :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||||
${CYAN} +:+ +:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||||
${CYAN} +#+ +:+ +#+ +#++:++#++: +#+ +#+ +#+ +:+ +#+ +#+ +:+ +#+ +#+
|
||||
${CYAN} +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ +#+
|
||||
${CYAN} #+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+# #+# #+#
|
||||
${CYAN} ### ### ### ### ########### ########## ### ### ######## ########## ###
|
||||
${CYAN} ${CYAN}${BAR}${NC}\n"; }
|
||||
|
||||
#header(){ echo -e "${CYAN}${BAR}${NC}
|
||||
#${CYAN} 888b d888 d8b 888 888 888 888 888
|
||||
#${CYAN} 8888b d8888 Y8P 888 888 o 888 888 888
|
||||
#${CYAN} 88888b.d88888 888 888 d8b 888 888 888
|
||||
#${CYAN} 888Y88888P888 8888b. 888 888 888 d888b 888 .d88b. 888 888888
|
||||
#${CYAN} 888 Y888P 888 '88b 888 888 888d88888b888 d88''88b 888 888
|
||||
#${CYAN} 888 Y8P 888 .d888888 888 888 88888P Y88888 888 888 888 888
|
||||
#${CYAN} 888 ' 888 888 888 888 888 8888P Y8888 Y88..88P 888 Y88b.
|
||||
#${CYAN} 888 888 'Y888888 888 888 888P Y888 'Y88P' 888 'Y888
|
||||
#${CYAN}${BAR}${NC}\n"; }
|
||||
|
||||
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_ipv4() {
|
||||
local ext=""
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
ext="$(curl -fsS --max-time 2 https://ifconfig.me 2>/dev/null || true)"
|
||||
[[ "$ext" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || ext=""
|
||||
fi
|
||||
echo "$ext"
|
||||
}
|
||||
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(){
|
||||
local tz
|
||||
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
|
||||
[[ -r /etc/timezone ]] && { tz="$(sed -n '1p' /etc/timezone | tr -d '[:space:]')" || true; [[ "$tz" == */* ]] && { echo "$tz"; return; }; }
|
||||
if [[ -L /etc/localtime ]]; then
|
||||
tz="$(readlink -f /etc/localtime 2>/dev/null || true)"; tz="${tz#/usr/share/zoneinfo/}"
|
||||
[[ "$tz" == */* ]] && { echo "$tz"; 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)"; [[ "$tz" == */* ]] && { echo "$tz"; return; }
|
||||
fi
|
||||
echo "UTC"
|
||||
}
|
||||
guess_locale_from_tz(){ case "${1:-UTC}" in
|
||||
Europe/Berlin|Europe/Vienna|Europe/Zurich|Europe/Luxembourg|Europe/Brussels|Europe/Amsterdam) echo "de";;
|
||||
*) echo "en";; esac; }
|
||||
|
||||
resolve_ok(){ local host="$1"; getent ahosts "$host" | awk '{print $1}' | sort -u | grep -q -F "${SERVER_PUBLIC_IPV4:-}" ; }
|
||||
join_host(){ local sub="$1" base="$2"; [[ -z "$sub" ]] && echo "$base" || echo "$sub.$base"; }
|
||||
|
||||
# dns_preflight HOST [HOST2 ...]
|
||||
# Prüft: A-Record → SERVER_PUBLIC_IPV4, MX (nur wenn HOST == MAIL_HOSTNAME), PTR.
|
||||
# Gibt strukturierte Zeilen aus: OK|WARN|FAIL <host> <check> <detail>
|
||||
# Rückgabe 0 = alles OK; 1 = mind. ein FAIL.
|
||||
dns_preflight(){
|
||||
local overall=0
|
||||
local server_ip="${SERVER_PUBLIC_IPV4:-}"
|
||||
|
||||
_dns_line(){ local level="$1" host="$2" check="$3" detail="$4"
|
||||
case "$level" in
|
||||
OK) echo -e "${GREEN}[DNS OK ]${NC} ${host} ${GREY}${check}${NC} → ${detail}" ;;
|
||||
WARN) echo -e "${YELLOW}[DNS WARN]${NC} ${host} ${GREY}${check}${NC} → ${detail}" ;;
|
||||
FAIL) echo -e "${RED}[DNS FAIL]${NC} ${host} ${GREY}${check}${NC} → ${detail}"; overall=1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
for host in "$@"; do
|
||||
[[ -z "$host" || "$host" == "example.com" ]] && continue
|
||||
|
||||
# A-Record
|
||||
local a_ip
|
||||
a_ip="$(dig +short A "$host" @1.1.1.1 2>/dev/null | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1)"
|
||||
if [[ -z "$a_ip" ]]; then
|
||||
_dns_line FAIL "$host" "A-Record" "kein Eintrag gefunden"
|
||||
elif [[ -n "$server_ip" && "$a_ip" != "$server_ip" ]]; then
|
||||
_dns_line FAIL "$host" "A-Record" "${a_ip} ≠ ${server_ip} (Server-IP)"
|
||||
else
|
||||
_dns_line OK "$host" "A-Record" "${a_ip}"
|
||||
fi
|
||||
|
||||
# MX (nur für MAIL_HOSTNAME)
|
||||
if [[ "$host" == "${MAIL_HOSTNAME:-}" ]]; then
|
||||
local mx
|
||||
mx="$(dig +short MX "$host" @1.1.1.1 2>/dev/null | awk '{print $2}' | head -n1)"
|
||||
if [[ -z "$mx" ]]; then
|
||||
_dns_line WARN "$host" "MX-Record" "kein Eintrag – ausgehende Mail ggf. eingeschränkt"
|
||||
else
|
||||
_dns_line OK "$host" "MX-Record" "${mx}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# PTR (nur wenn IP bekannt)
|
||||
if [[ -n "$server_ip" && -n "$a_ip" && "$a_ip" == "$server_ip" ]]; then
|
||||
local ptr
|
||||
ptr="$(dig +short -x "$server_ip" @1.1.1.1 2>/dev/null | head -n1 | sed 's/\.$//')"
|
||||
if [[ -z "$ptr" ]]; then
|
||||
_dns_line WARN "$host" "PTR-Record" "kein Reverse-DNS – kann Spam-Score erhöhen"
|
||||
elif [[ "$ptr" != "$host" && "$ptr" != "${MAIL_HOSTNAME:-}" ]]; then
|
||||
_dns_line WARN "$host" "PTR-Record" "${ptr} (zeigt nicht auf ${host})"
|
||||
else
|
||||
_dns_line OK "$host" "PTR-Record" "${ptr}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
return $overall
|
||||
}
|
||||
|
||||
upsert_env(){ # upsert in $ENV_FILE
|
||||
local k="$1" v="$2" 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" 2>/dev/null; then
|
||||
sed -Ei "s|^[#[:space:]]*${ek}=.*|${k}=${ev}|g" "$ENV_FILE"
|
||||
else
|
||||
printf '%s=%s\n' "$k" "$v" >> "$ENV_FILE"
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
# Phase 2: Go-Live — DNS-Check → LE-Zertifikate → Nginx → Postfix/Dovecot aktualisieren
|
||||
# Muss als root ausgeführt werden, NACHDEM die DNS-Einträge gesetzt wurden.
|
||||
# Liest Konfiguration aus /etc/mailwolt/installer.env (durch Phase 1 geschrieben).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib.sh"
|
||||
|
||||
require_root
|
||||
|
||||
ENV_FILE="/etc/mailwolt/installer.env"
|
||||
[[ -f "$ENV_FILE" ]] || die "Phase 1 noch nicht abgeschlossen – ${ENV_FILE} fehlt."
|
||||
|
||||
set -a; . "$ENV_FILE"; set +a
|
||||
|
||||
# ── Pflichtfelder ─────────────────────────────────────────────────────────────
|
||||
: "${BASE_DOMAIN:?BASE_DOMAIN fehlt in ${ENV_FILE}}"
|
||||
: "${UI_HOST:?UI_HOST fehlt in ${ENV_FILE}}"
|
||||
: "${WEBMAIL_HOST:?WEBMAIL_HOST fehlt in ${ENV_FILE}}"
|
||||
: "${MAIL_HOSTNAME:?MAIL_HOSTNAME fehlt in ${ENV_FILE}}"
|
||||
: "${SERVER_PUBLIC_IPV4:?SERVER_PUBLIC_IPV4 fehlt in ${ENV_FILE}}"
|
||||
|
||||
header
|
||||
echo -e "${CYAN} Phase 2 – Go-Live${NC}"
|
||||
echo -e "${CYAN}${BAR}${NC}"
|
||||
echo
|
||||
|
||||
# ── Schritt 1: DNS Preflight ──────────────────────────────────────────────────
|
||||
log "DNS-Vorab-Check …"
|
||||
|
||||
SKIP_DNS="${SKIP_DNS:-0}"
|
||||
dns_ok=0
|
||||
|
||||
if [[ "$SKIP_DNS" != "1" ]]; then
|
||||
if dns_preflight "$UI_HOST" "$WEBMAIL_HOST" "$MAIL_HOSTNAME"; then
|
||||
dns_ok=1
|
||||
else
|
||||
warn "Einige DNS-Einträge zeigen noch nicht auf diesen Server."
|
||||
echo
|
||||
echo -e " Möglichkeiten:"
|
||||
echo -e " a) DNS reparieren und dieses Skript erneut ausführen."
|
||||
echo -e " b) Trotzdem fortfahren: SKIP_DNS=1 bash phase2-go-live.sh"
|
||||
echo
|
||||
read -rp "Trotzdem fortfahren? [j/N] " _ans
|
||||
[[ "${_ans,,}" == "j" ]] || die "Abgebrochen."
|
||||
dns_ok=1
|
||||
fi
|
||||
else
|
||||
warn "DNS-Check übersprungen (SKIP_DNS=1)."
|
||||
dns_ok=1
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
# ── Schritt 2: Let's Encrypt Zertifikate ─────────────────────────────────────
|
||||
log "Let's Encrypt Zertifikate ausstellen …"
|
||||
bash "${SCRIPT_DIR}/75-le-issue.sh"
|
||||
echo
|
||||
|
||||
# ── Schritt 3: Nginx-Konfiguration neu schreiben (TLS) ───────────────────────
|
||||
log "Nginx-Konfiguration aktualisieren (TLS) …"
|
||||
# Nginx-Builder aus 70-nginx.sh wiederverwenden
|
||||
source "${SCRIPT_DIR}/70-nginx.sh" || true # sourcing setzt Variablen und führt aus
|
||||
echo
|
||||
|
||||
# ── Schritt 4: Postfix hostname + TLS-Zertifikate aktualisieren ──────────────
|
||||
log "Postfix: myhostname = ${MAIL_HOSTNAME} …"
|
||||
postconf -e "myhostname = ${MAIL_HOSTNAME}"
|
||||
postconf -e "myorigin = \$myhostname"
|
||||
postconf -e "smtpd_tls_cert_file = /etc/ssl/mail/fullchain.pem"
|
||||
postconf -e "smtpd_tls_key_file = /etc/ssl/mail/privkey.pem"
|
||||
systemctl reload postfix || true
|
||||
|
||||
# ── Schritt 5: Dovecot TLS ───────────────────────────────────────────────────
|
||||
log "Dovecot: TLS-Zertifikate aktualisieren …"
|
||||
if [[ -f /etc/dovecot/conf.d/10-ssl.conf ]]; then
|
||||
sed -i "s|^ssl_cert =.*|ssl_cert = </etc/ssl/mail/fullchain.pem|" /etc/dovecot/conf.d/10-ssl.conf || true
|
||||
sed -i "s|^ssl_key =.*|ssl_key = </etc/ssl/mail/privkey.pem|" /etc/dovecot/conf.d/10-ssl.conf || true
|
||||
systemctl reload dovecot || true
|
||||
fi
|
||||
|
||||
# ── Schritt 6: App-URL in .env aktualisieren ──────────────────────────────────
|
||||
APP_ENV_FILE="${APP_DIR}/.env"
|
||||
if [[ -f "$APP_ENV_FILE" ]]; then
|
||||
log "Laravel APP_URL aktualisieren → https://${UI_HOST} …"
|
||||
ENV_FILE="$APP_ENV_FILE"
|
||||
upsert_env APP_URL "https://${UI_HOST}"
|
||||
upsert_env APP_HOST "${UI_HOST}"
|
||||
upsert_env SESSION_SECURE_COOKIE "true"
|
||||
sudo -u "${APP_USER}" -H bash -lc "cd ${APP_DIR} && php artisan optimize:clear && php artisan config:cache" || true
|
||||
fi
|
||||
|
||||
# ── Fertig ────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo -e "${GREEN} ✔ Phase 2 abgeschlossen!${NC}"
|
||||
echo -e "${GREEN}${BAR}${NC}"
|
||||
echo -e " UI: ${CYAN}https://${UI_HOST}${NC}"
|
||||
echo -e " Webmail: ${CYAN}https://${WEBMAIL_HOST}${NC}"
|
||||
echo -e " MX: ${GREY}${MAIL_HOSTNAME}${NC}"
|
||||
echo
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# -------- Konfiguration -------------------------------------------------------
|
||||
APP_USER="${APP_USER:-mailwolt}"
|
||||
APP_GROUP="${APP_GROUP:-www-data}" # Fallback; setz das in deiner Umgebung falls anders
|
||||
APP_DIR="${APP_DIR:-/var/www/mailwolt}"
|
||||
BRANCH="${BRANCH:-main}" # nur relevant bei UPDATE_MODE=branch
|
||||
MODE="${UPDATE_MODE:-tags}" # tags | branch
|
||||
ALLOW_DIRTY="${ALLOW_DIRTY:-0}" # 1 = Dirty-Working-Tree zulassen
|
||||
|
||||
# npm / CI Defaults für weniger Lärm
|
||||
export CI=1
|
||||
export NPM_CONFIG_FUND=false
|
||||
export NPM_CONFIG_AUDIT=false
|
||||
export npm_config_loglevel=warn
|
||||
|
||||
# -------- Helper --------------------------------------------------------------
|
||||
as_app(){ sudo -u "$APP_USER" -H bash -lc "$*"; }
|
||||
|
||||
restart_if_exists(){
|
||||
local u="$1"
|
||||
systemctl list-unit-files | grep -q "^${u}\.service" && systemctl restart "$u" || true
|
||||
}
|
||||
|
||||
reload_if_active(){
|
||||
local u="$1"
|
||||
systemctl is-active --quiet "$u" && systemctl reload "$u" || true
|
||||
}
|
||||
|
||||
restart_php_fpm(){
|
||||
for u in php8.3-fpm php8.2-fpm php8.1-fpm php-fpm; do
|
||||
if systemctl list-unit-files | grep -q "^${u}\.service"; then
|
||||
# reload = graceful: laufende Requests fertig, dann Workers tauschen
|
||||
systemctl reload "$u" 2>/dev/null || systemctl restart "$u"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
artisan_down(){
|
||||
as_app "cd ${APP_DIR} && php artisan down --retry=10 --render=errors.503" 2>/dev/null || true
|
||||
echo "[i] Wartungsmodus aktiviert"
|
||||
}
|
||||
|
||||
artisan_up(){
|
||||
as_app "cd ${APP_DIR} && php artisan up" 2>/dev/null || true
|
||||
echo "[i] Wartungsmodus beendet"
|
||||
}
|
||||
|
||||
git_safe(){
|
||||
# Falls nötig: Repo als safe markieren (z.B. wenn Root den Wrapper aufruft)
|
||||
as_app "git -C ${APP_DIR} config --global --add safe.directory ${APP_DIR} >/dev/null 2>&1 || true"
|
||||
}
|
||||
|
||||
git_dirty_check(){
|
||||
if [[ "$ALLOW_DIRTY" != "1" ]]; then
|
||||
local dirty
|
||||
dirty="$(as_app "git -C ${APP_DIR} status --porcelain")"
|
||||
if [[ -n "$dirty" ]]; then
|
||||
echo "[!] Arbeitsbaum hat uncommitted Änderungen. Abbruch (ALLOW_DIRTY=1 zum Überschreiben)."
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_version(){
|
||||
as_app "cd ${APP_DIR} && (git describe --tags --always 2>/dev/null || git rev-parse --short=7 HEAD)"
|
||||
}
|
||||
|
||||
write_build_info(){
|
||||
local ver="$1" rev="$2"
|
||||
install -d -m 0755 /etc/mailwolt || true
|
||||
printf "version=%s\nrev=%s\nupdated=%s\n" "$ver" "$rev" "$(date -Is)" > /etc/mailwolt/build.info || true
|
||||
}
|
||||
|
||||
# --- Frontend build (quiet & robust) ------------------------------------------
|
||||
frontend_build_quiet() {
|
||||
local LOG="/var/log/mailwolt-frontend-build.log"
|
||||
local NPM_ENV="CI=1 NPM_CONFIG_FUND=false NPM_CONFIG_AUDIT=false npm_config_loglevel=warn"
|
||||
|
||||
# Logfile vorbereiten
|
||||
install -d -m 0755 "$(dirname "$LOG")"
|
||||
: > "$LOG" || true
|
||||
chmod 0644 "$LOG"
|
||||
|
||||
echo "[i] Frontend: vorbereiten …"
|
||||
# Build-Ziele & Temp besitzbar machen
|
||||
as_app "mkdir -p '${APP_DIR}/public/build' '${APP_DIR}/node_modules' '${APP_DIR}/.vite' '${APP_DIR}/.npm-cache'"
|
||||
chown -R "$APP_USER":"$APP_GROUP" "${APP_DIR}/public/build" "${APP_DIR}/node_modules" "${APP_DIR}/.vite" "${APP_DIR}/.npm-cache" || true
|
||||
chmod -R g+rwX "${APP_DIR}/public/build" "${APP_DIR}/node_modules" "${APP_DIR}/.vite" || true
|
||||
|
||||
# evtl. störrische Reste entfernen (vermeidet EACCES beim Unlink/Rimraf)
|
||||
rm -rf "${APP_DIR}/node_modules/.vite" "${APP_DIR}/public/build/"* 2>/dev/null || true
|
||||
|
||||
# npm Config (kein Fund/Audit, eigener Cache unter App-User)
|
||||
as_app "printf 'fund=false\naudit=false\ncache=${APP_DIR}/.npm-cache\n' > ~/.npmrc" >>"$LOG" 2>&1 || true
|
||||
|
||||
echo "[i] Frontend: Dependencies … (Details: $LOG)"
|
||||
if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm ci --no-audit --no-fund --no-progress" >>"$LOG" 2>&1; then
|
||||
if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm install --no-audit --no-fund --no-progress" >>"$LOG" 2>&1; then
|
||||
echo "[!] npm install fehlgeschlagen. Letzte 60 Zeilen:"
|
||||
tail -n 60 "$LOG" || true
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[i] Frontend: Build … (Details: $LOG)"
|
||||
if ! as_app "cd '${APP_DIR}' && ${NPM_ENV} npm run build --silent --loglevel=warn" >>"$LOG" 2>&1; then
|
||||
echo "[!] Build fehlgeschlagen. Letzte 80 Zeilen:"
|
||||
tail -n 80 "$LOG" || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Nach dem Build noch einmal Rechte begradigen
|
||||
chown -R "$APP_USER":"$APP_GROUP" "${APP_DIR}/public/build" || true
|
||||
find "${APP_DIR}/public/build" -type d -exec chmod 2775 {} \; 2>/dev/null || true
|
||||
find "${APP_DIR}/public/build" -type f -exec chmod 0664 {} \; 2>/dev/null || true
|
||||
|
||||
echo "[✓] Frontend gebaut."
|
||||
}
|
||||
|
||||
# -------- Guards --------------------------------------------------------------
|
||||
[[ "$(id -u)" -eq 0 ]] || { echo "[!] Bitte als root ausführen"; exit 1; }
|
||||
[[ -d "$APP_DIR/.git" ]] || { echo "[!] $APP_DIR scheint kein Git-Repo zu sein"; exit 1; }
|
||||
|
||||
git_safe
|
||||
git_dirty_check
|
||||
|
||||
# -------- Git: neuen Stand holen ---------------------------------------------
|
||||
echo "[i] Prüfe Repository …"
|
||||
OLD_REV="$(as_app "git -C ${APP_DIR} rev-parse HEAD")"
|
||||
OLD_VER="$(get_version)"
|
||||
NEW_REV="$OLD_REV"
|
||||
|
||||
if [[ "$MODE" = "tags" ]]; then
|
||||
# → Neueste Tags holen
|
||||
as_app "git -C ${APP_DIR} fetch --quiet origin && git -C ${APP_DIR} fetch --tags --quiet origin || true"
|
||||
LATEST_TAG="$(as_app "git -C ${APP_DIR} tag --list | sort -V | tail -n1")"
|
||||
if [[ -z "$LATEST_TAG" ]]; then
|
||||
echo "[!] Keine Tags gefunden – falle auf origin/${BRANCH} zurück"
|
||||
as_app "git -C ${APP_DIR} checkout -q ${BRANCH} && git -C ${APP_DIR} pull --ff-only origin ${BRANCH}"
|
||||
else
|
||||
TARGET_REV="$(as_app "git -C ${APP_DIR} rev-list -n1 ${LATEST_TAG}")"
|
||||
if [[ "$TARGET_REV" = "$OLD_REV" ]]; then
|
||||
echo "[✓] Bereits auf neuestem Release (${LATEST_TAG}) – nichts zu tun."
|
||||
write_build_info "$(get_version)" "$OLD_REV"
|
||||
exit 0
|
||||
fi
|
||||
echo "[i] Checkout auf Release ${LATEST_TAG} (${TARGET_REV:0:7}) …"
|
||||
as_app "git -C ${APP_DIR} checkout -q ${LATEST_TAG}"
|
||||
fi
|
||||
NEW_REV="$(as_app "git -C ${APP_DIR} rev-parse HEAD")"
|
||||
else
|
||||
# Rolling: branch folgen
|
||||
as_app "git -C ${APP_DIR} fetch --quiet origin ${BRANCH}"
|
||||
BEHIND="$(as_app "git -C ${APP_DIR} rev-list --count HEAD..origin/${BRANCH} || echo 0")"
|
||||
if [[ "$BEHIND" -eq 0 ]]; then
|
||||
echo "[✓] Branch origin/${BRANCH} ist bereits aktuell – nichts zu tun."
|
||||
write_build_info "$(get_version)" "$OLD_REV"
|
||||
exit 0
|
||||
fi
|
||||
echo "[i] Es gibt ${BEHIND} neue Commit(s) – ziehe Änderungen …"
|
||||
as_app "git -C ${APP_DIR} checkout -q ${BRANCH} && git -C ${APP_DIR} pull --ff-only origin ${BRANCH}"
|
||||
NEW_REV="$(as_app "git -C ${APP_DIR} rev-parse HEAD")"
|
||||
fi
|
||||
|
||||
# -------- Änderungstypen ermitteln -------------------------------------------
|
||||
CHANGED_FILES="$(as_app "git -C ${APP_DIR} diff --name-only ${OLD_REV}..${NEW_REV}")"
|
||||
|
||||
NEED_COMPOSER=0
|
||||
NEED_MIGRATIONS=0
|
||||
NEED_FRONTEND=0
|
||||
NEED_PHP_RESTART=0
|
||||
|
||||
echo "$CHANGED_FILES" | grep -qE '(^|/)composer\.(json|lock)$' && NEED_COMPOSER=1
|
||||
echo "$CHANGED_FILES" | grep -qE '^database/migrations/' && NEED_MIGRATIONS=1
|
||||
echo "$CHANGED_FILES" | grep -qE '^(package(-lock)?\.json|vite\.config(\.ts|\.js)?|resources/|public/.*\.(js|css))' && NEED_FRONTEND=1
|
||||
echo "$CHANGED_FILES" | grep -qE '^(app/|routes/|config/|resources/views/)' && NEED_PHP_RESTART=1
|
||||
|
||||
echo "[i] Zusammenfassung:"
|
||||
echo " Composer : $([[ $NEED_COMPOSER -eq 1 ]] && echo JA || echo nein)"
|
||||
echo " Migrations : $([[ $NEED_MIGRATIONS -eq 1 ]] && echo JA || echo nein)"
|
||||
echo " Frontend : $([[ $NEED_FRONTEND -eq 1 ]] && echo JA || echo nein)"
|
||||
echo " PHP restart : $([[ $NEED_PHP_RESTART -eq 1 ]] && echo JA || echo nein)"
|
||||
|
||||
# Wenn gar nichts relevantes geändert wurde → sauber beenden
|
||||
if [[ $NEED_COMPOSER -eq 0 && $NEED_MIGRATIONS -eq 0 && $NEED_FRONTEND -eq 0 && $NEED_PHP_RESTART -eq 0 ]]; then
|
||||
echo "[✓] Code-Stand aktualisiert, aber keine Build/Runtime-Änderungen – keine Neustarts nötig."
|
||||
NEW_VER="$(get_version)"
|
||||
write_build_info "$NEW_VER" "$NEW_REV"
|
||||
echo "[i] Version: ${OLD_VER} → ${NEW_VER}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -------- Composer (ohne Downtime) -------------------------------------------
|
||||
if [[ $NEED_COMPOSER -eq 1 ]]; then
|
||||
echo "[i] Composer …"
|
||||
as_app "cd ${APP_DIR} && composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev"
|
||||
fi
|
||||
|
||||
# -------- Frontend (ohne Downtime — Assets sind statisch) --------------------
|
||||
if [[ $NEED_FRONTEND -eq 1 ]]; then
|
||||
echo "[i] Frontend-Änderungen erkannt – baue Assets …"
|
||||
if ! frontend_build_quiet; then
|
||||
echo "[!] Frontend-Build ist fehlgeschlagen (siehe /var/log/mailwolt-frontend-build.log)."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# -------- Wartungsmodus: Migrations + Cache + PHP-FPM reload -----------------
|
||||
# Trap stellt sicher dass artisan up auch bei Fehler läuft
|
||||
MAINTENANCE_ACTIVE=0
|
||||
cleanup_maintenance(){
|
||||
if [[ "$MAINTENANCE_ACTIVE" -eq 1 ]]; then
|
||||
artisan_up
|
||||
fi
|
||||
}
|
||||
trap cleanup_maintenance EXIT INT TERM
|
||||
|
||||
if [[ $NEED_MIGRATIONS -eq 1 || $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 ]]; then
|
||||
artisan_down
|
||||
MAINTENANCE_ACTIVE=1
|
||||
fi
|
||||
|
||||
if [[ $NEED_MIGRATIONS -eq 1 ]]; then
|
||||
echo "[i] DB-Migrationen …"
|
||||
as_app "cd ${APP_DIR} && php artisan migrate --force"
|
||||
fi
|
||||
|
||||
if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
|
||||
echo "[i] Cache/Optimierungen …"
|
||||
as_app "cd ${APP_DIR} && php artisan optimize:clear || true"
|
||||
as_app "cd ${APP_DIR} && php artisan config:cache || true"
|
||||
as_app "cd ${APP_DIR} && php artisan route:cache || true"
|
||||
as_app "cd ${APP_DIR} && php artisan queue:restart || true"
|
||||
fi
|
||||
|
||||
# -------- Dienste neu laden (gezielt) ----------------------------------------
|
||||
echo "[i] Dienste neu laden …"
|
||||
if [[ $NEED_PHP_RESTART -eq 1 || $NEED_COMPOSER -eq 1 || $NEED_MIGRATIONS -eq 1 ]]; then
|
||||
restart_php_fpm
|
||||
restart_if_exists "${APP_USER}-queue"
|
||||
restart_if_exists "${APP_USER}-schedule"
|
||||
restart_if_exists "${APP_USER}-ws"
|
||||
fi
|
||||
if [[ $NEED_FRONTEND -eq 1 || $NEED_PHP_RESTART -eq 1 ]]; then
|
||||
reload_if_active nginx
|
||||
fi
|
||||
|
||||
# -------- Wartungsmodus beenden ----------------------------------------------
|
||||
if [[ "$MAINTENANCE_ACTIVE" -eq 1 ]]; then
|
||||
artisan_up
|
||||
MAINTENANCE_ACTIVE=0
|
||||
fi
|
||||
|
||||
# -------- Build-Info ablegen --------------------------------------------------
|
||||
NEW_VER="$(get_version)"
|
||||
write_build_info "$NEW_VER" "$NEW_REV"
|
||||
|
||||
echo "[✓] Update abgeschlossen: ${OLD_REV:0:7} → ${NEW_REV:0:7} (Version: ${NEW_VER})"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MSG=""
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)"
|
||||
REMOTE="${REMOTE:-origin}"
|
||||
|
||||
usage(){ echo "Usage: $0 -m \"commit message\" [-b branch]"; exit 1; }
|
||||
|
||||
while getopts ":m:b:" opt; do
|
||||
case "$opt" in
|
||||
m) MSG="$OPTARG" ;;
|
||||
b) BRANCH="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
[[ -n "$MSG" ]] || usage
|
||||
git rev-parse --git-dir >/dev/null 2>&1 || { echo "[x] kein Git-Repo"; exit 1; }
|
||||
|
||||
echo "[i] Add/Commit → $BRANCH"
|
||||
git add -A
|
||||
# Kein Commit, wenn nichts geändert ist
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "$MSG"
|
||||
else
|
||||
echo "[i] Nichts zu committen (Index leer) – pushe nur."
|
||||
fi
|
||||
|
||||
echo "[i] Push → $REMOTE/$BRANCH"
|
||||
git push "$REMOTE" "$BRANCH"
|
||||
echo "[✓] Done."
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE="${REMOTE:-origin}"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
ALLOW_DIRTY=0
|
||||
VERSION=""
|
||||
MSG=""
|
||||
|
||||
usage(){
|
||||
echo "Usage: $0 <X.Y.Z> [-m \"release notes\"] [-b branch] [--allow-dirty]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Args
|
||||
[[ $# -ge 1 ]] || usage
|
||||
VERSION="$1"; shift || true
|
||||
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "[x] Ungültige Version: $VERSION (erwarte SemVer X.Y.Z)"; exit 1; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-m) MSG="$2"; shift 2 ;;
|
||||
-b) BRANCH="$2"; shift 2 ;;
|
||||
--allow-dirty) ALLOW_DIRTY=1; shift ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
git rev-parse --git-dir >/dev/null 2>&1 || { echo "[x] kein Git-Repo"; exit 1; }
|
||||
|
||||
# Clean tree?
|
||||
if [[ $ALLOW_DIRTY -eq 0 ]]; then
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
echo "[x] Arbeitsbaum nicht sauber. Committe oder nutze --allow-dirty."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Aktuellen Stand holen
|
||||
git fetch --quiet "$REMOTE" --tags
|
||||
# Branch check-out/sync
|
||||
git checkout -q "$BRANCH"
|
||||
git pull --ff-only "$REMOTE" "$BRANCH"
|
||||
|
||||
TAG="v${VERSION}"
|
||||
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
echo "[x] Tag ${TAG} existiert bereits."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
|
||||
if [[ -n "$LAST_TAG" ]]; then
|
||||
CHANGELOG="$(git log --pretty=format:'- %s (%h)' "${LAST_TAG}..HEAD" || true)"
|
||||
else
|
||||
CHANGELOG="$(git log --pretty=format:'- %s (%h)' || true)"
|
||||
fi
|
||||
[[ -n "$CHANGELOG" ]] || CHANGELOG="- initial release content"
|
||||
|
||||
# VERSION bumpen
|
||||
echo -n "$VERSION" > VERSION
|
||||
git add VERSION
|
||||
git commit -m "chore(release): v${VERSION}"
|
||||
|
||||
# Tag-Message bauen
|
||||
if [[ -z "$MSG" ]]; then
|
||||
read -r -d '' MSG <<EOF || true
|
||||
MailWolt v${VERSION}
|
||||
|
||||
Changes since ${LAST_TAG:-repo start}:
|
||||
${CHANGELOG}
|
||||
EOF
|
||||
fi
|
||||
|
||||
git tag -a "$TAG" -m "$MSG"
|
||||
|
||||
# Push commit + tag
|
||||
git push "$REMOTE" "$BRANCH"
|
||||
git push "$REMOTE" "$TAG"
|
||||
|
||||
echo "[✓] Release ${TAG} erstellt & gepusht."
|
||||
|
|
@ -163,7 +163,9 @@
|
|||
|
||||
{{-- ── Schritt 5: Domain-Setup ── --}}
|
||||
@elseif($step === 5)
|
||||
@if(!$setupDone)
|
||||
<div wire:poll.2s="pollSetup"></div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$anyFailed = collect($domainStatus)->contains(fn($s) => in_array($s, ['error','nodns','noipv6']));
|
||||
|
|
@ -292,12 +294,10 @@
|
|||
</div>
|
||||
@elseif($step === 5 && $setupDone)
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:20px">
|
||||
{{-- Kein wire:click — plain Link damit kein Livewire-POST nötig ist.
|
||||
nginx leitet /login nach SSL-Switch automatisch auf HTTPS weiter. --}}
|
||||
<a href="/login" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content;text-decoration:none;display:inline-flex;align-items:center;gap:6px">
|
||||
<button wire:click="goToLogin" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
{{ collect($domainStatus)->contains(fn($s) => in_array($s, ['error','nodns','noipv6'])) ? 'Trotzdem zum Login' : 'Zum Login' }}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue