Icecast-metadata-IRC-announcer/icecast-irc-bot-manager.py

194 lines
6.1 KiB
Python
Raw Normal View History

#!/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())