Files
malware-watch/install.sh
T
2026-09-03 11:22:56 +09:00

313 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${ENV_FILE:-$SCRIPT_DIR/.env}"
SRC_DIR="$SCRIPT_DIR/src"
log(){ printf '[malware-watch] %s\n' "$*"; }
die(){ printf '[malware-watch] ERROR: %s\n' "$*" >&2; exit 1; }
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
exec sudo -E bash "$0" "$@"
fi
[[ -r "$ENV_FILE" ]] || die "Missing $ENV_FILE"
set -a
. "$ENV_FILE"
set +a
: "${DISCORD_WEBHOOK:?DISCORD_WEBHOOK is required in .env}"
: "${ABUSECH_AUTH_KEY:?ABUSECH_AUTH_KEY is required in .env}"
[[ "$DISCORD_WEBHOOK" != "디스코드웹훅URL" ]] || die "Replace DISCORD_WEBHOOK placeholder in .env"
[[ "$ABUSECH_AUTH_KEY" != "abuse.ch api 키" ]] || die "Replace ABUSECH_AUTH_KEY placeholder in .env"
CPU_THRESHOLD="${CPU_THRESHOLD:-150}"
ALERT_SCORE="${ALERT_SCORE:-60}"
CRITICAL_SCORE="${CRITICAL_SCORE:-100}"
COOLDOWN_SECONDS="${COOLDOWN_SECONDS:-1800}"
CLAMAV_VERSION="${CLAMAV_VERSION:-1.4.6}"
CLAMAV_URL="${CLAMAV_URL:-}"
INSTALL_CLAMAV="${INSTALL_CLAMAV:-1}"
RUN_FRESHCLAM_NOW="${RUN_FRESHCLAM_NOW:-0}"
SEND_INSTALL_TEST="${SEND_INSTALL_TEST:-0}"
case "$DISCORD_WEBHOOK$ABUSECH_AUTH_KEY" in
*"'"*) die "Secrets containing a single quote are not supported by this installer" ;;
esac
command -v systemctl >/dev/null || die "systemd is required"
command -v apt-get >/dev/null || die "This installer currently supports Debian/Ubuntu (apt)"
for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do
[[ -f "$SRC_DIR/$f" ]] || die "Missing $SRC_DIR/$f"
done
log "Installing base dependencies"
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates curl python3 iproute2 procps util-linux iptables coreutils passwd
install -d -m 700 /etc/malware-watch /var/lib/malware-watch /var/lib/malware-watch/feeds
install -d -m 700 /root/security-evidence/malware-watch /root/security-evidence/quarantine
BACKUP="/root/security-evidence/malware-watch/install-backup-$(date +%Y%m%d-%H%M%S)"
install -d -m 700 "$BACKUP"
for f in \
/etc/malware-watch/config \
/usr/local/sbin/malware-watch.py \
/usr/local/sbin/malware-quarantine.py \
/usr/local/sbin/malware-feed-update.py \
/usr/local/sbin/clamav-watch-scan.sh \
/usr/local/sbin/block-malware-iocs; do
[[ -e "$f" ]] && cp -a "$f" "$BACKUP/" || true
done
umask 077
{
printf "CPU_THRESHOLD='%s'\n" "$CPU_THRESHOLD"
printf "ALERT_SCORE='%s'\n" "$ALERT_SCORE"
printf "CRITICAL_SCORE='%s'\n" "$CRITICAL_SCORE"
printf "COOLDOWN_SECONDS='%s'\n" "$COOLDOWN_SECONDS"
printf "ABUSECH_AUTH_KEY='%s'\n" "$ABUSECH_AUTH_KEY"
printf "DISCORD_WEBHOOK='%s'\n" "$DISCORD_WEBHOOK"
} > /etc/malware-watch/config
chmod 600 /etc/malware-watch/config
for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do
install -o root -g root -m 700 "$SRC_DIR/$f" "/usr/local/sbin/$f"
done
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
arch="$(uname -m)"
case "$arch" in
aarch64|arm64) clam_arch="aarch64" ;;
x86_64|amd64) clam_arch="x86_64" ;;
*) die "Unsupported ClamAV architecture: $arch" ;;
esac
if ! /usr/local/bin/clamscan --version 2>/dev/null | grep -q "ClamAV $CLAMAV_VERSION"; then
log "Installing official ClamAV $CLAMAV_VERSION for $clam_arch"
deb="$(mktemp /tmp/clamav.XXXXXX.deb)"
url="${CLAMAV_URL:-https://www.clamav.net/downloads/production/clamav-${CLAMAV_VERSION}.linux.${clam_arch}.deb}"
curl -fL --retry 3 --retry-delay 2 -o "$deb" "$url"
systemctl disable --now clamav-freshclam 2>/dev/null || true
DEBIAN_FRONTEND=noninteractive apt-get remove -y \
clamav clamav-freshclam clamav-base libclamav9 libclamav11 2>/dev/null || true
DEBIAN_FRONTEND=noninteractive apt-get install -y "$deb"
rm -f "$deb"
fi
[[ -x /usr/local/bin/clamscan ]] || die "Official ClamAV clamscan not found after installation"
[[ -f /usr/local/etc/freshclam.conf.sample ]] || die "freshclam.conf.sample not found"
if ! getent group clamav >/dev/null 2>&1; then
groupadd --system clamav
fi
if ! id -u clamav >/dev/null 2>&1; then
useradd --system --gid clamav --home-dir /var/lib/clamav --shell /usr/sbin/nologin clamav
fi
install -d -o clamav -g clamav -m 755 /var/lib/clamav
cp /usr/local/etc/freshclam.conf.sample /usr/local/etc/freshclam.conf
sed -i 's/^Example/#Example/' /usr/local/etc/freshclam.conf
cat >> /usr/local/etc/freshclam.conf <<'EOF_FRESH'
DatabaseDirectory /var/lib/clamav
DatabaseOwner clamav
Checks 12
EOF_FRESH
chmod 644 /usr/local/etc/freshclam.conf
fi
cat > /etc/systemd/system/malware-watch.service <<'EOF_UNIT'
[Unit]
Description=Malware Watch realtime detector
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/malware-watch.py
User=root
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
EOF_UNIT
cat > /etc/systemd/system/malware-watch.timer <<'EOF_UNIT'
[Unit]
Description=Run Malware Watch every minute
[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
AccuracySec=5s
Persistent=true
[Install]
WantedBy=timers.target
EOF_UNIT
cat > /etc/systemd/system/malware-feed-update.service <<'EOF_UNIT'
[Unit]
Description=Malware Watch abuse.ch feed updater
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/malware-watch/config
ExecStart=/usr/local/sbin/malware-feed-update.py
User=root
EOF_UNIT
cat > /etc/systemd/system/malware-feed-update.timer <<'EOF_UNIT'
[Unit]
Description=Update malware threat feeds every 6 hours
[Timer]
OnBootSec=5min
OnUnitActiveSec=6h
RandomizedDelaySec=5min
Persistent=true
[Install]
WantedBy=timers.target
EOF_UNIT
cat > /etc/systemd/system/block-malware-iocs.service <<'EOF_UNIT'
[Unit]
Description=Restore malware IOC firewall blocks
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/block-malware-iocs
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF_UNIT
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
cat > /etc/systemd/system/clamav-freshclam-local.service <<EOF_UNIT
[Unit]
Description=ClamAV ${CLAMAV_VERSION} signature database update
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/freshclam --config-file=/usr/local/etc/freshclam.conf
ExecStartPost=-/bin/systemctl start clamav-risk-scan.service
User=root
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
EOF_UNIT
cat > /etc/systemd/system/clamav-freshclam-local.timer <<'EOF_UNIT'
[Unit]
Description=Update ClamAV signatures every 2 hours
[Timer]
OnBootSec=15min
OnUnitActiveSec=2h
RandomizedDelaySec=5min
Persistent=true
[Install]
WantedBy=timers.target
EOF_UNIT
cat > /etc/systemd/system/clamav-risk-scan.service <<'EOF_UNIT'
[Unit]
Description=ClamAV high-risk path scan
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/clamav-watch-scan.sh quick
User=root
Nice=15
IOSchedulingClass=idle
SuccessExitStatus=1
EOF_UNIT
cat > /etc/systemd/system/clamav-risk-scan.timer <<'EOF_UNIT'
[Unit]
Description=Hourly ClamAV scan of high-risk temporary paths
[Timer]
OnBootSec=20min
OnUnitActiveSec=1h
RandomizedDelaySec=5min
Persistent=true
[Install]
WantedBy=timers.target
EOF_UNIT
cat > /etc/systemd/system/clamav-deep-scan.service <<'EOF_UNIT'
[Unit]
Description=ClamAV daily low-priority server scan
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/clamav-watch-scan.sh deep
User=root
Nice=19
IOSchedulingClass=idle
SuccessExitStatus=1
EOF_UNIT
cat > /etc/systemd/system/clamav-deep-scan.timer <<'EOF_UNIT'
[Unit]
Description=Daily ClamAV low-priority server scan
[Timer]
OnCalendar=*-*-* 04:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
EOF_UNIT
fi
systemctl daemon-reload
systemctl enable --now malware-watch.timer malware-feed-update.timer block-malware-iocs.service
if [[ "$INSTALL_CLAMAV" == "1" ]]; then
systemctl enable --now clamav-freshclam-local.timer clamav-risk-scan.timer clamav-deep-scan.timer
fi
log "Updating abuse.ch feeds"
if ! systemctl start malware-feed-update.service; then
log "WARNING: abuse.ch feed update failed; timer will retry"
fi
if [[ "$INSTALL_CLAMAV" == "1" && "$RUN_FRESHCLAM_NOW" == "1" ]]; then
log "Trying initial ClamAV signature update"
systemctl start clamav-freshclam-local.service || log "WARNING: freshclam failed; timer will retry"
fi
systemctl start malware-watch.service || true
if [[ "$SEND_INSTALL_TEST" == "1" ]]; then
payload="$(python3 - <<'PYMSG'
import json,socket
print(json.dumps({'content':f'malware-watch install OK | server: {socket.gethostname()}'},ensure_ascii=False))
PYMSG
)"
curl -sS -o /dev/null -A 'Mozilla/5.0 (compatible; malware-watch/2.0)' \
-H 'Content-Type: application/json' --data "$payload" "$DISCORD_WEBHOOK" || true
fi
log "Installation complete"
/usr/local/sbin/malware-watch.py || true
if [[ -x "$SCRIPT_DIR/verify.sh" ]]; then
"$SCRIPT_DIR/verify.sh" || true
fi