-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·539 lines (457 loc) · 21.4 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·539 lines (457 loc) · 21.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
#!/bin/bash
# SpamEater Setup Script - Optimized Version
# Uses external config files to reduce script size
set -e # Exit on any error
# ── Terminal Ledger output theme ──────────────────────────────────────
if [ -t 1 ]; then
C_ACCENT=$'\e[38;2;163;230;53m'
C_TEXT=$'\e[38;2;238;244;234m'
C_DIM=$'\e[38;2;125;138;118m'
C_WARN=$'\e[38;2;230;194;41m'
C_ERR=$'\e[38;2;230;95;69m'
C_BOLD=$'\e[1m'
C_RESET=$'\e[0m'
else
C_ACCENT=''; C_TEXT=''; C_DIM=''; C_WARN=''; C_ERR=''; C_BOLD=''; C_RESET=''
fi
S_OK="${C_ACCENT}ok${C_RESET}"
S_ERR="${C_ERR}error:${C_RESET}"
S_WARN="${C_WARN}warn:${C_RESET}"
S_INFO="${C_DIM}info:${C_RESET}"
S_ARROW="${C_ACCENT}▸${C_RESET}"
banner() {
echo ""
echo "${C_ACCENT}${C_BOLD}SPAMEATER${C_RESET} ${C_DIM}$1${C_RESET}"
echo "${C_DIM}────────────────────────────────────────${C_RESET}"
}
banner "v4 setup"
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo "${S_ERR} This script needs to run as root!"
exit 1
fi
# Collect configuration
echo -e "\n${S_ARROW} Configuration Setup"
read -p "${S_ARROW} Enter your email domain (e.g., example.com): " EMAIL_DOMAIN
if [[ -z "$EMAIL_DOMAIN" ]]; then
echo "${S_ERR} Email domain is required!"
exit 1
fi
echo -e "\n${S_ARROW} Web Access Configuration"
echo "By default, the web interface will be accessible at: $EMAIL_DOMAIN"
echo "You can optionally use a different subdomain for privacy (e.g., mail.$EMAIL_DOMAIN)"
read -p "Enter web access domain (press Enter to use $EMAIL_DOMAIN): " WEB_DOMAIN
WEB_DOMAIN="${WEB_DOMAIN:-$EMAIL_DOMAIN}"
echo -e "\n${S_ARROW} Email Domain: $EMAIL_DOMAIN (emails will be received at @$EMAIL_DOMAIN)"
echo "${S_ARROW} Web Access: https://$WEB_DOMAIN"
read -p "Is this correct? (Y/n): " CONFIRM
if [[ "$CONFIRM" =~ ^[Nn]$ ]]; then
echo "${S_ERR} Setup cancelled"
exit 1
fi
# Generate credentials
ADMIN_EMAIL="admin@$EMAIL_DOMAIN"
# openssl rand -hex 16 always yields exactly 32 chars of full entropy
DELETE_TOKEN_SECRET=$(openssl rand -hex 16)
CSRF_SECRET=$(openssl rand -hex 16)
ENCRYPTION_KEY=$(openssl rand -hex 16)
echo -e "\n${S_OK} Generated credentials and secrets"
# Create dedicated user
echo "${S_ARROW} Creating spameater user..."
useradd -r -s /bin/false -d /opt/spameater spameater 2>/dev/null || echo "User already exists"
# Store the original script directory
ORIGINAL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Detect OS and install dependencies
echo -e "\n${S_ARROW} Installing system dependencies..."
if command -v dnf >/dev/null 2>&1; then
PKG_MGR="dnf"
echo " Detected: RHEL/Fedora-based system (dnf)"
echo -n " ├─ Updating package cache... "
dnf update -y -q && echo "${S_OK}"
echo -n " ├─ Installing EPEL repository (certbot, fail2ban)... "
dnf install -y epel-release -q 2>/dev/null && echo "${S_OK}" || echo "${S_WARN} not available (assuming EPEL is already configured)"
echo -n " ├─ Adding Node.js 22 repository... "
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 && echo "${S_OK}"
echo -n " ├─ Installing: nodejs nginx sqlite certbot fail2ban firewalld... "
dnf install -y nodejs nginx sqlite certbot python3-certbot-nginx fail2ban firewalld -q && echo "${S_OK}"
echo -n " ├─ Installing: Development Tools (gcc, make, etc.)... "
dnf groupinstall -y "Development Tools" -q && echo "${S_OK}"
echo -n " ├─ Installing: python3-devel sqlite-devel... "
dnf install -y python3-devel sqlite-devel -q && echo "${S_OK}"
echo -n " ├─ Installing: ModSecurity WAF... "
if dnf install -y nginx-mod-modsecurity libmodsecurity mod_security_crs -q 2>/dev/null; then
echo "${S_OK}"
else
# EPEL does not package the nginx connector for every release
# (EL10 at the time of writing). Fall back to a COPR that builds
# binary RPMs against the distro nginx on Fedora infrastructure.
echo -n "trying COPR (mikelo2/modsecurity-el10)... "
cat > /etc/yum.repos.d/copr-modsecurity-el10.repo << 'COPREOF'
[copr-modsecurity-el10]
name=COPR mikelo2/modsecurity-el10 (ModSecurity for nginx)
baseurl=https://download.copr.fedorainfracloud.org/results/mikelo2/modsecurity-el10/epel-10-$basearch/
gpgcheck=1
gpgkey=https://download.copr.fedorainfracloud.org/results/mikelo2/modsecurity-el10/pubkey.gpg
enabled=1
repo_gpgcheck=0
COPREOF
if dnf install -y nginx-mod-modsecurity libmodsecurity mod_security_crs -q 2>/dev/null; then
echo "${S_OK}"
else
rm -f /etc/yum.repos.d/copr-modsecurity-el10.repo
echo "${S_WARN} not available"
fi
fi
echo -n " └─ Enabling firewalld... "
systemctl enable --now firewalld >/dev/null 2>&1 && echo "${S_OK}"
elif command -v yum >/dev/null 2>&1; then
PKG_MGR="yum"
echo " Detected: CentOS/RHEL 7 (yum)"
echo -n " ├─ Updating package cache... "
yum update -y -q && echo "${S_OK}"
echo -n " ├─ Installing EPEL repository... "
yum install -y epel-release -q && echo "${S_OK}"
echo -n " ├─ Adding Node.js 22 repository... "
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 && echo "${S_OK}"
echo -n " ├─ Installing: nodejs nginx sqlite certbot fail2ban firewalld... "
yum install -y nodejs nginx sqlite certbot python2-certbot-nginx fail2ban firewalld -q && echo "${S_OK}"
echo -n " ├─ Installing: Development Tools (gcc, make, etc.)... "
yum groupinstall -y "Development Tools" -q && echo "${S_OK}"
echo -n " ├─ Installing: python3-devel sqlite-devel... "
yum install -y python3-devel sqlite-devel -q && echo "${S_OK}"
echo -n " ├─ Installing: ModSecurity WAF... "
yum install -y mod_security mod_security_crs -q 2>/dev/null && echo "${S_OK}" || echo "${S_WARN} not available"
echo -n " └─ Enabling firewalld... "
systemctl enable --now firewalld >/dev/null 2>&1 && echo "${S_OK}"
else
PKG_MGR="apt"
echo " Detected: Debian/Ubuntu-based system (apt)"
echo -n " ├─ Updating package cache... "
apt update -qq && echo "${S_OK}"
echo -n " ├─ Adding Node.js 22 repository... "
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 && echo "${S_OK}"
echo -n " ├─ Installing: nodejs nginx sqlite3 certbot fail2ban ufw... "
apt install -y nodejs nginx sqlite3 certbot python3-certbot-nginx fail2ban ufw -qq && echo "${S_OK}"
echo -n " ├─ Installing: build-essential python3-dev libsqlite3-dev... "
apt install -y build-essential python3-dev libsqlite3-dev -qq && echo "${S_OK}"
echo -n " └─ Installing: ModSecurity WAF... "
apt install -y libnginx-mod-modsecurity libmodsecurity3 libmodsecurity-dev -qq 2>/dev/null && echo "${S_OK}" || echo "${S_WARN} not available"
fi
# Verify Node.js version
NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
if [ "$NODE_VERSION" -lt 22 ]; then
echo "${S_ERR} Node.js version $NODE_VERSION is too old. Please install Node.js 22 LTS."
exit 1
fi
echo "${S_OK} Node.js $(node --version), npm $(npm --version 2>/dev/null)"
# Create directory structure
echo -e "\n${S_ARROW} Setting up directories..."
mkdir -p /opt/spameater/{haraka,frontend,data,logs,modsecurity}
mkdir -p /opt/spameater/data/inboxes
mkdir -p /opt/spameater/haraka/queue
mkdir -p /opt/spameater/frontend/.well-known
chown -R spameater:spameater /opt/spameater
chmod 755 /opt/spameater
chmod 755 /opt/spameater/data
# Create environment file
cat > /opt/spameater/.env << EOF
DELETE_TOKEN_SECRET=$DELETE_TOKEN_SECRET
CSRF_SECRET=$CSRF_SECRET
ENCRYPTION_KEY=$ENCRYPTION_KEY
NODE_ENV=production
EOF
chown spameater:spameater /opt/spameater/.env
chmod 600 /opt/spameater/.env
# Setup database
echo "${S_ARROW} Setting up database..."
sudo -u spameater sqlite3 /opt/spameater/data/emails.db < "$ORIGINAL_DIR/database/schema.sql" >/dev/null 2>&1
chmod 600 /opt/spameater/data/emails.db
# Install Haraka
echo -e "\n${S_ARROW} Installing Haraka..."
npm install -g Haraka@3.3.3 --loglevel=error 2>/dev/null
# Initialize Haraka
cd /opt/spameater/haraka
sudo -u spameater haraka -i /opt/spameater/haraka >/dev/null 2>&1
# Install npm dependencies
echo -e "\n${S_ARROW} Installing npm dependencies..."
mkdir -p /tmp/spameater-npm-cache
chown -R spameater:spameater /tmp/spameater-npm-cache
# Apply the same dependency overrides as the repo manifest, so sqlite3's
# build-tool chain resolves without known-vulnerable versions
sudo -u spameater node -e '
const fs = require("fs");
const p = "/opt/spameater/haraka/package.json";
const pkg = fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, "utf8")) : {};
pkg.overrides = Object.assign({}, pkg.overrides, { "node-gyp": "^11.0.0", "tar": "^7.5.22" });
fs.writeFileSync(p, JSON.stringify(pkg, null, 2));
' 2>/dev/null || true
# Install for Haraka
echo -n " ├─ Haraka plugins: sqlite3 isomorphic-dompurify... "
cd /opt/spameater/haraka
sudo -u spameater npm install sqlite3@5.1.7 isomorphic-dompurify@3.22.0 --cache /tmp/spameater-npm-cache --unsafe-perm --loglevel=error 2>/dev/null && echo "${S_OK}" || echo "${S_WARN} failed"
# Install for API server
echo -n " └─ API server: express helmet express-rate-limit sqlite3... "
cd /opt/spameater
sudo -u spameater npm install express@5.2.1 helmet@8.3.0 express-rate-limit@8.6.2 sqlite3@5.1.7 --cache /tmp/spameater-npm-cache --unsafe-perm --loglevel=error 2>/dev/null && echo "${S_OK}" || echo "${S_WARN} failed"
# sqlite3 stays on 5.1.7: its prebuilt binaries load on every supported OS
# (6.x prebuilds require glibc 2.38+, newer than RHEL 9, Ubuntu 22.04, and
# Debian 11 ship, and would force a from-source compile at install time).
echo -e "\n${S_ARROW} Verifying sqlite3 native binding..."
for APP_DIR in /opt/spameater/haraka /opt/spameater; do
if sudo -u spameater node -e "require('$APP_DIR/node_modules/sqlite3')" >/dev/null 2>&1; then
echo " ├─ $APP_DIR: ${S_OK}"
else
echo " ├─ $APP_DIR: ${S_WARN} sqlite3 binding failed to load"
fi
done
# Copy all files
echo -e "\n${S_ARROW} Copying application files..."
# Copy Haraka config files
cp "$ORIGINAL_DIR/haraka/config/"*.ini /opt/spameater/haraka/config/
cp "$ORIGINAL_DIR/haraka/config/plugins" /opt/spameater/haraka/config/
# Process Haraka templates
sed "s/EMAIL_DOMAIN_PLACEHOLDER/$EMAIL_DOMAIN/g" "$ORIGINAL_DIR/haraka/config/me.template" > /opt/spameater/haraka/config/me
sed "s/EMAIL_DOMAIN_PLACEHOLDER/$EMAIL_DOMAIN/g" "$ORIGINAL_DIR/haraka/config/host_list.template" > /opt/spameater/haraka/config/host_list
# Copy Haraka plugins
cp "$ORIGINAL_DIR/haraka/plugins/"*.js /opt/spameater/haraka/plugins/
# Copy frontend files
cp -r "$ORIGINAL_DIR/frontend/"* /opt/spameater/frontend/
# Process frontend templates
sed -i "s/EMAIL_DOMAIN_PLACEHOLDER/$EMAIL_DOMAIN/g" /opt/spameater/frontend/index.html
# Process security.txt
if [ -f "/opt/spameater/frontend/.well-known/security.txt.template" ]; then
EXPIRY_DATE=$(date -d "+1 year" -u +"%Y-%m-%dT%H:%M:%S.000Z")
sed -e "s/EMAIL_DOMAIN_PLACEHOLDER/$EMAIL_DOMAIN/g" \
-e "s/EXPIRY_DATE_PLACEHOLDER/$EXPIRY_DATE/g" \
/opt/spameater/frontend/.well-known/security.txt.template > /opt/spameater/frontend/.well-known/security.txt
rm -f /opt/spameater/frontend/.well-known/security.txt.template
fi
# Copy other files
cp "$ORIGINAL_DIR/api-server.js" /opt/spameater/
mkdir -p /opt/spameater/lib
cp "$ORIGINAL_DIR/lib/"*.js /opt/spameater/lib/
cp "$ORIGINAL_DIR/deploy/cleanup.sh" /opt/spameater/
chmod +x /opt/spameater/cleanup.sh /opt/spameater/api-server.js
# Setup ModSecurity
echo -e "\n${S_ARROW} Setting up ModSecurity WAF..."
MODSEC_ENABLED=false
if [ -f "/etc/nginx/modules/ngx_http_modsecurity_module.so" ] || \
[ -f "/usr/lib64/nginx/modules/ngx_http_modsecurity_module.so" ] || \
[ -f "/usr/share/nginx/modules/mod-modsecurity.conf" ]; then
echo "${S_OK} ModSecurity module detected"
cd /opt/spameater/modsecurity
# Download unicode mapping
wget -q https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/unicode.mapping 2>/dev/null || \
curl -s -o unicode.mapping https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/unicode.mapping 2>/dev/null
# Download OWASP CRS if not exists
if [ ! -d "crs" ]; then
git clone https://github.com/coreruleset/coreruleset.git crs --quiet
cd crs && cp crs-setup.conf.example crs-setup.conf
fi
# Copy ModSecurity configs
cp "$ORIGINAL_DIR/deploy/modsecurity-main.conf" /opt/spameater/modsecurity/modsecurity.conf
cp "$ORIGINAL_DIR/deploy/modsecurity-rules.conf" /opt/spameater/modsecurity/spameater-rules.conf
cp "$ORIGINAL_DIR/deploy/nginx-modsecurity.conf" /opt/spameater/modsecurity/
# The audit log is written by the nginx worker, not by spameater
mkdir -p /opt/spameater/logs
touch /opt/spameater/logs/modsec_audit.log
chown nginx:nginx /opt/spameater/logs/modsec_audit.log 2>/dev/null || true
chmod 640 /opt/spameater/logs/modsec_audit.log
if [ -f "/opt/spameater/modsecurity/unicode.mapping" ] && [ -d "/opt/spameater/modsecurity/crs/rules" ]; then
MODSEC_ENABLED=true
echo "${S_OK} ModSecurity configuration complete"
fi
fi
cd "$ORIGINAL_DIR"
# Set ownership
chown -R spameater:spameater /opt/spameater/haraka /opt/spameater/frontend /opt/spameater/data
# Configure nginx rate limiting
echo -e "\n${S_ARROW} Configuring nginx..."
if ! grep -q "zone=api_limit" /etc/nginx/nginx.conf; then
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
# Insert rate limiting configuration
sed -i "/^http {/r $ORIGINAL_DIR/deploy/nginx-rate-limits.conf" /etc/nginx/nginx.conf
fi
# Stop nginx and clean configs
systemctl stop nginx 2>/dev/null || true
find /etc/nginx -name "*spameater*" -delete 2>/dev/null || true
# Detect nginx config directory
if [ -d "/etc/nginx/sites-available" ]; then
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
else
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
fi
# Determine if subdomain
IS_SUBDOMAIN=false
[[ "$WEB_DOMAIN" == *"."*"."* ]] && IS_SUBDOMAIN=true
# Create nginx config
cp "$ORIGINAL_DIR/deploy/nginx.conf" /tmp/spameater.conf
# Remove SSL sections for initial setup
sed -i '/^server {.*listen 443/,/^}/d' /tmp/spameater.conf
# Replace placeholders - ORDER MATTERS!
sed -i "s/EMAIL_DOMAIN_PLACEHOLDER/$EMAIL_DOMAIN/g" /tmp/spameater.conf
sed -i "s/DOMAIN_PLACEHOLDER/$WEB_DOMAIN/g" /tmp/spameater.conf
# Remove www for subdomains
if [ "$IS_SUBDOMAIN" = true ]; then
sed -i "s/server_name $WEB_DOMAIN www.$WEB_DOMAIN;/server_name $WEB_DOMAIN;/g" /tmp/spameater.conf
fi
# Add ModSecurity if enabled
# Dynamic modules never show in `nginx -V`; check for the module file
if [ "$MODSEC_ENABLED" = true ] && { [ -f /usr/lib64/nginx/modules/ngx_http_modsecurity_module.so ] || [ -f /etc/nginx/modules/ngx_http_modsecurity_module.so ] || nginx -V 2>&1 | grep -q modsecurity; }; then
sed -i '/location \/ {/i\ # ModSecurity WAF\n include /opt/spameater/modsecurity/nginx-modsecurity.conf;\n' /tmp/spameater.conf
fi
# Install nginx config
if [ "$NGINX_SITES_DIR" = "/etc/nginx/conf.d" ]; then
mv /tmp/spameater.conf /etc/nginx/conf.d/spameater.conf
else
mv /tmp/spameater.conf $NGINX_SITES_DIR/spameater
ln -sf $NGINX_SITES_DIR/spameater $NGINX_ENABLED_DIR/
fi
# Test and start nginx
nginx -t && systemctl start nginx || echo "${S_ERR} nginx configuration error"
# Configure systemd services
echo -e "\n${S_ARROW} Setting up systemd services..."
cp "$ORIGINAL_DIR/deploy/haraka.service" /etc/systemd/system/
cp "$ORIGINAL_DIR/deploy/spameater-api.service" /etc/systemd/system/
systemctl daemon-reload
systemctl enable haraka spameater-api
# Configure firewall
echo -e "\n${S_ARROW} Configuring firewall..."
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
SSH_PORT=$(ss -tlnp 2>/dev/null | grep sshd | grep -oP ':\K[0-9]+' | head -1)
SSH_PORT="${SSH_PORT:-22}"
firewall-cmd --permanent --add-service=smtp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
[ "$SSH_PORT" != "22" ] && firewall-cmd --permanent --add-port=${SSH_PORT}/tcp || firewall-cmd --permanent --add-service=ssh
firewall-cmd --reload
elif command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
SSH_PORT=$(ss -tlnp 2>/dev/null | grep sshd | grep -oP ':\K[0-9]+' | head -1)
SSH_PORT="${SSH_PORT:-22}"
ufw allow 25/tcp
ufw allow 80/tcp
ufw allow 443/tcp
[ "$SSH_PORT" != "22" ] && ufw allow ${SSH_PORT}/tcp || ufw allow ssh
else
echo "${S_WARN} No active firewall detected!"
fi
# Configure fail2ban
echo -e "\n${S_ARROW} Setting up fail2ban..."
cp "$ORIGINAL_DIR/deploy/jail.local" /etc/fail2ban/
systemctl enable fail2ban
systemctl restart fail2ban
# SSL setup
echo -e "\n${S_ARROW} Setting up SSL certificates..."
mkdir -p /var/www/html/.well-known/acme-challenge
# Determine SSL domains
CERT_DOMAINS="-d $WEB_DOMAIN"
[ "$IS_SUBDOMAIN" = false ] && CERT_DOMAINS="$CERT_DOMAINS -d www.$WEB_DOMAIN"
SSL_CONFIGURED=false
if [ -d "/etc/letsencrypt/live/$WEB_DOMAIN" ]; then
echo "${S_OK} SSL certificates already exist"
certbot --nginx $CERT_DOMAINS --reinstall --redirect --non-interactive 2>/dev/null && SSL_CONFIGURED=true
else
echo "Creating new SSL certificates..."
read -p "Proceed with certificate creation? (Y/n): " CREATE_CERTS
if [[ ! "$CREATE_CERTS" =~ ^[Nn]$ ]]; then
certbot --nginx $CERT_DOMAINS --email "$ADMIN_EMAIL" --agree-tos --non-interactive --redirect && SSL_CONFIGURED=true
fi
fi
# Re-add ModSecurity AFTER SSL setup (if it was enabled earlier)
if [ "$MODSEC_ENABLED" = true ]; then
echo "${S_ARROW} Re-adding ModSecurity after SSL setup..."
# Determine which nginx config file to update
if [ "$NGINX_SITES_DIR" = "/etc/nginx/conf.d" ]; then
NGINX_CONFIG="/etc/nginx/conf.d/spameater.conf"
else
NGINX_CONFIG="/etc/nginx/sites-available/spameater"
fi
# Add ModSecurity include after server_tokens off line (ONLY IF NOT ALREADY PRESENT)
if [ -f "$NGINX_CONFIG" ] && grep -q "server_tokens off;" "$NGINX_CONFIG" && ! grep -q "modsecurity" "$NGINX_CONFIG"; then
sed -i '/server_tokens off;/a\ \n # ModSecurity WAF\n include /opt/spameater/modsecurity/nginx-modsecurity.conf;' "$NGINX_CONFIG"
# Test and reload nginx
if nginx -t >/dev/null 2>&1; then
systemctl reload nginx
echo "${S_OK} ModSecurity re-enabled after SSL setup"
fi
fi
fi
# Setup cron jobs
echo -e "\n${S_ARROW} Setting up cleanup job..."
crontab -u spameater -l 2>/dev/null | grep -v "cleanup.sh" | crontab -u spameater -
(crontab -u spameater -l 2>/dev/null; echo "0 * * * * /opt/spameater/cleanup.sh") | crontab -u spameater -
[ "$SSL_CONFIGURED" = true ] && \
(crontab -l 2>/dev/null | grep -v "certbot renew"; echo "0 12 * * * /usr/bin/certbot renew --quiet") | crontab -
# Start services
echo -e "\n${S_ARROW} Starting services..."
systemctl restart nginx
systemctl start haraka
systemctl start spameater-api
# Check services are running
if systemctl is-active --quiet haraka; then
echo "${S_OK} Haraka SMTP server: Running"
else
echo "${S_ERR} Haraka SMTP server: Failed to start"
fi
if systemctl is-active --quiet spameater-api; then
echo "${S_OK} SpamEater API: Running"
else
echo "${S_ERR} SpamEater API: Failed to start"
fi
if systemctl is-active --quiet nginx; then
echo "${S_OK} Nginx web server: Running"
else
echo "${S_ERR} Nginx web server: Failed to start"
fi
# Final message
banner "installed"
echo " ${C_DIM}email domain${C_RESET} $EMAIL_DOMAIN"
if [ "$SSL_CONFIGURED" = true ]; then
echo " ${C_DIM}web access${C_RESET} https://$WEB_DOMAIN"
else
echo " ${C_DIM}web access${C_RESET} http://$WEB_DOMAIN ${S_WARN} SSL not configured"
fi
echo " ${C_DIM}admin email${C_RESET} $ADMIN_EMAIL"
if [ "$MODSEC_ENABLED" = true ]; then
echo " ${C_DIM}modsecurity${C_RESET} enabled"
else
echo " ${C_DIM}modsecurity${C_RESET} disabled"
fi
echo " ${C_DIM}secrets${C_RESET} /opt/spameater/.env (mode 600)"
echo ""
echo "${S_INFO} Secrets are generated, stored in /opt/spameater/.env, and never shown here."
echo ""
# Get server's public IP (try multiple methods)
SERVER_IP=""
if command -v curl >/dev/null 2>&1; then
SERVER_IP=$(curl -s -4 --connect-timeout 5 ifconfig.me 2>/dev/null)
fi
if [ -z "$SERVER_IP" ] && command -v wget >/dev/null 2>&1; then
SERVER_IP=$(wget -qO- --timeout=5 ifconfig.me 2>/dev/null)
fi
if [ -z "$SERVER_IP" ]; then
# Fallback to primary network interface IP
SERVER_IP=$(ip -4 route get 8.8.8.8 2>/dev/null | awk '{print $7; exit}')
fi
if [ -z "$SERVER_IP" ]; then
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
fi
SERVER_IP="${SERVER_IP:-[YOUR_SERVER_IP]}"
echo "${S_ARROW} DNS Configuration Required:"
echo "1. MX record: $EMAIL_DOMAIN → 10 $EMAIL_DOMAIN"
echo "2. A record: $EMAIL_DOMAIN → $SERVER_IP"
if [[ "$WEB_DOMAIN" != "$EMAIL_DOMAIN" ]]; then
echo "3. A record: $WEB_DOMAIN → $SERVER_IP"
fi
echo ""
echo "${S_ARROW} Service commands:"
echo " systemctl status haraka"
echo " systemctl status nginx"
echo " systemctl status spameater-api"
echo ""
echo "${S_ARROW} Logs:"
echo " journalctl -u haraka -f"
echo " journalctl -u spameater-api -f"
echo " tail -f /var/log/nginx/access.log"