Initial: Caddy, Stalwart, fail2ban, crontab, allowlist scripts

This commit is contained in:
James 2026-02-23 23:20:49 +01:00
commit 4f7283ca89
12 changed files with 245 additions and 0 deletions

71
caddy_Caddyfile Normal file
View File

@ -0,0 +1,71 @@
# Global config
{
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 5
}
format json
}
}
# Zurich infrastructure
zurich.inou.com {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
-Server
}
respond "inou security infrastructure" 200
}
ntfy.inou.com {
reverse_proxy 127.0.0.1:2586
}
kuma.inou.com {
log {
output file /var/log/caddy/kuma.log {
roll_size 50mb
roll_keep 5
}
format json
}
reverse_proxy 127.0.0.1:3001
}
vault.inou.com {
log {
output file /var/log/caddy/vault.log {
roll_size 50mb
roll_keep 5
}
format json
}
reverse_proxy 127.0.0.1:8080
}
mail.inou.com {
reverse_proxy 127.0.0.1:8880
}
mail.jongsma.me {
reverse_proxy 127.0.0.1:8880
}
harryhaasjes.nl, www.harryhaasjes.nl {
root * /var/www/harryhaasjes/public
file_server
encode gzip
}
stpetersburgaquatics.com, www.stpetersburgaquatics.com {
root * /var/www/stpetersburgaquatics/public
file_server
encode gzip
}

2
crontab_root.txt Normal file
View File

@ -0,0 +1,2 @@
0 3 * * * /opt/vaultwarden/backup.sh >> /var/log/vaultwarden-backup.log 2>&1
*/15 * * * * /usr/local/bin/stalwart-allowlist-sync.sh >> /var/log/stalwart-allowsync.log 2>&1

4
fail2ban/caddy-kuma.conf Normal file
View File

@ -0,0 +1,4 @@
[Definition]
failregex = .*"remote_ip":"<HOST>".*"uri":"/api/login.*"status":40[13]
.*"client_ip":"<HOST>".*"uri":"/api/login.*"status":40[13]
ignoreregex =

View File

@ -0,0 +1,4 @@
[Definition]
failregex = .*"remote_ip":"<HOST>".*"uri":".*(?:wp-admin|wp-login|\.env|\.git|phpmyadmin|phpinfo|xmlrpc|actuator|setup\.php|config\.php|admin\.php|shell|eval\(|\.aws|\.ssh).*"status":(?:400|403|404|405)
.*"client_ip":"<HOST>".*"uri":".*(?:wp-admin|wp-login|\.env|\.git|phpmyadmin|phpinfo|xmlrpc|actuator|setup\.php|config\.php|admin\.php|shell|eval\(|\.aws|\.ssh).*"status":(?:400|403|404|405)
ignoreregex =

View File

@ -0,0 +1,7 @@
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = true

8
fail2ban/jail.local Normal file
View File

@ -0,0 +1,8 @@
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

6
fail2ban/stalwart.conf Normal file
View File

@ -0,0 +1,6 @@
[Definition]
# Match auth failures and too-many-attempts from Stalwart logs
failregex = ^\S+ \S+ .*\(auth\.failed\).*remoteIp = <HOST>
^\S+ \S+ .*\(auth\.too-many-attempts\).*remoteIp = <HOST>
ignoreregex =
datepattern = %%Y-%%m-%%dT%%H:%%M:%%SZ

View File

@ -0,0 +1,4 @@
[Definition]
failregex = .*"remote_ip":"<HOST>".*"uri":"/api/accounts/login.*"status":40[13]
.*"client_ip":"<HOST>".*"uri":"/api/accounts/login.*"status":40[13]
ignoreregex =

12
stalwart-allowlist-sync.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
STATE_FILE=/var/lib/stalwart-allowsync/home-ip
CURRENT_IP=47.197.93.62
[ -z "$CURRENT_IP" ] && exit 1
mkdir -p /var/lib/stalwart-allowsync
LAST_IP=$(cat "$STATE_FILE" 2>/dev/null)
[ "$CURRENT_IP" = "$LAST_IP" ] && exit 0
echo "$(date -u): Home IP changed: $LAST_IP -> $CURRENT_IP"
python3 /usr/local/bin/stalwart-update-allowip.py "$CURRENT_IP"
/opt/stalwart/bin/stalwart-cli -u http://127.0.0.1:8880 -c admin:JamesAdmin2026x server reload-config > /dev/null
echo "$CURRENT_IP" > "$STATE_FILE"
echo Done.

