mirror of
https://github.com/deunix-educ/Fail2banMqttActionBanishment.git
synced 2026-08-24 03:11:58 +02:00
852 lines
39 KiB
Bash
Executable File
852 lines
39 KiB
Bash
Executable File
#!/bin/bash
|
|
# install.sh — déploiement idempotent de Fail2banActionBanishment
|
|
# Cible : VPS Debian 13. Usage : sudo ./install.sh {install|install-master|install-mariadb|join|status}
|
|
#
|
|
# Modèle de déploiement (2026-07-16) : PAS de copie vers /opt. Le dépôt git
|
|
# cloné dans le $HOME d'un utilisateur dédié (ex. /home/banisher/...) EST le
|
|
# déploiement — le venv, la base SQLite, staticfiles/ et .env vivent tous
|
|
# dans ce checkout, possédés par cet utilisateur. Ce script doit donc être
|
|
# lancé depuis un checkout déjà possédé par l'utilisateur de déploiement
|
|
# souhaité (jamais root) ; l'utilisateur est déterminé automatiquement via le
|
|
# propriétaire du dépôt, pas codé en dur.
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ENV_FILE="$REPO_DIR/.env"
|
|
APP_DIR="$REPO_DIR/emitter"
|
|
|
|
DEPLOY_USER=""
|
|
DEPLOY_GROUP=""
|
|
|
|
require_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Ce script doit être exécuté en root (sudo ./install.sh install)." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Détermine l'utilisateur de déploiement à partir du propriétaire du dépôt
|
|
# lui-même (pas de compte système dédié séparé, cf. note en tête de fichier).
|
|
resolve_deploy_user() {
|
|
DEPLOY_USER="$(stat -c '%U' "$REPO_DIR")"
|
|
if [ "$DEPLOY_USER" = "root" ]; then
|
|
echo "ERREUR : $REPO_DIR appartient à root." >&2
|
|
echo " Cloner/chown ce dépôt vers un utilisateur non-root dédié" >&2
|
|
echo " (ex. 'banisher') avant de lancer install.sh." >&2
|
|
exit 1
|
|
fi
|
|
DEPLOY_GROUP="$(id -gn "$DEPLOY_USER")"
|
|
}
|
|
|
|
# Ajoute/retire une variable dans un fichier .env de façon idempotente.
|
|
upsert_env_var() {
|
|
local key="$1" value="$2" file="$3"
|
|
if grep -q "^${key}=" "$file"; then
|
|
sed -i "s#^${key}=.*#${key}=${value}#" "$file"
|
|
else
|
|
echo "${key}=${value}" >> "$file"
|
|
fi
|
|
}
|
|
|
|
install_packages() {
|
|
echo " [Paquets]"
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends \
|
|
fail2ban iptables mosquitto mosquitto-clients python3-paho-mqtt \
|
|
redis-server python3-venv rsync
|
|
}
|
|
|
|
# coturn : le logpath réel dépend de la config coturn locale (log-file=
|
|
# dans /etc/turnserver.conf), jamais une valeur codée en dur partagée entre
|
|
# noeuds (piège déjà vécu : /var/log/coturn/turnserver.log sur un VPS,
|
|
# /var/log/turnserver.log sur un autre, même paquet Debian coturn). Coturn
|
|
# absent/mal configuré sur ce noeud => jail désactivée plutôt qu'un chemin
|
|
# invalide qui ferait planter fail2ban au démarrage ("Have not found any
|
|
# log file for coturn jail").
|
|
deploy_coturn_jail() {
|
|
local logpath=""
|
|
if [ -f /etc/turnserver.conf ]; then
|
|
logpath="$(grep -oP '^log-file=\K.*' /etc/turnserver.conf | head -1)"
|
|
fi
|
|
if [ -n "$logpath" ]; then
|
|
# Sans la directive "simple-log", coturn insère un suffixe (date)
|
|
# avant l'extension dans le nom de fichier réel — le chemin exact
|
|
# de "log-file=" ne correspond alors à aucun fichier qui existe
|
|
# vraiment, faisant planter fail2ban au démarrage ("Have not found
|
|
# any log file for coturn jail"), même après la correction du
|
|
# chemin lui-même (vécu sur linuxtarn-vps1, corrigé à la main avant
|
|
# d'être généralisé ici). Un glob (turnserver*.log au lieu de
|
|
# turnserver.log) couvre les deux cas : avec simple-log, "*" ne
|
|
# matche que la chaîne vide et retombe sur le nom exact.
|
|
local ext="${logpath##*.}"
|
|
local base="${logpath%.*}"
|
|
logpath="${base}*.${ext}"
|
|
echo " -> jail coturn : logpath=$logpath (lu depuis /etc/turnserver.conf)"
|
|
sed "s#__COTURN_LOGPATH__#$logpath#g" \
|
|
"$REPO_DIR/generic/fail2ban/jail.d/coturn.conf" > /etc/fail2ban/jail.d/coturn.conf
|
|
else
|
|
echo " -> coturn absent ou log-file non configuré : jail coturn désactivée"
|
|
sed -e "s#__COTURN_LOGPATH__#/var/log/coturn/turnserver.log#g" \
|
|
-e "s/^enabled[[:space:]]*=[[:space:]]*true/enabled = false/" \
|
|
"$REPO_DIR/generic/fail2ban/jail.d/coturn.conf" > /etc/fail2ban/jail.d/coturn.conf
|
|
fi
|
|
}
|
|
|
|
# logpath dépend du checkout ($APP_DIR/logs/admin-auth.log, cf.
|
|
# emitter/config/settings/base.py LOG_DIR) donc du $HOME de l'utilisateur
|
|
# de déploiement — jamais une valeur en dur partagée entre noeuds (même
|
|
# raison que deploy_coturn_jail ci-dessus).
|
|
deploy_emitter_admin_auth_jail() {
|
|
sed "s#__EMITTER_ADMIN_AUTH_LOGPATH__#$APP_DIR/logs/admin-auth.log#g" \
|
|
"$REPO_DIR/generic/fail2ban/jail.d/emitter-admin-auth.conf" > /etc/fail2ban/jail.d/emitter-admin-auth.conf
|
|
}
|
|
|
|
# OpenVPN, comme coturn ci-dessus : service tiers optionnel, pas propre à
|
|
# ce projet. Sur un noeud sans OpenVPN (cas courant, ex. noeud derrière
|
|
# une box résidentielle), les deux logpath codés en dur n'existent pas et
|
|
# fail2ban refuse de charger la jail au démarrage ("Have not found any
|
|
# log file for openvpn jail") — même piège que coturn, désactiver plutôt
|
|
# que planter.
|
|
deploy_openvpn_jail() {
|
|
if [ -f /var/log/openvpn/openvpn.log ] || [ -f /var/log/openvpn.log ]; then
|
|
echo " -> jail openvpn : log détecté, activée"
|
|
cp "$REPO_DIR/generic/fail2ban/jail.d/openvpn.conf" /etc/fail2ban/jail.d/openvpn.conf
|
|
else
|
|
echo " -> OpenVPN absent : jail openvpn désactivée"
|
|
sed "s/^enabled[[:space:]]*=[[:space:]]*true/enabled = false/" \
|
|
"$REPO_DIR/generic/fail2ban/jail.d/openvpn.conf" > /etc/fail2ban/jail.d/openvpn.conf
|
|
fi
|
|
}
|
|
|
|
deploy_fail2ban() {
|
|
echo " [fail2ban]"
|
|
install -d /etc/fail2ban/action.d /etc/fail2ban/filter.d /etc/fail2ban/jail.d
|
|
|
|
cp "$REPO_DIR/generic/fail2ban/jail.local" /etc/fail2ban/jail.local
|
|
cp "$REPO_DIR/generic/fail2ban/fail2ban.local" /etc/fail2ban/fail2ban.local
|
|
for f in "$REPO_DIR"/generic/fail2ban/jail.d/*.conf; do
|
|
case "$(basename "$f")" in
|
|
coturn.conf|openvpn.conf|emitter-admin-auth.conf) continue ;;
|
|
esac
|
|
cp "$f" /etc/fail2ban/jail.d/
|
|
done
|
|
deploy_coturn_jail
|
|
deploy_openvpn_jail
|
|
deploy_emitter_admin_auth_jail
|
|
cp "$REPO_DIR"/generic/fail2ban/filter.d/*.conf /etc/fail2ban/filter.d/
|
|
cp "$REPO_DIR"/generic/fail2ban/action.d/*.conf /etc/fail2ban/action.d/
|
|
cp "$REPO_DIR/generic/fail2ban/action.d/f2b_mqtt_action_banisher.py" /etc/fail2ban/action.d/
|
|
chmod +x /etc/fail2ban/action.d/f2b_mqtt_action_banisher.py
|
|
|
|
if [ ! -f /etc/fail2ban/mqtt.conf ]; then
|
|
echo " -> /etc/fail2ban/mqtt.conf absent : copie du modèle (à éditer avec les vrais identifiants)"
|
|
cp "$REPO_DIR/generic/fail2ban/mqtt.conf.example" /etc/fail2ban/mqtt.conf
|
|
chmod 600 /etc/fail2ban/mqtt.conf
|
|
fi
|
|
}
|
|
|
|
deploy_firewall() {
|
|
echo " [firewall]"
|
|
install -d /etc/firewall
|
|
|
|
# firewall-filters.conf : contrairement à firewall-launcher.sh/.service
|
|
# (génériques, ré-écrits à chaque déploiement), ce fichier est propre à
|
|
# CE noeud (services réellement exposés, SSH_PORT, etc. — voir
|
|
# docs/installation.md). Ne jamais l'écraser une fois personnalisé, au
|
|
# même titre que .env (deploy_env ci-dessus) : sinon un simple
|
|
# `sudo make install` après un `git pull` effacerait silencieusement
|
|
# l'adaptation faite pour ce noeud et réintroduirait des règles
|
|
# (VPN/coturn/SSH) qui ne lui correspondent pas.
|
|
if [ ! -f /etc/firewall/firewall-filters.conf ]; then
|
|
echo " -> firewall-filters.conf absent : copié depuis le modèle générique (À ADAPTER à ce noeud)"
|
|
cp "$REPO_DIR/generic/firewall/firewall-filters.conf" /etc/firewall/firewall-filters.conf
|
|
fi
|
|
cp "$REPO_DIR/generic/firewall/firewall-launcher.sh" /etc/firewall/firewall-launcher.sh
|
|
chmod +x /etc/firewall/firewall-launcher.sh
|
|
sed "s#__ENV_FILE__#$ENV_FILE#g" \
|
|
"$REPO_DIR/generic/firewall/firewall.service" > /etc/systemd/system/firewall.service
|
|
}
|
|
|
|
deploy_mosquitto() {
|
|
echo " [mosquitto]"
|
|
install -d /etc/mosquitto/conf.d /etc/systemd/system/mosquitto.service.d
|
|
|
|
# Notre drop-in ci-dessous ajoute ProtectSystem=strict + ReadWritePaths
|
|
# sur /var/log/mosquitto et /var/lib/mosquitto : ces chemins doivent
|
|
# déjà exister AVANT que systemd ne construise le mount namespace de
|
|
# l'unité (partagé par tous ses Exec*, y compris le tout premier
|
|
# ExecStartPre du paquet Debian, `mkdir -p /var/log/mosquitto`) — sinon
|
|
# ce mkdir lui-même échoue avec "226/NAMESPACE" (bind-mount impossible
|
|
# d'un chemin qui n'existe pas encore). On les crée donc nous-mêmes ici,
|
|
# avant toute tentative de (re)démarrage du service.
|
|
install -d -o mosquitto -g mosquitto -m 750 /var/log/mosquitto /var/lib/mosquitto
|
|
|
|
# MQTT_VPN_LISTENER_IP est propre à CE VPS (chaque noeud a sa propre IP
|
|
# VPN, ex. tun0) — jamais une valeur codée en dur partagée entre noeuds
|
|
# (piège déjà vécu : un listener qui refuse de démarrer avec "Cannot
|
|
# assign requested address" sur tout VPS différent de celui où l'IP a
|
|
# été relevée). Lu depuis .env, substitué dans __VPN_LISTENER_BLOCK__ ;
|
|
# absent/vide => pas de second listener, loopback seul.
|
|
local vpn_ip vpn_block
|
|
vpn_ip="$(grep '^MQTT_VPN_LISTENER_IP=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-)"
|
|
if [ -n "$vpn_ip" ]; then
|
|
echo " -> second listener VPN sur $vpn_ip (MQTT_VPN_LISTENER_IP dans .env)"
|
|
vpn_block="listener 1883 $vpn_ip
|
|
protocol mqtt
|
|
allow_anonymous false
|
|
password_file /etc/mosquitto/passwd_local"
|
|
else
|
|
echo " -> MQTT_VPN_LISTENER_IP absent/vide dans .env : pas de second listener (loopback seul)"
|
|
vpn_block="# MQTT_VPN_LISTENER_IP absent/vide dans .env : pas de second listener."
|
|
fi
|
|
awk -v block="$vpn_block" '{gsub(/__VPN_LISTENER_BLOCK__/, block)}1' \
|
|
"$REPO_DIR/generic/mosquitto/mosquitto-local.conf" > /etc/mosquitto/conf.d/mosquitto-local.conf
|
|
cp "$REPO_DIR/generic/mosquitto/mosquitto.service.d/override.conf" /etc/systemd/system/mosquitto.service.d/override.conf
|
|
|
|
# Mots de passe repris tels quels des fichiers qui font déjà foi
|
|
# ailleurs (jamais re-demandés interactivement) : sinon rien ne garantit
|
|
# que la valeur tapée ici corresponde à celle que lit réellement le
|
|
# client MQTT concerné — piège vécu : "Connection Refused: not
|
|
# authorised" faute de correspondance entre un mot de passe saisi à la
|
|
# volée dans ce prompt et celui laissé dans mqtt.conf/.env.
|
|
if [ ! -f /etc/mosquitto/passwd_local ]; then
|
|
local fail2ban_mqtt_password
|
|
fail2ban_mqtt_password="$(grep '^password' /etc/fail2ban/mqtt.conf | head -1 | cut -d= -f2- | xargs)"
|
|
echo " -> compte MQTT 'fail2ban' absent : création depuis /etc/fail2ban/mqtt.conf"
|
|
mosquitto_passwd -b -c /etc/mosquitto/passwd_local fail2ban "$fail2ban_mqtt_password"
|
|
fi
|
|
if ! grep -q '^emitter:' /etc/mosquitto/passwd_local; then
|
|
local emitter_mqtt_password
|
|
emitter_mqtt_password="$(grep '^MQTT_BROKER_PASSWORD=' "$ENV_FILE" | cut -d= -f2-)"
|
|
echo " -> compte MQTT 'emitter' absent : création depuis MQTT_BROKER_PASSWORD (.env)"
|
|
mosquitto_passwd -b /etc/mosquitto/passwd_local emitter "$emitter_mqtt_password"
|
|
fi
|
|
chown mosquitto:mosquitto /etc/mosquitto/passwd_local
|
|
chmod 640 /etc/mosquitto/passwd_local
|
|
}
|
|
|
|
deploy_master_broker() {
|
|
echo " [broker master (Phase 3)]"
|
|
local domain="${MQTT_MASTER_DOMAIN:-mqtt.linuxtarn.org}"
|
|
local ca_dir="/etc/mosquitto/master-ca"
|
|
|
|
if [ ! -f "/etc/letsencrypt/live/$domain/fullchain.pem" ]; then
|
|
echo " -> ERREUR : certificat Let's Encrypt introuvable pour $domain" >&2
|
|
echo " (attendu : /etc/letsencrypt/live/$domain/fullchain.pem)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$ca_dir/ca.key" ]; then
|
|
echo " -> CA privée absente : initialisation ($ca_dir)"
|
|
"$REPO_DIR/scripts/master-ca-init.sh" "$ca_dir"
|
|
fi
|
|
|
|
sed "s#__MQTT_MASTER_DOMAIN__#$domain#g" "$REPO_DIR/generic/mosquitto/mosquitto-master.conf" > /etc/mosquitto/conf.d/mosquitto-master.conf
|
|
|
|
cp "$REPO_DIR/generic/mosquitto/master-acl.conf" /etc/mosquitto/master-acl.conf
|
|
# mosquitto refuse (ou avertit, selon version) de charger un acl_file dont
|
|
# le propriétaire n'est pas mosquitto ou qui est lisible par d'autres.
|
|
chown mosquitto:mosquitto /etc/mosquitto/master-acl.conf
|
|
chmod 0700 /etc/mosquitto/master-acl.conf
|
|
|
|
# /etc/letsencrypt est verrouillé root-only par certbot (le dossier
|
|
# lui-même, pas seulement privkey.pem) : mosquitto (utilisateur système
|
|
# dédié, pas root) ne peut jamais lire les fichiers à leur emplacement
|
|
# d'origine. Le deploy-hook copie vers un répertoire dédié, group-lisible
|
|
# pour mosquitto (et redémarre mosquitto, son rôle normal déclenché par
|
|
# certbot à chaque renouvellement) ; installé ici pour les
|
|
# renouvellements futurs, et lancé une fois tout de suite pour peupler
|
|
# /etc/mosquitto/master-tls/. Appelé avec || true : en cas d'échec (par
|
|
# ex. mosquitto pas encore capable de redémarrer à ce stade), on veut
|
|
# quand même atteindre les vérifications ci-dessous avant d'abandonner,
|
|
# plutôt qu'un arrêt sec sans explication.
|
|
install -d /etc/letsencrypt/renewal-hooks/deploy
|
|
sed "s#__MQTT_MASTER_DOMAIN__#$domain#g" "$REPO_DIR/generic/mosquitto/certbot-deploy-mosquitto.sh" > /etc/letsencrypt/renewal-hooks/deploy/mosquitto-master.sh
|
|
chmod +x /etc/letsencrypt/renewal-hooks/deploy/mosquitto-master.sh
|
|
/etc/letsencrypt/renewal-hooks/deploy/mosquitto-master.sh || true
|
|
|
|
# Vérification : le process mosquitto tourne sous l'utilisateur
|
|
# 'mosquitto', pas root — s'assurer qu'il peut vraiment lire ce dont il a
|
|
# besoin plutôt que de découvrir un échec silencieux au redémarrage.
|
|
echo " -> vérification des permissions (utilisateur mosquitto)"
|
|
local perms_ok=true
|
|
for f in "$ca_dir/ca.crt" \
|
|
/etc/mosquitto/master-tls/fullchain.pem \
|
|
/etc/mosquitto/master-tls/privkey.pem; do
|
|
if runuser -u mosquitto -- test -r "$f"; then
|
|
echo " OK : $f"
|
|
else
|
|
echo " ECHEC: mosquitto ne peut pas lire $f" >&2
|
|
perms_ok=false
|
|
fi
|
|
done
|
|
if [ "$perms_ok" != true ]; then
|
|
echo " -> permissions incomplètes, le broker ne démarrera probablement pas (voir ci-dessus)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
systemctl daemon-reload
|
|
systemctl restart mosquitto.service
|
|
|
|
echo " -> pour émettre un certificat client de noeud :"
|
|
echo " sudo $REPO_DIR/scripts/generate-node-cert.sh <nom_du_noeud>"
|
|
|
|
# Ingestion côté master (banevents.master_listen, Phase 3 minimal) :
|
|
# nécessite que `./install.sh install` ait déjà tourné au moins une fois
|
|
# (venv de l'émetteur provisionné dans $APP_DIR).
|
|
if [ ! -d "$APP_DIR/.venv" ]; then
|
|
echo " -> émetteur Django absent : lancer 'sudo ./install.sh install' avant" >&2
|
|
echo " pour activer l'ingestion master (master_listen)." >&2
|
|
return 0
|
|
fi
|
|
|
|
local listener_tls_dir="$APP_DIR/master-tls"
|
|
if [ ! -f "$listener_tls_dir/listener.crt" ]; then
|
|
echo " -> certificat d'ingestion master-internal absent : génération"
|
|
local tmp_cert_dir
|
|
tmp_cert_dir="$(mktemp -d)"
|
|
"$REPO_DIR/scripts/generate-node-cert.sh" master-internal "$tmp_cert_dir"
|
|
install -d "$listener_tls_dir"
|
|
cp "$tmp_cert_dir/master-internal.crt" "$listener_tls_dir/listener.crt"
|
|
cp "$tmp_cert_dir/master-internal.key" "$listener_tls_dir/listener.key"
|
|
rm -rf "$tmp_cert_dir"
|
|
chown -R "$DEPLOY_USER:$DEPLOY_GROUP" "$listener_tls_dir"
|
|
chmod 600 "$listener_tls_dir/listener.key"
|
|
fi
|
|
|
|
sed "s#__EMITTER_DIR__#$APP_DIR#g; s#__ENV_FILE__#$ENV_FILE#g; s#__EMITTER_USER__#$DEPLOY_USER#g; s#__EMITTER_GROUP__#$DEPLOY_GROUP#g" \
|
|
"$REPO_DIR/generic/emitter/fail2ban-emitter-master-listen.service" > /etc/systemd/system/fail2ban-emitter-master-listen.service
|
|
systemctl daemon-reload
|
|
systemctl enable fail2ban-emitter-master-listen.service
|
|
systemctl restart fail2ban-emitter-master-listen.service
|
|
}
|
|
|
|
# sudoers NOPASSWD:ALL pour l'utilisateur de déploiement (banisher ou
|
|
# équivalent) : demandé explicitement pour ce modèle "tout depuis le $HOME"
|
|
# (déploiement + services tournent sous le même compte, qui doit pouvoir
|
|
# reconfigurer systemd/fail2ban/mosquitto/iptables sans mot de passe). Note :
|
|
# ceci remplace le sudoers restreint à une seule jail utilisé auparavant pour
|
|
# SYNC_BAN (fail2ban-client) — ce compte a désormais accès root complet, à
|
|
# n'utiliser que sur un VPS dédié à ce projet.
|
|
deploy_sudoers() {
|
|
echo " [sudoers]"
|
|
local sudoers_file="/etc/sudoers.d/${DEPLOY_USER}-full-access"
|
|
if [ -f "$sudoers_file" ]; then
|
|
echo " -> $sudoers_file déjà présent, inchangé"
|
|
return 0
|
|
fi
|
|
echo " -> création de $sudoers_file (NOPASSWD:ALL pour '$DEPLOY_USER')"
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
echo "$DEPLOY_USER ALL=(ALL:ALL) NOPASSWD:ALL" > "$tmp"
|
|
if visudo -c -f "$tmp" >/dev/null 2>&1; then
|
|
install -o root -g root -m 440 "$tmp" "$sudoers_file"
|
|
else
|
|
echo " -> ERREUR : le fichier sudoers généré est invalide, non installé" >&2
|
|
rm -f "$tmp"
|
|
exit 1
|
|
fi
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
# Prépare .env (copié depuis .env.example à la racine du dépôt s'il est
|
|
# absent, jamais régénéré ensuite pour ne pas écraser des valeurs déjà
|
|
# personnalisées) et tient FAIL2BAN_CLIENT_PATH à jour à chaque exécution.
|
|
deploy_env() {
|
|
echo " [.env]"
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo " -> .env absent : génération depuis .env.example (SECRET_KEY aléatoire, à compléter)"
|
|
cp "$REPO_DIR/.env.example" "$ENV_FILE"
|
|
local secret_key
|
|
secret_key="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')"
|
|
sed -i "s#^DJANGO_SECRET_KEY=.*#DJANGO_SECRET_KEY=$secret_key#" "$ENV_FILE"
|
|
fi
|
|
|
|
local fail2ban_client_path
|
|
fail2ban_client_path="$(command -v fail2ban-client || echo /usr/bin/fail2ban-client)"
|
|
upsert_env_var FAIL2BAN_CLIENT_PATH "$fail2ban_client_path" "$ENV_FILE"
|
|
|
|
# ETH_IF : contrairement à FAIL2BAN_CLIENT_PATH ci-dessus, ajouté une
|
|
# seule fois si absent (jamais réécrit ensuite) — le nom de
|
|
# l'interface WAN ne change pas d'une exécution à l'autre, et un
|
|
# admin a pu le corriger à la main (multi-NIC, nom non standard).
|
|
# Avant ce correctif, firewall-launcher.sh utilisait un défaut codé en
|
|
# dur ("eth0") jamais transmis par le service systemd : sur toute
|
|
# interface nommée différemment (enp1s0, ens18, ...), aucune règle
|
|
# -i $ETH_IF ne matchait jamais le vrai trafic, et tout — y compris
|
|
# HTTP/HTTPS — tombait silencieusement sur le DROP final.
|
|
if ! grep -q '^ETH_IF=' "$ENV_FILE"; then
|
|
local eth_if
|
|
eth_if="$(ip -o route show default 2>/dev/null | awk '{print $5; exit}')"
|
|
eth_if="${eth_if:-eth0}"
|
|
echo " -> ETH_IF absent de .env : détecté automatiquement ($eth_if, interface de la route par défaut) — à corriger ici si erroné (ip -o link show)"
|
|
echo "ETH_IF=$eth_if" >> "$ENV_FILE"
|
|
fi
|
|
|
|
chown "$DEPLOY_USER:$DEPLOY_GROUP" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
}
|
|
|
|
deploy_emitter() {
|
|
echo " [émetteur Django]"
|
|
deploy_env
|
|
|
|
if [ ! -d "$APP_DIR/.venv" ]; then
|
|
echo " -> création du venv Python de l'émetteur"
|
|
runuser -u "$DEPLOY_USER" -- python3 -m venv "$APP_DIR/.venv"
|
|
fi
|
|
runuser -u "$DEPLOY_USER" -- "$APP_DIR/.venv/bin/pip" install --quiet --upgrade pip
|
|
runuser -u "$DEPLOY_USER" -- "$APP_DIR/.venv/bin/pip" install --quiet -r "$APP_DIR/requirements.txt"
|
|
|
|
# logs/admin-auth.log (verrouillages django-axes, cf. AXES_* dans
|
|
# config/settings/base.py) : le FileHandler de Django ne crée pas son
|
|
# dossier parent, et emitter-admin-auth.conf (jail fail2ban) doit
|
|
# trouver un fichier existant dès le démarrage du service — même piège
|
|
# que coturn.conf, résolu ici en touch-créant le fichier vide plutôt
|
|
# qu'en attendant le premier verrouillage réel.
|
|
install -d -o "$DEPLOY_USER" -g "$DEPLOY_GROUP" "$APP_DIR/logs"
|
|
[ -f "$APP_DIR/logs/admin-auth.log" ] || runuser -u "$DEPLOY_USER" -- touch "$APP_DIR/logs/admin-auth.log"
|
|
|
|
# staticfiles/ peut avoir été créé par un autre utilisateur lors d'un
|
|
# débogage manuel passé (ex. collectstatic lancé en root) : s'assurer
|
|
# qu'il appartient au bon compte avant que collectstatic ne tente d'y
|
|
# réécrire, sous peine de PermissionError.
|
|
if [ -d "$APP_DIR/staticfiles" ]; then
|
|
chown -R "$DEPLOY_USER:$DEPLOY_GROUP" "$APP_DIR/staticfiles"
|
|
fi
|
|
|
|
# migrate/collectstatic tournent en tant que $DEPLOY_USER (propriétaire du
|
|
# dépôt) pour que la base SQLite et staticfiles/ lui appartiennent
|
|
# directement. Les variables de .env sont passées explicitement à `env`
|
|
# plutôt que via source+export+runuser : selon les versions, runuser ne
|
|
# propage pas forcément l'environnement du shell appelant.
|
|
mapfile -t emitter_env_vars < <(grep -v '^#' "$ENV_FILE" | grep -v '^\s*$')
|
|
( cd "$APP_DIR" && runuser -u "$DEPLOY_USER" -- env "${emitter_env_vars[@]}" DJANGO_SETTINGS_MODULE=config.settings.production \
|
|
./.venv/bin/python manage.py migrate --noinput )
|
|
( cd "$APP_DIR" && runuser -u "$DEPLOY_USER" -- env "${emitter_env_vars[@]}" DJANGO_SETTINGS_MODULE=config.settings.production \
|
|
./.venv/bin/python manage.py collectstatic --noinput )
|
|
|
|
local sed_placeholders="s#__EMITTER_DIR__#$APP_DIR#g; s#__ENV_FILE__#$ENV_FILE#g; s#__EMITTER_USER__#$DEPLOY_USER#g; s#__EMITTER_GROUP__#$DEPLOY_GROUP#g"
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/emitter/fail2ban-emitter-web.service" > /etc/systemd/system/fail2ban-emitter-web.service
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/emitter/fail2ban-emitter-mqtt.service" > /etc/systemd/system/fail2ban-emitter-mqtt.service
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/emitter/fail2ban-emitter-worker.service" > /etc/systemd/system/fail2ban-emitter-worker.service
|
|
# master_client (relais SYNC_BAN vers/depuis le master) : déployé sur
|
|
# tous les noeuds, pas seulement le master. Ne pourra réellement se
|
|
# connecter qu'une fois un certificat de noeud généré et placé
|
|
# manuellement (scripts/generate-node-cert.sh + MQTT_MASTER_NODE_NAME) —
|
|
# en échouera/retentera (Restart=on-failure) sans bloquer le reste.
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/emitter/fail2ban-emitter-master-client.service" > /etc/systemd/system/fail2ban-emitter-master-client.service
|
|
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/emitter/logrotate-admin-auth.conf" > /etc/logrotate.d/emitter-admin-auth
|
|
}
|
|
|
|
enable_services() {
|
|
echo " [services systemd]"
|
|
systemctl daemon-reload
|
|
# Ordre : redis/mosquitto puis firewall (cf. Before=/PartOf de firewall.service)
|
|
# puis fail2ban, puis les services de l'émetteur (dépendent de mosquitto/redis)
|
|
#
|
|
# mosquitto/firewall/fail2ban : `enable --now` seul ne suffit PAS sur un
|
|
# VPS où le service tournait déjà avant ce déploiement (cas réel : VPS
|
|
# partagé avec d'autres services, fail2ban actif depuis des semaines) —
|
|
# `--now` sur un service déjà actif ne relit pas sa config. Or
|
|
# deploy_mosquitto()/deploy_firewall()/deploy_fail2ban() viennent de
|
|
# réécrire leurs fichiers de conf juste avant : il faut un `restart`
|
|
# explicite pour que le changement s'applique, même déjà signalé pour
|
|
# les services de l'émetteur ci-dessous.
|
|
systemctl enable --now redis-server.service
|
|
systemctl enable mosquitto.service
|
|
systemctl restart mosquitto.service
|
|
systemctl enable firewall.service
|
|
systemctl restart firewall.service
|
|
systemctl enable fail2ban.service
|
|
systemctl restart fail2ban.service
|
|
# restart (pas enable --now) : le code de l'émetteur vient d'être
|
|
# resynchronisé, un service déjà actif doit relire la nouvelle version
|
|
systemctl enable fail2ban-emitter-web.service fail2ban-emitter-mqtt.service fail2ban-emitter-worker.service fail2ban-emitter-master-client.service
|
|
systemctl restart fail2ban-emitter-web.service
|
|
systemctl restart fail2ban-emitter-mqtt.service
|
|
systemctl restart fail2ban-emitter-worker.service
|
|
systemctl restart fail2ban-emitter-master-client.service
|
|
}
|
|
|
|
# MariaDB (opt-in, Phase 5) : sqlite reste le défaut, cette sous-commande
|
|
# provisionne un serveur MariaDB local + une base/utilisateur dédiés et
|
|
# bascule .env en conséquence. Nécessite qu'un premier 'install' ait déjà
|
|
# tourné (venv + .env présents).
|
|
deploy_mariadb() {
|
|
echo " [MariaDB (opt-in)]"
|
|
if [ ! -f "$ENV_FILE" ] || [ ! -d "$APP_DIR/.venv" ]; then
|
|
echo " -> ERREUR : lancer 'sudo ./install.sh install' avant install-mariadb" >&2
|
|
exit 1
|
|
fi
|
|
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends mariadb-server mariadb-client
|
|
systemctl enable --now mariadb.service
|
|
|
|
local db_name db_user db_password
|
|
db_name="$(grep '^DB_NAME=' "$ENV_FILE" | cut -d= -f2-)"
|
|
db_user="$(grep '^DB_USER=' "$ENV_FILE" | cut -d= -f2-)"
|
|
db_password="$(grep '^DB_PASSWORD=' "$ENV_FILE" | cut -d= -f2-)"
|
|
[ -n "$db_name" ] || db_name="fail2ban_emitter"
|
|
[ -n "$db_user" ] || db_user="fail2ban_emitter"
|
|
if [ -z "$db_password" ] || [ "$db_password" = "changeme" ]; then
|
|
db_password="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
|
echo " -> mot de passe DB_PASSWORD généré aléatoirement"
|
|
fi
|
|
|
|
echo " -> création de la base '$db_name' et de l'utilisateur '$db_user' (idempotent)"
|
|
mariadb -u root <<-SQL
|
|
CREATE DATABASE IF NOT EXISTS \`$db_name\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
CREATE USER IF NOT EXISTS '$db_user'@'127.0.0.1' IDENTIFIED BY '$db_password';
|
|
GRANT ALL PRIVILEGES ON \`$db_name\`.* TO '$db_user'@'127.0.0.1';
|
|
FLUSH PRIVILEGES;
|
|
SQL
|
|
|
|
upsert_env_var DB_ENGINE mariadb "$ENV_FILE"
|
|
upsert_env_var DB_NAME "$db_name" "$ENV_FILE"
|
|
upsert_env_var DB_USER "$db_user" "$ENV_FILE"
|
|
upsert_env_var DB_PASSWORD "$db_password" "$ENV_FILE"
|
|
upsert_env_var DB_HOST 127.0.0.1 "$ENV_FILE"
|
|
upsert_env_var DB_PORT 3306 "$ENV_FILE"
|
|
chown "$DEPLOY_USER:$DEPLOY_GROUP" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
runuser -u "$DEPLOY_USER" -- "$APP_DIR/.venv/bin/pip" install --quiet -r "$APP_DIR/requirements-mariadb.txt"
|
|
|
|
echo " -> DB_ENGINE=mariadb appliqué à .env"
|
|
echo " -> relancer 'sudo ./install.sh install' pour migrer vers MariaDB et redémarrer les services"
|
|
}
|
|
|
|
# Auto-inscription d'un noeud par jeton (flux "kubeadm join", Phase 4/5) —
|
|
# alternative à scripts/generate-node-cert.sh + copie manuelle : la clé
|
|
# privée est générée ICI, sur ce noeud, et n'en sort jamais (seule la CSR,
|
|
# une demande de signature, part vers le master). Jeton émis côté master
|
|
# via 'manage.py create_join_token <nom>' (banevents/views.py::join_node).
|
|
deploy_join() {
|
|
local master="" node_name="" token=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--master) master="$2"; shift 2 ;;
|
|
--node) node_name="$2"; shift 2 ;;
|
|
--token) token="$2"; shift 2 ;;
|
|
*) echo "Option inconnue : $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
if [ -z "$master" ] || [ -z "$node_name" ] || [ -z "$token" ]; then
|
|
echo "Usage: $0 join --master <url> --node <nom> --token <jeton>" >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$ENV_FILE" ] || [ ! -d "$APP_DIR/.venv" ]; then
|
|
echo "ERREUR : lancer 'sudo ./install.sh install' avant 'install.sh join'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo " [auto-inscription (join) auprès de $master]"
|
|
local tls_dir="$APP_DIR/master-tls"
|
|
install -d "$tls_dir"
|
|
|
|
local tmp_dir
|
|
tmp_dir="$(mktemp -d)"
|
|
|
|
# Clé + CSR générées ici (root, comme le reste du script — cohérence
|
|
# avec generate-node-cert.sh/install-master qui font de même) ; seule
|
|
# la CSR quitte cette machine, jamais node.key.
|
|
openssl genrsa -out "$tmp_dir/node.key" 2048 2>/dev/null
|
|
openssl req -new -key "$tmp_dir/node.key" -subj "/CN=$node_name" -out "$tmp_dir/node.csr"
|
|
|
|
local payload response http_code
|
|
payload="$(python3 -c '
|
|
import json, sys
|
|
node_name, token, csr = sys.argv[1], sys.argv[2], open(sys.argv[3]).read()
|
|
print(json.dumps({"node_name": node_name, "token": token, "csr": csr}))
|
|
' "$node_name" "$token" "$tmp_dir/node.csr")"
|
|
|
|
response="$(mktemp)"
|
|
http_code="$(curl -sS -o "$response" -w '%{http_code}' -X POST "${master%/}/api/join/" \
|
|
-H 'Content-Type: application/json' -d "$payload")"
|
|
|
|
if [ "$http_code" != "201" ]; then
|
|
echo "ERREUR : échec de l'auto-inscription auprès de $master (HTTP $http_code)" >&2
|
|
cat "$response" >&2
|
|
rm -rf "$tmp_dir" "$response"
|
|
exit 1
|
|
fi
|
|
|
|
python3 -c '
|
|
import json, sys
|
|
data = json.load(open(sys.argv[1]))
|
|
open(sys.argv[2], "w").write(data["cert"])
|
|
open(sys.argv[3], "w").write(data["ca_cert"])
|
|
' "$response" "$tls_dir/node.crt" "$tls_dir/ca.crt"
|
|
|
|
mv "$tmp_dir/node.key" "$tls_dir/node.key"
|
|
rm -rf "$tmp_dir" "$response"
|
|
chown "$DEPLOY_USER:$DEPLOY_GROUP" "$tls_dir/node.key" "$tls_dir/node.crt" "$tls_dir/ca.crt"
|
|
chmod 600 "$tls_dir/node.key"
|
|
|
|
upsert_env_var MQTT_MASTER_NODE_NAME "$node_name" "$ENV_FILE"
|
|
chown "$DEPLOY_USER:$DEPLOY_GROUP" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
echo " -> certificat obtenu pour '$node_name' ($tls_dir)"
|
|
echo " -> relancer 'sudo ./install.sh install' pour l'appliquer."
|
|
}
|
|
|
|
# nginx (opt-in, reverse proxy TLS devant le tableau de bord) : lit
|
|
# DASHBOARD_DOMAIN dans .env, installe nginx/certbot si absents, obtient un
|
|
# certificat Let's Encrypt (webroot) et déploie le gabarit
|
|
# generic/nginx/fail2ban-emitter.example.conf. Nécessite qu'un premier
|
|
# 'install' ait déjà tourné (staticfiles/, .env, services de l'émetteur).
|
|
# Idempotent : relançable pour changer de domaine ou retenter après un échec
|
|
# DNS/ACME.
|
|
deploy_nginx() {
|
|
echo " [nginx (tableau de bord)]"
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo " -> ERREUR : .env absent — lancer 'sudo ./install.sh install' avant" >&2
|
|
exit 1
|
|
fi
|
|
local domain
|
|
domain="$(grep '^DASHBOARD_DOMAIN=' "$ENV_FILE" | cut -d= -f2-)"
|
|
if [ -z "$domain" ]; then
|
|
echo " -> ERREUR : DASHBOARD_DOMAIN absent/vide dans .env" >&2
|
|
echo " Renseigner le domaine public du tableau de bord avant de relancer." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v nginx >/dev/null 2>&1 || ! command -v certbot >/dev/null 2>&1; then
|
|
echo " -> nginx/certbot : installation"
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends nginx certbot
|
|
fi
|
|
install -d /var/www/html
|
|
|
|
if [ ! -f "/etc/letsencrypt/live/$domain/fullchain.pem" ]; then
|
|
echo " -> certificat Let's Encrypt absent pour $domain : vhost ACME minimal puis certbot"
|
|
# Sans vhost dont le server_name correspond à $domain, nginx retombe
|
|
# sur le premier vhost du port 80 pour toute requête non reconnue —
|
|
# s'il s'agit d'un reverse-proxy pour un autre service, le challenge
|
|
# ACME se prend un 502 (vécu en déployant banishing.miraceti.net).
|
|
# Un vhost minimal dédié, juste pour /.well-known/acme-challenge/,
|
|
# évite ça avant même d'avoir un certificat.
|
|
cat > "/etc/nginx/sites-available/$domain" <<EOF_ACME
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name $domain;
|
|
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/html;
|
|
}
|
|
|
|
location / {
|
|
return 444;
|
|
}
|
|
}
|
|
EOF_ACME
|
|
ln -sf "/etc/nginx/sites-available/$domain" "/etc/nginx/sites-enabled/$domain"
|
|
nginx -t
|
|
systemctl reload nginx
|
|
certbot certonly --webroot -w /var/www/html -d "$domain" \
|
|
--non-interactive --agree-tos --keep-until-expiring \
|
|
--register-unsafely-without-email
|
|
fi
|
|
|
|
# http2 : forme de la directive dépendante de la version nginx
|
|
# installée sur CE noeud (voir commentaire dans le template) — jamais
|
|
# une des deux formes codée en dur, sinon ça casse forcément l'autre
|
|
# cas de figure (vécu : "unknown directive http2" avec la forme
|
|
# séparée sur une nginx < 1.25.1, avertissement de dépréciation avec
|
|
# l'ancienne forme sur les toutes dernières versions).
|
|
local nginx_version http2_block
|
|
# Chemin explicite plutôt que "nginx" nu : /usr/sbin n'est pas
|
|
# toujours sur $PATH selon comment ce script est invoqué.
|
|
nginx_version="$(/usr/sbin/nginx -v 2>&1 | grep -oP 'nginx/\K[0-9]+\.[0-9]+\.[0-9]+' || true)"
|
|
if [ -n "$nginx_version" ] && \
|
|
[ "$(printf '%s\n1.25.1\n' "$nginx_version" | sort -V | tail -1)" = "$nginx_version" ]; then
|
|
echo " -> nginx $nginx_version >= 1.25.1 : directive http2 séparée"
|
|
http2_block="listen 443 ssl;
|
|
listen [::]:443 ssl;
|
|
http2 on;"
|
|
else
|
|
echo " -> nginx ${nginx_version:-inconnue} < 1.25.1 (ou version indétectable) : http2 sur listen"
|
|
http2_block="listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;"
|
|
fi
|
|
|
|
awk -v block="$http2_block" '{gsub(/__HTTP2_LISTEN_BLOCK__/, block)}1' \
|
|
"$REPO_DIR/generic/nginx/fail2ban-emitter.example.conf" | \
|
|
sed "s#__DASHBOARD_DOMAIN__#$domain#g" \
|
|
> "/etc/nginx/sites-available/$domain"
|
|
ln -sf "/etc/nginx/sites-available/$domain" "/etc/nginx/sites-enabled/$domain"
|
|
nginx -t
|
|
systemctl reload nginx
|
|
|
|
upsert_env_var DJANGO_ALLOWED_HOSTS "$domain,127.0.0.1,localhost" "$ENV_FILE"
|
|
upsert_env_var DJANGO_CSRF_TRUSTED_ORIGINS "https://$domain" "$ENV_FILE"
|
|
upsert_env_var DJANGO_SECURE_SSL true "$ENV_FILE"
|
|
chown "$DEPLOY_USER:$DEPLOY_GROUP" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
systemctl restart fail2ban-emitter-web.service
|
|
echo " -> tableau de bord disponible sur https://$domain/"
|
|
}
|
|
|
|
# Liste des 4 services de l'émetteur présents sur tout noeud (web/mqtt/worker/
|
|
# master-client) — master-listen n'existe que sur le VPS master, traité à
|
|
# part (conditionnel à sa présence) dans install-supervisor/uninstall-supervisor.
|
|
EMITTER_SERVICE_NAMES=(fail2ban-emitter-web fail2ban-emitter-mqtt fail2ban-emitter-worker fail2ban-emitter-master-client)
|
|
|
|
# Bascule d'un déploiement systemd vers supervisor (Phase 5, option) : arrête
|
|
# et désactive les services systemd de l'émetteur (jamais les deux process
|
|
# managers en même temps sur le même service), déploie les 5 gabarits
|
|
# generic/supervisor/*.example.conf substitués (mêmes placeholders que les
|
|
# units systemd) et démarre supervisor.
|
|
deploy_supervisor() {
|
|
echo " [supervisor (bascule depuis systemd)]"
|
|
|
|
echo " -> arrêt/désactivation des services systemd de l'émetteur"
|
|
systemctl stop "${EMITTER_SERVICE_NAMES[@]/%/.service}" 2>/dev/null || true
|
|
systemctl disable "${EMITTER_SERVICE_NAMES[@]/%/.service}" 2>/dev/null || true
|
|
if systemctl list-unit-files fail2ban-emitter-master-listen.service >/dev/null 2>&1; then
|
|
systemctl stop fail2ban-emitter-master-listen.service 2>/dev/null || true
|
|
systemctl disable fail2ban-emitter-master-listen.service 2>/dev/null || true
|
|
fi
|
|
|
|
apt-get update -qq
|
|
apt-get install -y --no-install-recommends supervisor
|
|
install -d /var/log/supervisor
|
|
|
|
local sed_placeholders="s#__EMITTER_DIR__#$APP_DIR#g; s#__ENV_FILE__#$ENV_FILE#g; s#__EMITTER_USER__#$DEPLOY_USER#g; s#__EMITTER_GROUP__#$DEPLOY_GROUP#g"
|
|
local name
|
|
for name in "${EMITTER_SERVICE_NAMES[@]}"; do
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/supervisor/${name}.example.conf" > "/etc/supervisor/conf.d/${name}.conf"
|
|
done
|
|
if [ -f "$REPO_DIR/generic/supervisor/fail2ban-emitter-master-listen.example.conf" ] && [ -d "$APP_DIR/master-tls" ]; then
|
|
sed "$sed_placeholders" "$REPO_DIR/generic/supervisor/fail2ban-emitter-master-listen.example.conf" > /etc/supervisor/conf.d/fail2ban-emitter-master-listen.conf
|
|
fi
|
|
|
|
systemctl enable --now supervisor.service
|
|
supervisorctl reread
|
|
supervisorctl update
|
|
echo " -> pour revenir à systemd : sudo ./install.sh uninstall-supervisor"
|
|
supervisorctl status || true
|
|
}
|
|
|
|
# Retour arrière : arrête/retire les confs supervisor et réactive les
|
|
# services systemd correspondants.
|
|
undo_supervisor() {
|
|
echo " [retour à systemd (arrêt de supervisor)]"
|
|
|
|
local name
|
|
for name in "${EMITTER_SERVICE_NAMES[@]}" fail2ban-emitter-master-listen; do
|
|
if [ -f "/etc/supervisor/conf.d/${name}.conf" ]; then
|
|
supervisorctl stop "$name" 2>/dev/null || true
|
|
rm -f "/etc/supervisor/conf.d/${name}.conf"
|
|
fi
|
|
done
|
|
supervisorctl reread
|
|
supervisorctl update
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable "${EMITTER_SERVICE_NAMES[@]/%/.service}"
|
|
systemctl restart "${EMITTER_SERVICE_NAMES[@]/%/.service}"
|
|
if systemctl list-unit-files fail2ban-emitter-master-listen.service >/dev/null 2>&1; then
|
|
systemctl enable --now fail2ban-emitter-master-listen.service
|
|
fi
|
|
echo " -> services systemd de l'émetteur réactivés"
|
|
}
|
|
|
|
print_status() {
|
|
echo "=== firewall ==="
|
|
systemctl is-active firewall.service || true
|
|
|
|
echo "=== fail2ban ==="
|
|
systemctl is-active fail2ban.service || true
|
|
fail2ban-client status 2>/dev/null || echo "(fail2ban-client indisponible)"
|
|
|
|
echo "=== mosquitto ==="
|
|
systemctl is-active mosquitto.service || true
|
|
ss -tln 2>/dev/null | grep -q ':1883 ' \
|
|
&& echo "127.0.0.1:1883 en écoute" \
|
|
|| echo "(mosquitto : port 1883 non détecté en écoute)"
|
|
|
|
echo "=== redis ==="
|
|
systemctl is-active redis-server.service || true
|
|
|
|
echo "=== émetteur Django ($APP_DIR) ==="
|
|
systemctl is-active fail2ban-emitter-web.service || true
|
|
systemctl is-active fail2ban-emitter-mqtt.service || true
|
|
systemctl is-active fail2ban-emitter-worker.service || true
|
|
systemctl is-active fail2ban-emitter-master-client.service || true
|
|
ss -tln 2>/dev/null | grep -q ':8050 ' \
|
|
&& echo "127.0.0.1:8050 en écoute (tableau de bord)" \
|
|
|| echo "(émetteur : port 8050 non détecté en écoute)"
|
|
|
|
if systemctl list-unit-files fail2ban-emitter-master-listen.service >/dev/null 2>&1; then
|
|
echo "=== ingestion master (master_listen) ==="
|
|
systemctl is-active fail2ban-emitter-master-listen.service || true
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
install)
|
|
require_root
|
|
resolve_deploy_user
|
|
install_packages
|
|
deploy_fail2ban
|
|
deploy_firewall
|
|
# .env doit exister avant deploy_mosquitto (lit MQTT_VPN_LISTENER_IP) ;
|
|
# deploy_emitter l'appelle aussi plus loin, idempotent (ne recrée pas
|
|
# .env s'il existe déjà).
|
|
deploy_env
|
|
deploy_mosquitto
|
|
deploy_sudoers
|
|
deploy_emitter
|
|
enable_services
|
|
echo " [installation terminée]"
|
|
print_status
|
|
;;
|
|
install-master)
|
|
require_root
|
|
resolve_deploy_user
|
|
deploy_master_broker
|
|
echo " [broker master installé]"
|
|
;;
|
|
install-mariadb)
|
|
require_root
|
|
resolve_deploy_user
|
|
deploy_mariadb
|
|
;;
|
|
join)
|
|
require_root
|
|
resolve_deploy_user
|
|
shift
|
|
deploy_join "$@"
|
|
;;
|
|
install-nginx)
|
|
require_root
|
|
resolve_deploy_user
|
|
deploy_nginx
|
|
;;
|
|
install-supervisor)
|
|
require_root
|
|
resolve_deploy_user
|
|
deploy_supervisor
|
|
;;
|
|
uninstall-supervisor)
|
|
require_root
|
|
resolve_deploy_user
|
|
undo_supervisor
|
|
;;
|
|
status)
|
|
print_status
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {install|install-master|install-mariadb|join|install-nginx|install-supervisor|uninstall-supervisor|status}"
|
|
exit 1
|
|
;;
|
|
esac
|