V0.1 de la migration vers systemd
This commit is contained in:
parent
b23c0dde53
commit
016ac85bc7
9 changed files with 142 additions and 24 deletions
17
README.md
17
README.md
|
|
@ -61,6 +61,9 @@ Kuby est un bot Discord complet développé en Python avec `discord.py`. Il offr
|
||||||
|
|
||||||
### Prérequis
|
### 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
|
- Python 3.10 ou supérieur
|
||||||
- pip (gestionnaire de paquets Python)
|
- pip (gestionnaire de paquets Python)
|
||||||
|
|
||||||
|
|
@ -86,6 +89,20 @@ source venv/bin/activate # Linux/Mac
|
||||||
pip install -r requirements.txt
|
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 :
|
Les dépendances incluent :
|
||||||
- `discord.py>=2.3.0` - Framework Discord
|
- `discord.py>=2.3.0` - Framework Discord
|
||||||
- `python-dotenv>=1.0.0` - Variables d'environnement
|
- `python-dotenv>=1.0.0` - Variables d'environnement
|
||||||
|
|
|
||||||
2
bot.py
2
bot.py
|
|
@ -102,7 +102,7 @@ class MyBot(commands.Bot):
|
||||||
self.advanced_logger = AdvancedLogger(self)
|
self.advanced_logger = AdvancedLogger(self)
|
||||||
kuby_logger.info("✅ Advanced logger initialized")
|
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):
|
async def do_async_setup(self):
|
||||||
kuby_logger.info("🚀 Configuration initiale du bot Kuby...")
|
kuby_logger.info("🚀 Configuration initiale du bot Kuby...")
|
||||||
|
|
|
||||||
|
|
@ -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:
|
if "comment" in payload or payload.get("comment") or "comment" in action:
|
||||||
return "note"
|
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 "issue"
|
||||||
|
|
||||||
return None
|
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]]:
|
def extract_label_change(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
"""Extrait le changement de labels d’un payload d’issue."""
|
"""Extrait le changement de labels d’un payload d’issue."""
|
||||||
changes = payload.get("changes") or {}
|
action = str(payload.get("action") or payload.get("event") or "").lower()
|
||||||
labels_change = changes.get("labels") or changes.get("label")
|
if not action.startswith("label"):
|
||||||
if not isinstance(labels_change, dict):
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
previous = normalize_labels(labels_change.get("previous"))
|
issue = payload.get("issue") or {}
|
||||||
current = normalize_labels(labels_change.get("current"))
|
current_labels = normalize_labels(issue.get("labels") or payload.get("labels"))
|
||||||
if not previous and not current:
|
if not current_labels:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
added = [label for label in current if label not in previous]
|
label_payload = payload.get("label")
|
||||||
removed = [label for label in previous if label not in current]
|
label_name = None
|
||||||
if not added and not removed:
|
if isinstance(label_payload, dict):
|
||||||
return None
|
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 {
|
return {
|
||||||
"previous": previous,
|
"previous": previous_labels,
|
||||||
"current": current,
|
"current": current_labels,
|
||||||
"added": added,
|
"added": added,
|
||||||
"removed": removed,
|
"removed": removed,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
scripts/install_systemd_services.sh
Executable file
20
scripts/install_systemd_services.sh
Executable 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
16
scripts/run_kuby_bot.sh
Executable 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
16
scripts/run_kuby_flask.sh
Executable 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
|
||||||
|
|
@ -17,9 +17,6 @@ PROCESS_NAME = "kuby"
|
||||||
# webhook de logs Discord
|
# webhook de logs Discord
|
||||||
DISCORD_LOG_URL = "https://discord.com/api/webhooks/1482148910888652910/IA9CcOWtjGswbuxMaOu_6uaclv5zojZo4ttxtV6RYyZzJ9gW7BF7xp_Zv3oPIjYEuh8o"
|
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 Proxy (Relai vers le Cog de ton bot)
|
||||||
BOT_LOCAL_URL = "http://127.0.0.1:5001/bot_event"
|
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:
|
if use_rsync:
|
||||||
subprocess.run(["rsync", "-a", "--exclude=.git", "--exclude=venv", "--exclude=.venv", "--exclude=__pycache__", f"{target_path}/", f"{PROJECT_PATH}/"], check=True)
|
subprocess.run(["rsync", "-a", "--exclude=.git", "--exclude=venv", "--exclude=.venv", "--exclude=__pycache__", f"{target_path}/", f"{PROJECT_PATH}/"], check=True)
|
||||||
|
|
||||||
# 3. PM2 Restart
|
# 3. Redémarrage via systemd
|
||||||
subprocess.run(
|
subprocess.run(["systemctl", "restart", "kuby-bot.service"], check=True, capture_output=True, text=True)
|
||||||
[PM2_BINARY, "restart", PROCESS_NAME],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
duration = (datetime.now() - start_time).total_seconds()
|
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é."
|
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
17
systemd/kuby-bot.service
Normal 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
|
||||||
17
systemd/kuby-flask.service
Normal file
17
systemd/kuby-flask.service
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue