194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import os
|
|
import signal
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
)
|
|
logger = logging.getLogger('icecast-irc-bot-manager')
|
|
|
|
class BotManager:
|
|
"""Manages multiple Icecast IRC bot instances."""
|
|
|
|
def __init__(self):
|
|
self.bots: Dict[str, asyncio.subprocess.Process] = {}
|
|
self.config_dir = Path('/etc/icecast-irc-bot')
|
|
self.socket_dir = Path(tempfile.gettempdir())
|
|
|
|
async def start_bot(self, config_path: Path) -> bool:
|
|
"""Start a bot instance with the given config.
|
|
|
|
Args:
|
|
config_path: Path to the bot's config file
|
|
|
|
Returns:
|
|
bool: True if bot was started successfully
|
|
"""
|
|
try:
|
|
# Create unique name for this bot instance
|
|
bot_id = config_path.stem
|
|
|
|
# Check if bot is already running
|
|
if bot_id in self.bots:
|
|
logger.warning(f"Bot {bot_id} is already running")
|
|
return False
|
|
|
|
# Start the bot process
|
|
process = await asyncio.create_subprocess_exec(
|
|
sys.executable, '-m', 'main',
|
|
'--config', str(config_path),
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE
|
|
)
|
|
|
|
self.bots[bot_id] = process
|
|
logger.info(f"Started bot {bot_id} (PID: {process.pid})")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to start bot with config {config_path}: {e}")
|
|
return False
|
|
|
|
async def stop_bot(self, bot_id: str) -> bool:
|
|
"""Stop a running bot instance.
|
|
|
|
Args:
|
|
bot_id: ID of the bot to stop
|
|
|
|
Returns:
|
|
bool: True if bot was stopped successfully
|
|
"""
|
|
if bot_id not in self.bots:
|
|
logger.warning(f"Bot {bot_id} is not running")
|
|
return False
|
|
|
|
try:
|
|
process = self.bots[bot_id]
|
|
process.terminate()
|
|
try:
|
|
await asyncio.wait_for(process.wait(), timeout=5.0)
|
|
except asyncio.TimeoutError:
|
|
process.kill()
|
|
await process.wait()
|
|
|
|
del self.bots[bot_id]
|
|
logger.info(f"Stopped bot {bot_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to stop bot {bot_id}: {e}")
|
|
return False
|
|
|
|
async def restart_bot(self, bot_id: str) -> bool:
|
|
"""Restart a running bot instance.
|
|
|
|
Args:
|
|
bot_id: ID of the bot to restart
|
|
|
|
Returns:
|
|
bool: True if bot was restarted successfully
|
|
"""
|
|
# Find the config file for this bot
|
|
config_path = self.config_dir / f"{bot_id}.yaml"
|
|
if not config_path.exists():
|
|
logger.error(f"Config file not found for bot {bot_id}")
|
|
return False
|
|
|
|
# Stop the bot if it's running
|
|
if bot_id in self.bots:
|
|
if not await self.stop_bot(bot_id):
|
|
return False
|
|
|
|
# Start the bot with the same config
|
|
return await self.start_bot(config_path)
|
|
|
|
async def list_bots(self) -> List[Dict]:
|
|
"""List all running bot instances.
|
|
|
|
Returns:
|
|
List[Dict]: List of bot info dictionaries
|
|
"""
|
|
bot_info = []
|
|
for bot_id, process in self.bots.items():
|
|
info = {
|
|
'id': bot_id,
|
|
'pid': process.pid,
|
|
'running': process.returncode is None
|
|
}
|
|
bot_info.append(info)
|
|
return bot_info
|
|
|
|
async def cleanup(self):
|
|
"""Clean up all running bots."""
|
|
for bot_id in list(self.bots.keys()):
|
|
await self.stop_bot(bot_id)
|
|
|
|
async def main():
|
|
parser = argparse.ArgumentParser(description='Icecast IRC Bot Manager')
|
|
parser.add_argument('--config', type=str, help='Path to config file or directory')
|
|
parser.add_argument('command', choices=['start', 'stop', 'restart', 'list'], help='Command to execute')
|
|
parser.add_argument('bot_id', nargs='?', help='Bot ID for start/stop/restart commands')
|
|
|
|
args = parser.parse_args()
|
|
|
|
manager = BotManager()
|
|
|
|
try:
|
|
if args.command == 'list':
|
|
bot_info = await manager.list_bots()
|
|
if bot_info:
|
|
print(json.dumps(bot_info, indent=2))
|
|
else:
|
|
print("No bots running")
|
|
|
|
elif args.command == 'start':
|
|
if not args.config:
|
|
print("Error: --config required for start command")
|
|
sys.exit(1)
|
|
config_path = Path(args.config)
|
|
if config_path.is_dir():
|
|
# Start all bots in directory
|
|
for config_file in config_path.glob('*.yaml'):
|
|
await manager.start_bot(config_file)
|
|
else:
|
|
# Start single bot
|
|
await manager.start_bot(config_path)
|
|
|
|
elif args.command == 'stop':
|
|
if not args.bot_id:
|
|
print("Error: bot_id required for stop command")
|
|
sys.exit(1)
|
|
await manager.stop_bot(args.bot_id)
|
|
|
|
elif args.command == 'restart':
|
|
if not args.bot_id:
|
|
print("Error: bot_id required for restart command")
|
|
sys.exit(1)
|
|
await manager.restart_bot(args.bot_id)
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("Shutting down...")
|
|
await manager.cleanup()
|
|
except Exception as e:
|
|
logger.error(f"Unhandled error: {e}")
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
# Set up signal handlers
|
|
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
signal.signal(sig, lambda s, f: sys.exit(0))
|
|
|
|
# Run the manager
|
|
asyncio.run(main()) |