d262253faf
Bei vorhandener config.yml wird das DB-Passwort daraus gelesen und PostgreSQL entsprechend aktualisiert — verhindert Auth-Fehler beim zweiten Installer-Durchlauf auf demselben Server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
367 lines
14 KiB
Bash
Executable File
367 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
||
# archivmail Server-Installer
|
||
# Unterstützte Systeme: Debian 12 / 13
|
||
# Aufruf: bash install.sh
|
||
# Mit eigenem DB-Passwort: DB_PASSWORD=geheim bash install.sh
|
||
|
||
set -euo pipefail
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||
log() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||
info() { echo -e "${BLUE}[..]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
|
||
die() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; }
|
||
|
||
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen: sudo bash install.sh"
|
||
|
||
DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)}"
|
||
API_SECRET="${API_SECRET:-$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 64)}"
|
||
INSTALL_DIR="/opt/archivmail"
|
||
STORE_DIR="/var/archivmail"
|
||
LOG_DIR="/var/log/archivmail"
|
||
CONFIG_DIR="/etc/archivmail"
|
||
SSL_DIR="/etc/ssl/archivmail"
|
||
AM_USER="archivmail"
|
||
FQDN="$(hostname -f 2>/dev/null || hostname)"
|
||
REPO_URL="${REPO_URL:-https://gitea.perlbach24.de/scripte/archivmail.git}"
|
||
|
||
echo ""
|
||
echo " ╔══════════════════════════════════════╗"
|
||
echo " ║ archivmail Installer v1.1 ║"
|
||
echo " ╚══════════════════════════════════════╝"
|
||
echo ""
|
||
info "Hostname: $FQDN"
|
||
echo ""
|
||
|
||
# ── 1. Pakete ─────────────────────────────────────────────────────────────────
|
||
info "Installiere Systempakete..."
|
||
apt-get update -qq
|
||
apt-get install -y -qq \
|
||
golang-go nodejs npm postgresql nginx \
|
||
libxapian-dev pkg-config build-essential \
|
||
curl git rsync logrotate openssl
|
||
log "Pakete installiert"
|
||
|
||
# go im PATH sicherstellen (Debian legt binary nicht immer in /usr/bin)
|
||
if ! command -v go >/dev/null 2>&1; then
|
||
GO_BIN=$(find /usr/lib/go-*/bin /usr/local/go/bin -name "go" 2>/dev/null | head -1)
|
||
[[ -n "$GO_BIN" ]] || die "go binary nicht gefunden nach Installation"
|
||
ln -sf "$GO_BIN" /usr/local/bin/go
|
||
log "go binary verlinkt: $GO_BIN → /usr/local/bin/go"
|
||
fi
|
||
log "go $(go version | awk '{print $3}')"
|
||
|
||
# ── 2. Systembenutzer ─────────────────────────────────────────────────────────
|
||
info "Lege Systembenutzer '$AM_USER' an..."
|
||
id "$AM_USER" &>/dev/null \
|
||
&& log "Benutzer '$AM_USER' existiert bereits" \
|
||
|| { useradd --system --shell /bin/false --home "$STORE_DIR" --create-home "$AM_USER"; log "Benutzer angelegt"; }
|
||
|
||
# ── 3. Verzeichnisstruktur ────────────────────────────────────────────────────
|
||
info "Erstelle Verzeichnisstruktur..."
|
||
mkdir -p "$STORE_DIR/store" "$STORE_DIR/astore" "$STORE_DIR/xapian"
|
||
mkdir -p "$CONFIG_DIR" "$LOG_DIR" "$INSTALL_DIR" "$INSTALL_DIR/web" "$SSL_DIR"
|
||
chown -R "$AM_USER:$AM_USER" "$STORE_DIR" "$LOG_DIR"
|
||
chmod 755 "$STORE_DIR"
|
||
chmod 700 "$STORE_DIR/store" "$STORE_DIR/astore" "$STORE_DIR/xapian"
|
||
log "Verzeichnisse erstellt"
|
||
|
||
# ── 4. Keyfile ────────────────────────────────────────────────────────────────
|
||
info "Generiere Verschlüsselungs-Keyfile..."
|
||
if [ ! -f "$CONFIG_DIR/keyfile" ]; then
|
||
openssl rand -base64 32 > "$CONFIG_DIR/keyfile"
|
||
chmod 400 "$CONFIG_DIR/keyfile"
|
||
chown "$AM_USER:$AM_USER" "$CONFIG_DIR/keyfile"
|
||
log "Keyfile generiert: $CONFIG_DIR/keyfile"
|
||
else
|
||
log "Keyfile existiert bereits – wird nicht überschrieben"
|
||
fi
|
||
|
||
# ── 5. TLS-Zertifikat ─────────────────────────────────────────────────────────
|
||
info "Erstelle selbstsigniertes TLS-Zertifikat..."
|
||
if [ ! -f "$SSL_DIR/archivmail.crt" ]; then
|
||
SERVER_IP="$(hostname -I | awk '{print $1}')"
|
||
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
|
||
-keyout "$SSL_DIR/archivmail.key" \
|
||
-out "$SSL_DIR/archivmail.crt" \
|
||
-subj "/CN=${FQDN}/O=archivmail/C=DE" \
|
||
-addext "subjectAltName=DNS:${FQDN},DNS:$(hostname -s),IP:${SERVER_IP}" \
|
||
2>/dev/null
|
||
chmod 640 "$SSL_DIR/archivmail.key"
|
||
chmod 644 "$SSL_DIR/archivmail.crt"
|
||
chown "root:$AM_USER" "$SSL_DIR/archivmail.key"
|
||
log "TLS-Zertifikat erstellt: $SSL_DIR/archivmail.crt (FQDN: $FQDN, IP: $SERVER_IP)"
|
||
else
|
||
log "TLS-Zertifikat existiert bereits – wird nicht überschrieben"
|
||
fi
|
||
|
||
# ── 6. PostgreSQL ─────────────────────────────────────────────────────────────
|
||
info "Richte PostgreSQL ein..."
|
||
systemctl enable postgresql --quiet
|
||
systemctl start postgresql
|
||
|
||
# Falls config.yml schon existiert, DB-Passwort daraus lesen (verhindert Passwort-Mismatch bei Re-Install)
|
||
if [ -f "$CONFIG_DIR/config.yml" ]; then
|
||
EXISTING_PW=$(grep -A5 '^database:' "$CONFIG_DIR/config.yml" | awk '/password:/{print $2}' | head -1)
|
||
[[ -n "$EXISTING_PW" ]] && DB_PASSWORD="$EXISTING_PW" && info "DB-Passwort aus vorhandener config.yml übernommen"
|
||
fi
|
||
|
||
su -c "psql -tc \"SELECT 1 FROM pg_roles WHERE rolname='archivmail'\" | grep -q 1 \
|
||
&& psql -c \"ALTER USER archivmail WITH PASSWORD '$DB_PASSWORD'\" \
|
||
|| psql -c \"CREATE USER archivmail WITH PASSWORD '$DB_PASSWORD'\"" postgres
|
||
su -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname='archivmail'\" | grep -q 1 || \
|
||
psql -c \"CREATE DATABASE archivmail OWNER archivmail\"" postgres
|
||
|
||
su -c "psql archivmail -c \"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO archivmail;\"" postgres
|
||
su -c "psql archivmail -c \"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO archivmail;\"" postgres
|
||
su -c "psql archivmail -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO archivmail;\"" postgres
|
||
su -c "psql archivmail -c \"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO archivmail;\"" postgres
|
||
|
||
log "PostgreSQL eingerichtet"
|
||
log "Standard-Benutzer werden beim ersten Daemon-Start angelegt"
|
||
|
||
# ── 7. Konfiguration ──────────────────────────────────────────────────────────
|
||
info "Erstelle Konfigurationsdatei..."
|
||
if [ ! -f "$CONFIG_DIR/config.yml" ]; then
|
||
cat > "$CONFIG_DIR/config.yml" << CONFIG
|
||
# archivmail Konfiguration – generiert am $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
server:
|
||
api_port: 8080
|
||
smtp_port: 2525
|
||
|
||
database:
|
||
host: 127.0.0.1
|
||
port: 5432
|
||
name: archivmail
|
||
user: archivmail
|
||
password: ${DB_PASSWORD}
|
||
sslmode: disable
|
||
|
||
storage:
|
||
store_path: ${STORE_DIR}/store
|
||
astore_path: ${STORE_DIR}/astore
|
||
xapian_path: ${STORE_DIR}/xapian
|
||
keyfile: ${CONFIG_DIR}/keyfile
|
||
|
||
api:
|
||
bind: ":8080"
|
||
secret: ${API_SECRET}
|
||
trusted_proxies:
|
||
- 127.0.0.1
|
||
secure_cookies: true
|
||
|
||
index:
|
||
path: ${STORE_DIR}/xapian
|
||
backend: xapian
|
||
batch_size: 100
|
||
|
||
audit:
|
||
log_path: ${LOG_DIR}/audit.log
|
||
retention_days: 0
|
||
|
||
smtp:
|
||
enabled: true
|
||
bind: ":2525"
|
||
domain: "${FQDN}"
|
||
tls_cert: ${SSL_DIR}/archivmail.crt
|
||
tls_key: ${SSL_DIR}/archivmail.key
|
||
allowed_ips:
|
||
- 127.0.0.1
|
||
|
||
imap_server:
|
||
enabled: true
|
||
bind: ":993"
|
||
tls_cert: ${SSL_DIR}/archivmail.crt
|
||
tls_key: ${SSL_DIR}/archivmail.key
|
||
CONFIG
|
||
chmod 640 "$CONFIG_DIR/config.yml"
|
||
chown "root:$AM_USER" "$CONFIG_DIR/config.yml"
|
||
log "Konfiguration erstellt: $CONFIG_DIR/config.yml"
|
||
else
|
||
log "config.yml existiert bereits – wird nicht überschrieben"
|
||
fi
|
||
|
||
# ── 8. Nginx (HTTP + HTTPS) ────────────────────────────────────────────────────
|
||
info "Konfiguriere Nginx (HTTP → HTTPS Redirect + TLS)..."
|
||
cat > /etc/nginx/sites-available/archivmail << NGINX
|
||
# HTTP → HTTPS Redirect
|
||
server {
|
||
listen 80;
|
||
server_name ${FQDN} _;
|
||
return 301 https://\$host\$request_uri;
|
||
}
|
||
|
||
# HTTPS
|
||
server {
|
||
listen 443 ssl;
|
||
http2 on;
|
||
server_name ${FQDN} _;
|
||
|
||
ssl_certificate ${SSL_DIR}/archivmail.crt;
|
||
ssl_certificate_key ${SSL_DIR}/archivmail.key;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
ssl_prefer_server_ciphers on;
|
||
ssl_session_cache shared:SSL:10m;
|
||
ssl_session_timeout 10m;
|
||
|
||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||
add_header X-Content-Type-Options nosniff always;
|
||
add_header X-Frame-Options SAMEORIGIN always;
|
||
|
||
location /api/ {
|
||
proxy_pass http://127.0.0.1:8080;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host \$host;
|
||
proxy_set_header X-Real-IP \$remote_addr;
|
||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||
client_max_body_size 512M;
|
||
}
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade \$http_upgrade;
|
||
proxy_set_header Connection 'upgrade';
|
||
proxy_set_header Host \$host;
|
||
proxy_set_header X-Real-IP \$remote_addr;
|
||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||
proxy_cache_bypass \$http_upgrade;
|
||
}
|
||
|
||
access_log /var/log/nginx/archivmail.access.log;
|
||
error_log /var/log/nginx/archivmail.error.log;
|
||
}
|
||
NGINX
|
||
ln -sf /etc/nginx/sites-available/archivmail /etc/nginx/sites-enabled/archivmail
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
nginx -t
|
||
systemctl enable nginx --quiet
|
||
systemctl restart nginx
|
||
log "Nginx konfiguriert (HTTP→HTTPS, TLS)"
|
||
|
||
# ── 9. logrotate ──────────────────────────────────────────────────────────────
|
||
cat > /etc/logrotate.d/archivmail << LOGROTATE
|
||
${LOG_DIR}/audit.log {
|
||
daily
|
||
rotate 365
|
||
compress
|
||
delaycompress
|
||
missingok
|
||
notifempty
|
||
create 640 ${AM_USER} ${AM_USER}
|
||
}
|
||
LOGROTATE
|
||
log "logrotate konfiguriert"
|
||
|
||
# ── 10. systemd Units ─────────────────────────────────────────────────────────
|
||
info "Erstelle systemd Units..."
|
||
cat > /etc/systemd/system/archivmail.service << UNIT
|
||
[Unit]
|
||
Description=archivmail Mail Archive Daemon
|
||
After=network.target postgresql.service
|
||
Requires=postgresql.service
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=${AM_USER}
|
||
Group=${AM_USER}
|
||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||
ExecStart=${INSTALL_DIR}/archivmail --config ${CONFIG_DIR}/config.yml
|
||
Restart=on-failure
|
||
RestartSec=5
|
||
StandardOutput=journal
|
||
StandardError=journal
|
||
SyslogIdentifier=archivmail
|
||
NoNewPrivileges=false
|
||
ProtectSystem=strict
|
||
ReadWritePaths=${STORE_DIR} ${LOG_DIR}
|
||
ReadOnlyPaths=${CONFIG_DIR} ${SSL_DIR}
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
UNIT
|
||
|
||
cat > /etc/systemd/system/archivmail-web.service << UNIT
|
||
[Unit]
|
||
Description=archivmail Web Frontend
|
||
After=network.target archivmail.service
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=${AM_USER}
|
||
Group=${AM_USER}
|
||
WorkingDirectory=${INSTALL_DIR}/web
|
||
ExecStart=/usr/bin/node server.js
|
||
Environment=NODE_ENV=production
|
||
Environment=PORT=3000
|
||
Environment=NEXT_PUBLIC_API_URL=http://127.0.0.1:8080
|
||
Restart=on-failure
|
||
RestartSec=5
|
||
StandardOutput=journal
|
||
StandardError=journal
|
||
SyslogIdentifier=archivmail-web
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
UNIT
|
||
|
||
systemctl daemon-reload
|
||
systemctl enable archivmail archivmail-web --quiet
|
||
log "systemd Units erstellt und aktiviert"
|
||
|
||
# ── 11. update.sh installieren und erstes Deployment ──────────────────────────
|
||
info "Installiere update.sh und führe erstes Deployment durch..."
|
||
curl -fsSL "${REPO_URL/\.git/}/raw/branch/main/update.sh" -o "$INSTALL_DIR/update.sh" 2>/dev/null \
|
||
|| { warn "update.sh konnte nicht von Gitea geladen werden – überspringe Deployment"; }
|
||
|
||
if [ -f "$INSTALL_DIR/update.sh" ]; then
|
||
chmod +x "$INSTALL_DIR/update.sh"
|
||
log "update.sh installiert: $INSTALL_DIR/update.sh"
|
||
info "Führe erstes Deployment durch (Build + Start)..."
|
||
bash "$INSTALL_DIR/update.sh"
|
||
else
|
||
warn "update.sh fehlt — manuell ausführen: bash $INSTALL_DIR/update.sh"
|
||
fi
|
||
|
||
# ── Abschlussbericht ──────────────────────────────────────────────────────────
|
||
echo ""
|
||
echo " ╔══════════════════════════════════════════════════════════╗"
|
||
echo " ║ Installation abgeschlossen! ║"
|
||
echo " ╚══════════════════════════════════════════════════════════╝"
|
||
echo ""
|
||
echo " Hostname (FQDN): $FQDN"
|
||
echo ""
|
||
echo " Pfade:"
|
||
echo " Binaries: $INSTALL_DIR/"
|
||
echo " Mail-Speicher: $STORE_DIR/"
|
||
echo " Konfiguration: $CONFIG_DIR/config.yml"
|
||
echo " Keyfile: $CONFIG_DIR/keyfile (chmod 400 – sicher aufbewahren!)"
|
||
echo " TLS-Zertifikat: $SSL_DIR/archivmail.crt"
|
||
echo " Logs: $LOG_DIR/"
|
||
echo ""
|
||
echo " Datenbank:"
|
||
echo " Host: 127.0.0.1:5432 / archivmail"
|
||
printf " Passwort: %s\n" "$DB_PASSWORD"
|
||
echo ""
|
||
echo " Standard-Zugangsdaten (nach Deployment ändern!):"
|
||
echo " Admin: admin@archivmail / archivmailrockz"
|
||
echo " Auditor: auditor@archivmail / archivmailrockz"
|
||
echo ""
|
||
echo " Dienste:"
|
||
echo " Web (HTTPS): https://$FQDN"
|
||
echo " IMAP (TLS): $FQDN:993"
|
||
echo " SMTP: $FQDN:2525"
|
||
echo ""
|
||
echo " Hinweis: Das TLS-Zertifikat ist selbstsigniert."
|
||
echo " Zertifikat importieren oder im Browser-Ausnahme hinzufügen."
|
||
echo " Für ein vertrauenswürdiges Zertifikat: Admin → Zertifikat → Let's Encrypt"
|
||
echo ""
|
||
warn "DB-Passwort steht in $CONFIG_DIR/config.yml (chmod 640, root:archivmail)"
|
||
warn "Standardpasswörter unbedingt nach dem ersten Login ändern!"
|
||
echo ""
|