16
stalwart-update-allowip.py Executable file
View File

@ -0,0 +1,16 @@
import sys, re
new_ip = sys.argv[1]
config_path = '/opt/stalwart/etc/config.toml'
content = open(config_path).read()
new_section = '[server.allowed-ip]\n"' + new_ip + '" = true\n'
if '[server.allowed-ip]' in content:
content = re.sub(r'\[server\.allowed-ip\].*?(?=\n\[|\Z)', new_section, content, flags=re.DOTALL)
else:
content = content.rstrip() + '\n\n' + new_section
open(config_path, 'w').write(content)
print(f'Updated allowed-ip to {new_ip}')
# After writing config, commit to git
import subprocess
subprocess.run(['git', '-C', '/opt/stalwart/etc', 'add', 'config.toml'], capture_output=True)
subprocess.run(['git', '-C', '/opt/stalwart/etc', 'commit', '-m', f'auto: allowed-ip updated to {new_ip}'], capture_output=True)

83
stalwart_config.toml Normal file
View File

@ -0,0 +1,83 @@
[server.listener.smtp]
bind = "[::]:25"
protocol = "smtp"
[server.listener.submission]
bind = "[::]:587"
protocol = "smtp"
[server.listener.submissions]
bind = "[::]:465"
protocol = "smtp"
tls.implicit = true
[server.listener.imap]
bind = "[::]:143"
protocol = "imap"
[server.listener.imaptls]
bind = "[::]:993"
protocol = "imap"
tls.implicit = true
[server.listener.pop3]
bind = "[::]:110"
protocol = "pop3"
[server.listener.pop3s]
bind = "[::]:995"
protocol = "pop3"
tls.implicit = true
[server.listener.sieve]
bind = "[::]:4190"
protocol = "managesieve"
[server.listener.https]
protocol = "http"
bind = "127.0.0.1:8443"
tls.implicit = false
[server.listener.http]
protocol = "http"
bind = "127.0.0.1:8880"
[storage]
data = "rocksdb"
fts = "rocksdb"
blob = "rocksdb"
lookup = "rocksdb"
directory = "internal"
[store.rocksdb]
type = "rocksdb"
path = "/opt/stalwart/data"
compression = "lz4"
[directory.internal]
type = "internal"
store = "rocksdb"
[tracer.log]
type = "log"
level = "info"
path = "/opt/stalwart/logs"
prefix = "stalwart.log"
rotate = "daily"
ansi = false
enable = true
[authentication.fallback-admin]
user = "admin"
secret = "$6$stalwartjames$OlCxhWXHNuO3Szh.HHPmjuh3oI/B0iCYjeERKqXSlpGHw40oHxVOd0IW9pJZn54QjA2Dbdlrin.SQRfZBG8pw1"
[lookup.default]
hostname = "mail.jongsma.me"
[certificate.default]
cert = "%{file:/etc/letsencrypt/live/mail.jongsma.me/fullchain.pem}%"
private-key = "%{file:/etc/letsencrypt/live/mail.jongsma.me/privkey.pem}%"
default = true
[server.allowed-ip]
"47.197.93.62" = true

28
vaultwarden_backup.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
# Vaultwarden daily backup
BACKUP_DIR="/opt/vaultwarden/backups"
DATA_DIR="/opt/vaultwarden/data"
DATE=$(date +%Y-%m-%d)
BACKUP_FILE="$BACKUP_DIR/vaultwarden-$DATE.tar.gz.gpg"
mkdir -p "$BACKUP_DIR"
# Stop container briefly for consistent backup
docker stop vaultwarden
sleep 2
# Create encrypted backup (symmetric, passphrase from file)
tar czf - -C /opt/vaultwarden data | gpg --batch --yes --symmetric --cipher-algo AES256 --passphrase-file /opt/vaultwarden/.backup_passphrase -o "$BACKUP_FILE"
# Restart
docker start vaultwarden
# Upload to gdrive if rclone is configured
if rclone listremotes 2>/dev/null | grep -q gdrive; then
rclone copy "$BACKUP_FILE" gdrive:backups/vaultwarden/
fi
# Retain only last 30 days
find "$BACKUP_DIR" -name "vaultwarden-*.tar.gz.gpg" -mtime +30 -delete
echo "Backup complete: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))"