V0.1 de la migration vers systemd

This commit is contained in:
Mathis 2026-07-12 15:01:47 +02:00
parent b23c0dde53
commit 016ac85bc7
9 changed files with 142 additions and 24 deletions

View file

@ -61,6 +61,9 @@ Kuby est un bot Discord complet développé en Python avec `discord.py`. Il offr
### Prérequis
- systemd installé sur la machine cible
- utilisateur `discord` existant (ou adapter `User=` dans les fichiers de service)
- Python 3.10 ou supérieur
- pip (gestionnaire de paquets Python)
@ -86,6 +89,20 @@ source venv/bin/activate # Linux/Mac
pip install -r requirements.txt
```
### 4. Installer les services systemd
```bash
sudo chmod +x scripts/*.sh
sudo ./scripts/install_systemd_services.sh
```
### 5. Vérifier les services
```bash
sudo systemctl status kuby-bot.service kuby-flask.service
sudo journalctl -u kuby-bot.service -f
```
Les dépendances incluent :
- `discord.py>=2.3.0` - Framework Discord
- `python-dotenv>=1.0.0` - Variables d'environnement

2
bot.py
View file

@ -102,7 +102,7 @@ class MyBot(commands.Bot):
self.advanced_logger = AdvancedLogger(self)
kuby_logger.info("✅ Advanced logger initialized")
# Flask est géré par PM2 (kuby-flask), pas besoin de le lancer ici
# Flask est géré par le service systemd kuby-flask, pas besoin de le lancer ici
async def do_async_setup(self):
kuby_logger.info("🚀 Configuration initiale du bot Kuby...")

View file

@ -29,7 +29,7 @@ def classify_webhook_event(payload: Dict[str, Any]) -> Optional[str]:
if "comment" in payload or payload.get("comment") or "comment" in action:
return "note"
if payload.get("issue") or payload.get("object_attributes") or action in {"open", "reopen", "close", "closed", "updated", "created", "edited"}:
if payload.get("issue") or payload.get("object_attributes") or action in {"open", "reopen", "close", "closed", "updated", "created", "edited", "label_updated", "label_added", "label_removed", "label_changed"}:
return "issue"
return None
@ -82,24 +82,47 @@ def normalize_labels(labels: Any) -> list[str]:
def extract_label_change(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Extrait le changement de labels dun payload dissue."""
changes = payload.get("changes") or {}
labels_change = changes.get("labels") or changes.get("label")
if not isinstance(labels_change, dict):
action = str(payload.get("action") or payload.get("event") or "").lower()
if not action.startswith("label"):
return None
previous = normalize_labels(labels_change.get("previous"))
current = normalize_labels(labels_change.get("current"))
if not previous and not current:
issue = payload.get("issue") or {}
current_labels = normalize_labels(issue.get("labels") or payload.get("labels"))
if not current_labels:
return None
added = [label for label in current if label not in previous]
removed = [label for label in previous if label not in current]
if not added and not removed:
return None
label_payload = payload.get("label")
label_name = None
if isinstance(label_payload, dict):
label_name = label_payload.get("name") or label_payload.get("title") or label_payload.get("label")
elif isinstance(label_payload, str):
label_name = label_payload
previous_labels = []
if isinstance(payload.get("changes"), dict):
prev = payload["changes"].get("labels") or payload["changes"].get("label")
if isinstance(prev, dict):
previous_labels = normalize_labels(prev.get("previous"))
if not previous_labels and issue.get("labels"):
previous_labels = []
if action in {"label_removed", "label_deleted"}:
return {
"previous": previous_labels or ([label_name] if label_name else []),
"current": current_labels,
"added": [],
"removed": [label_name] if label_name else [],
}
added = [label for label in current_labels if label not in previous_labels]
removed = [label for label in previous_labels if label not in current_labels]
if not added and not removed and label_name:
added = [label_name]
return {
"previous": previous,
"current": current,
"previous": previous_labels,
"current": current_labels,
"added": added,
"removed": removed,
}

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [ "$EUID" -ne 0 ]; then
echo "Ce script doit être exécuté avec sudo." >&2
exit 1
fi
mkdir -p /var/log/kuby
for service in kuby-bot.service kuby-flask.service; do
sed "s#__KUBY_DIR__#${ROOT_DIR}#g" "$ROOT_DIR/systemd/$service" > "/etc/systemd/system/$service"
done
systemctl daemon-reload
systemctl enable --now kuby-bot.service kuby-flask.service
echo "Services systemd installés et démarrés."

16
scripts/run_kuby_bot.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
export PYTHONUNBUFFERED=1
if [ -x "$ROOT_DIR/.venv/bin/python" ]; then
exec "$ROOT_DIR/.venv/bin/python" "$ROOT_DIR/kuby.py"
elif command -v python3 >/dev/null 2>&1; then
exec python3 "$ROOT_DIR/kuby.py"
else
echo "python3 introuvable" >&2
exit 1
fi

16
scripts/run_kuby_flask.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
export PYTHONUNBUFFERED=1
if [ -x "$ROOT_DIR/.venv/bin/python" ]; then
exec "$ROOT_DIR/.venv/bin/python" "$ROOT_DIR/serveur_flask.py"
elif command -v python3 >/dev/null 2>&1; then
exec python3 "$ROOT_DIR/serveur_flask.py"
else
echo "python3 introuvable" >&2
exit 1
fi

View file

@ -17,9 +17,6 @@ PROCESS_NAME = "kuby"
# webhook de logs Discord
DISCORD_LOG_URL = "https://discord.com/api/webhooks/1482148910888652910/IA9CcOWtjGswbuxMaOu_6uaclv5zojZo4ttxtV6RYyZzJ9gW7BF7xp_Zv3oPIjYEuh8o"
# Chemin vers PM2
PM2_BINARY = "/home/discord/.nvm/versions/node/v24.15.0/bin/pm2"
# Bot Local Proxy (Relai vers le Cog de ton bot)
BOT_LOCAL_URL = "http://127.0.0.1:5001/bot_event"
@ -88,13 +85,8 @@ def run_mep_logic(author, commit_msg, branch="main"):
if use_rsync:
subprocess.run(["rsync", "-a", "--exclude=.git", "--exclude=venv", "--exclude=.venv", "--exclude=__pycache__", f"{target_path}/", f"{PROJECT_PATH}/"], check=True)
# 3. PM2 Restart
subprocess.run(
[PM2_BINARY, "restart", PROCESS_NAME],
check=True,
capture_output=True,
text=True
)
# 3. Redémarrage via systemd
subprocess.run(["systemctl", "restart", "kuby-bot.service"], check=True, capture_output=True, text=True)
duration = (datetime.now() - start_time).total_seconds()
msg = f"✅ **Déploiement réussi en {duration:.2f}s**\nLe bot est à jour sur Forgejo et redémarré."

17
systemd/kuby-bot.service Normal file
View file

@ -0,0 +1,17 @@
[Unit]
Description=Kuby Discord Bot
After=network.target
[Service]
Type=simple
User=discord
WorkingDirectory=__KUBY_DIR__
Environment=PYTHONUNBUFFERED=1
ExecStart=__KUBY_DIR__/scripts/run_kuby_bot.sh
Restart=always
RestartSec=5
StandardOutput=append:/var/log/kuby/bot.log
StandardError=append:/var/log/kuby/bot.log
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Kuby Flask webhook service
After=network.target
[Service]
Type=simple
User=discord
WorkingDirectory=__KUBY_DIR__
Environment=PYTHONUNBUFFERED=1
ExecStart=__KUBY_DIR__/scripts/run_kuby_flask.sh
Restart=always
RestartSec=5
StandardOutput=append:/var/log/kuby/flask.log
StandardError=append:/var/log/kuby/flask.log
[Install]
WantedBy=multi-user.target