improved logging, added docs, better help support, much improved instancing, pip installable
This commit is contained in:
parent
6817437380
commit
252fdf4db1
137
DOCS.md
Normal file
137
DOCS.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Icecast IRC Bot Documentation v1.1.0
|
||||||
|
|
||||||
|
This document is automatically generated from the codebase.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An IRC bot that monitors an Icecast stream and announces track changes.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See `config.yaml.example` for a full example configuration file.
|
||||||
|
|
||||||
|
No command documentation available (help template not found)
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### admin_required
|
||||||
|
|
||||||
|
Decorator to mark a command as requiring admin privileges.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `f`: The command handler function to wrap.
|
||||||
|
- `Returns`:
|
||||||
|
|
||||||
|
|
||||||
|
### announce_song
|
||||||
|
|
||||||
|
Announce a song in the IRC channel.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `song`: The song title to announce.
|
||||||
|
- `Only announces if`:
|
||||||
|
|
||||||
|
|
||||||
|
### fetch_json_metadata
|
||||||
|
|
||||||
|
Fetch metadata from the Icecast JSON status endpoint.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
str: The current song title, or an error message if fetching failed.
|
||||||
|
|
||||||
|
|
||||||
|
### format_help_section
|
||||||
|
|
||||||
|
Format a help section according to the template.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `section_config`: Configuration dictionary for the section.
|
||||||
|
- `prefix`: Command prefix to use.
|
||||||
|
- `Returns`:
|
||||||
|
- `List[str]`: List of formatted help lines for each command.
|
||||||
|
|
||||||
|
|
||||||
|
### get_version
|
||||||
|
|
||||||
|
Get the current version from VERSION file.
|
||||||
|
|
||||||
|
|
||||||
|
### help_command_fallback
|
||||||
|
|
||||||
|
Fallback help command implementation using hardcoded format.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `message`: The IRC message object that triggered this command.
|
||||||
|
|
||||||
|
|
||||||
|
### is_admin
|
||||||
|
|
||||||
|
Check if a user has admin privileges.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `user`: Full IRC user string (nickname!username@hostname) or User object.
|
||||||
|
- `Returns`:
|
||||||
|
- `bool`: True if user has admin privileges, False otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
### load_config
|
||||||
|
|
||||||
|
Load and validate the bot configuration from a YAML file.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `config_path`: Path to the YAML configuration file. If None, uses default path.
|
||||||
|
- `Returns`:
|
||||||
|
- `dict`: The loaded and validated configuration dictionary with default values applied.
|
||||||
|
|
||||||
|
|
||||||
|
### monitor_metadata
|
||||||
|
|
||||||
|
Monitor the Icecast stream for metadata changes.
|
||||||
|
|
||||||
|
|
||||||
|
### restart_monitoring
|
||||||
|
|
||||||
|
Restart the metadata monitoring task and verify the reconnection.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
|
||||||
|
bool: True if reconnection was successful and verified, False otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
### setup_handlers
|
||||||
|
|
||||||
|
Set up all IRC event handlers and command patterns.
|
||||||
|
|
||||||
|
|
||||||
|
### should_announce_song
|
||||||
|
|
||||||
|
Check if a song should be announced based on configured ignore patterns.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
|
||||||
|
- `song`: The song title to check.
|
||||||
|
- `Returns`:
|
||||||
|
- `bool`: True if the song should be announced, False if it matches any ignore patterns.
|
||||||
|
|
||||||
|
|
||||||
|
### start
|
||||||
|
|
||||||
|
Start the IRC bot and begin processing events.
|
||||||
|
|
||||||
|
|
||||||
|
### start_monitoring
|
||||||
|
|
||||||
|
Start the metadata monitoring task.
|
||||||
|
|
||||||
|
|
||||||
|
### stop_monitoring
|
||||||
|
|
||||||
|
Stop the metadata monitoring task.
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Icecast-metadata-IRC-announcer
|
# Icecast-metadata-IRC-announcer
|
||||||
|
|
||||||
[](https://code.cottongin.xyz/cottongin/Icecast-metadata-IRC-announcer/releases/tag/v1.0.1)
|
[](https://code.cottongin.xyz/cottongin/Icecast-metadata-IRC-announcer/releases)
|
||||||
|
|
||||||
A simple asynchronous Python bot that monitors an Icecast stream and announces track changes to an IRC channel. Supports running multiple instances with different configurations.
|
A simple asynchronous Python bot that monitors an Icecast stream and announces track changes to an IRC channel. Supports running multiple instances with different configurations.
|
||||||
|
|
||||||
|
|||||||
@ -23,9 +23,17 @@ commands:
|
|||||||
require_nick_prefix: false # If true, commands must be prefixed with "botname: " or "botname, "
|
require_nick_prefix: false # If true, commands must be prefixed with "botname: " or "botname, "
|
||||||
allow_private_commands: false # If true, allows commands in private messages
|
allow_private_commands: false # If true, allows commands in private messages
|
||||||
|
|
||||||
|
help: # Help message templates
|
||||||
|
specific_format: "\x02{prefix}{cmd}\x02: {desc}" # Format for specific command help
|
||||||
|
list_format: "(\x02{cmd}\x02, {desc})" # Format for commands in list
|
||||||
|
list_separator: " | " # Separator between commands in list
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
users: # List of users who can use admin commands (use "*" for anyone)
|
users: # List of users who can use admin commands (use "*" for anyone)
|
||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level: "INFO" # Logging level: DEBUG, INFO, WARNING, ERROR, or CRITICAL
|
level: "INFO" # Logging level: DEBUG, INFO, WARNING, ERROR, or CRITICAL
|
||||||
|
format: "%(asctime)s - %(levelname)s - %(message)s" # Format for console logs
|
||||||
|
error_format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" # Format for error logs
|
||||||
|
datefmt: "%H:%M:%S" # Date/time format for log timestamps
|
||||||
99
generate_docs.py
Executable file
99
generate_docs.py
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import yaml
|
||||||
|
from pathlib import Path
|
||||||
|
from main import IcecastBot
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
"""Get the current version from VERSION file."""
|
||||||
|
try:
|
||||||
|
with open('VERSION') as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def format_docstring(obj):
|
||||||
|
"""Format a docstring into markdown."""
|
||||||
|
doc = inspect.getdoc(obj)
|
||||||
|
if not doc:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Split into description and args
|
||||||
|
parts = doc.split('\n\n')
|
||||||
|
formatted = [parts[0]] # Description
|
||||||
|
|
||||||
|
for part in parts[1:]:
|
||||||
|
if part.startswith('Args:'):
|
||||||
|
formatted.append("\n**Arguments:**\n")
|
||||||
|
# Parse args section
|
||||||
|
args = part.replace('Args:', '').strip().split('\n')
|
||||||
|
for arg in args:
|
||||||
|
if ':' in arg:
|
||||||
|
name, desc = arg.split(':', 1)
|
||||||
|
formatted.append(f"- `{name.strip()}`: {desc.strip()}")
|
||||||
|
elif part.startswith('Returns:'):
|
||||||
|
formatted.append("\n**Returns:**\n")
|
||||||
|
formatted.append(part.replace('Returns:', '').strip())
|
||||||
|
|
||||||
|
return '\n'.join(formatted)
|
||||||
|
|
||||||
|
def generate_command_docs(config_path='config.yaml.example'):
|
||||||
|
"""Generate command documentation from help templates."""
|
||||||
|
try:
|
||||||
|
with open(config_path) as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "No command documentation available (config file not found)"
|
||||||
|
|
||||||
|
help_config = config.get('commands', {}).get('help', {})
|
||||||
|
if not help_config:
|
||||||
|
return "No command documentation available (help template not found)"
|
||||||
|
|
||||||
|
docs = ["## Commands\n"]
|
||||||
|
|
||||||
|
# Regular commands
|
||||||
|
if 'commands' in help_config.get('sections', {}):
|
||||||
|
docs.append("### Regular Commands\n")
|
||||||
|
for cmd, desc in help_config['sections']['commands']['commands'].items():
|
||||||
|
docs.append(f"- `{cmd}`: {desc}")
|
||||||
|
docs.append("")
|
||||||
|
|
||||||
|
# Admin commands
|
||||||
|
if 'admin' in help_config.get('sections', {}):
|
||||||
|
docs.append("### Admin Commands\n")
|
||||||
|
docs.append("These commands are only available to users listed in the `admin.users` config section.\n")
|
||||||
|
for cmd, desc in help_config['sections']['admin']['commands'].items():
|
||||||
|
docs.append(f"- `{cmd}`: {desc}")
|
||||||
|
docs.append("")
|
||||||
|
|
||||||
|
return '\n'.join(docs)
|
||||||
|
|
||||||
|
def generate_docs():
|
||||||
|
"""Generate full documentation in markdown format."""
|
||||||
|
version = get_version()
|
||||||
|
|
||||||
|
docs = [
|
||||||
|
f"# Icecast IRC Bot Documentation v{version}\n",
|
||||||
|
"This document is automatically generated from the codebase.\n",
|
||||||
|
"## Overview\n",
|
||||||
|
format_docstring(IcecastBot),
|
||||||
|
"\n## Configuration\n",
|
||||||
|
"See `config.yaml.example` for a full example configuration file.\n",
|
||||||
|
generate_command_docs(),
|
||||||
|
"\n## Methods\n"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Document public methods
|
||||||
|
for name, method in inspect.getmembers(IcecastBot, predicate=inspect.isfunction):
|
||||||
|
if not name.startswith('_'): # Only public methods
|
||||||
|
docs.append(f"### {name}\n")
|
||||||
|
docs.append(format_docstring(method))
|
||||||
|
docs.append("\n")
|
||||||
|
|
||||||
|
# Write to DOCS.md
|
||||||
|
with open('DOCS.md', 'w') as f:
|
||||||
|
f.write('\n'.join(docs))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
generate_docs()
|
||||||
194
icecast-irc-bot-manager.py
Normal file
194
icecast-irc-bot-manager.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#!/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())
|
||||||
26
icecast-irc-bot-manager.service
Normal file
26
icecast-irc-bot-manager.service
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Icecast IRC Bot Manager
|
||||||
|
After=network.target
|
||||||
|
Wants=network.target
|
||||||
|
Documentation=https://code.cottongin.xyz/cottongin/Icecast-metadata-IRC-announcer
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=icecast-bot
|
||||||
|
Group=icecast-bot
|
||||||
|
Environment=VIRTUAL_ENV=/opt/icecast-irc-bot/venv
|
||||||
|
Environment=PATH=/opt/icecast-irc-bot/venv/bin:$PATH
|
||||||
|
ExecStart=/opt/icecast-irc-bot/venv/bin/python3 /usr/local/bin/icecast-irc-bot-manager --config /etc/icecast-irc-bot/config.yaml
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
StandardOutput=append:/var/log/icecast-irc-bot/bot.log
|
||||||
|
StandardError=append:/var/log/icecast-irc-bot/error.log
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
71
install.sh
Executable file
71
install.sh
Executable file
@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit on any error
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create icecast-bot user and group if they don't exist
|
||||||
|
if ! getent group icecast-bot >/dev/null; then
|
||||||
|
groupadd icecast-bot
|
||||||
|
fi
|
||||||
|
if ! getent passwd icecast-bot >/dev/null; then
|
||||||
|
useradd -r -g icecast-bot -s /bin/false icecast-bot
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
mkdir -p /etc/icecast-irc-bot
|
||||||
|
mkdir -p /var/log/icecast-irc-bot
|
||||||
|
mkdir -p /opt/icecast-irc-bot
|
||||||
|
|
||||||
|
# Set ownership
|
||||||
|
chown icecast-bot:icecast-bot /etc/icecast-irc-bot
|
||||||
|
chown icecast-bot:icecast-bot /var/log/icecast-irc-bot
|
||||||
|
chown icecast-bot:icecast-bot /opt/icecast-irc-bot
|
||||||
|
|
||||||
|
# Create and activate virtual environment
|
||||||
|
python3 -m venv /opt/icecast-irc-bot/venv
|
||||||
|
export VIRTUAL_ENV="/opt/icecast-irc-bot/venv"
|
||||||
|
export PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
unset PYTHONHOME
|
||||||
|
|
||||||
|
# Install Python dependencies in virtual environment
|
||||||
|
pip3 install --upgrade pip
|
||||||
|
pip3 install hatchling
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
pip3 install .
|
||||||
|
|
||||||
|
# Install manager script
|
||||||
|
cp icecast-irc-bot-manager.py /usr/local/bin/icecast-irc-bot-manager
|
||||||
|
chmod +x /usr/local/bin/icecast-irc-bot-manager
|
||||||
|
|
||||||
|
# Update manager script shebang to use venv Python
|
||||||
|
sed -i "1c#\!$VIRTUAL_ENV/bin/python3" /usr/local/bin/icecast-irc-bot-manager
|
||||||
|
|
||||||
|
# Install service file
|
||||||
|
cp icecast-irc-bot-manager.service /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Copy example config if it doesn't exist
|
||||||
|
if [ ! -f /etc/icecast-irc-bot/config.yaml ]; then
|
||||||
|
cp config.yaml.example /etc/icecast-irc-bot/config.yaml
|
||||||
|
chown icecast-bot:icecast-bot /etc/icecast-irc-bot/config.yaml
|
||||||
|
chmod 640 /etc/icecast-irc-bot/config.yaml
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installation complete!"
|
||||||
|
echo
|
||||||
|
echo "Next steps:"
|
||||||
|
echo "1. Configure your bot(s) in /etc/icecast-irc-bot/config.yaml"
|
||||||
|
echo "2. Start the service: systemctl start icecast-irc-bot-manager"
|
||||||
|
echo "3. Enable at boot: systemctl enable icecast-irc-bot-manager"
|
||||||
|
echo
|
||||||
|
echo "Usage:"
|
||||||
|
echo "icecast-irc-bot-manager list # List running bots"
|
||||||
|
echo "icecast-irc-bot-manager start --config FILE # Start a bot"
|
||||||
|
echo "icecast-irc-bot-manager stop BOT_ID # Stop a bot"
|
||||||
|
echo "icecast-irc-bot-manager restart BOT_ID # Restart a bot"
|
||||||
31
pyproject.toml
Normal file
31
pyproject.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[project]
|
||||||
|
name = "icecast-irc-bot"
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = "Icecast metadata IRC announcer bot"
|
||||||
|
authors = [
|
||||||
|
{name = "cottongin", email = "cottongin@cottongin.xyz"},
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"asif",
|
||||||
|
"aiohttp",
|
||||||
|
"pyyaml",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["."]
|
||||||
|
|
||||||
|
[tool.hatch.build]
|
||||||
|
include = [
|
||||||
|
"*.py",
|
||||||
|
"README*",
|
||||||
|
"LICENSE*",
|
||||||
|
"config.yaml.example"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {file = "VERSION"}
|
||||||
Loading…
x
Reference in New Issue
Block a user