From e7a8cc90dde896d18a4e3b7c5bd69ae044e5f0a2 Mon Sep 17 00:00:00 2001 From: Fimeg Date: Sat, 20 Dec 2025 14:12:11 -0500 Subject: [PATCH] fix: Update .gitignore and remove dev files from repository Remove development and investigation files that shouldn't be in repo: - Kate editor swap files (*.swp, *.kate-swp) - Discord development folder (contains credentials) - Development investigation scripts (db_investigation.sh, etc.) - Configuration files (docker-compose.dev.yml) Note: Files removed from git but kept locally (rm --cached) Files are still present in working directory but won't be tracked --- .MIGRATION_STRATEGY.md.kate-swp | Bin 139 -> 0 bytes .gitignore | 24 +- db_investigation.sh | 56 -- discord/.env.example | 24 - discord/.gitignore | 31 - discord/discord_manager.py | 1046 ------------------------------- discord/env_manager.py | 153 ----- discord/requirements.txt | 4 - discord/setup.py | 124 ---- fix_agent_permissions.sh | 136 ---- install.sh | 383 ----------- 11 files changed, 23 insertions(+), 1958 deletions(-) delete mode 100644 .MIGRATION_STRATEGY.md.kate-swp delete mode 100644 db_investigation.sh delete mode 100644 discord/.env.example delete mode 100644 discord/.gitignore delete mode 100755 discord/discord_manager.py delete mode 100755 discord/env_manager.py delete mode 100644 discord/requirements.txt delete mode 100644 discord/setup.py delete mode 100644 fix_agent_permissions.sh delete mode 100755 install.sh diff --git a/.MIGRATION_STRATEGY.md.kate-swp b/.MIGRATION_STRATEGY.md.kate-swp deleted file mode 100644 index aa66ab2223b568b5b97b4b92beeaa623785f90c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139 zcmZQzU=Z?7EJ;-eE>A2_aLdd|RWQ;sU|?Vnk(qL4XT7?fro*xCQ(w#qUaY+9_JQCa t1_nk)pez><^Lrw37;{{MVSH|&Bx5Fm&jaG;A^5x?eky{`2jUmG0syB186p4x diff --git a/.gitignore b/.gitignore index 99bca4d..de99f27 100644 --- a/.gitignore +++ b/.gitignore @@ -445,4 +445,26 @@ TEST-CLONE.md !docs/API.md !docs/CONFIGURATION.md !docs/ARCHITECTURE.md -!docs/DEVELOPMENT.md \ No newline at end of file +!docs/DEVELOPMENT.md + +# ============================================================================= +# Development and investigation files (should not be in repo) +# ============================================================================= +db_investigation.sh +fix_agent_permissions.sh +install.sh +docker-compose.dev.yml +.migration_temp/ + +# ============================================================================= +# Kate editor swap files +# ============================================================================= +*.swp +*.kate-swp +.MIGRATION_STRATEGY.md.kate-swp + +# ============================================================================= +# Discord bot development (private, contains credentials) +# ============================================================================= +discord/ +discord/.env.example \ No newline at end of file diff --git a/db_investigation.sh b/db_investigation.sh deleted file mode 100644 index cee18c7..0000000 --- a/db_investigation.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -echo "=== RedFlag Database Investigation ===" -echo - -# Check if containers are running -echo "1. Checking container status..." -docker ps | grep -E "redflag|postgres" - -echo -echo "2. Testing database connection with different credentials..." - -# Try with postgres credentials -echo "Trying with postgres user:" -docker exec redflag-postgres psql -U postgres -c "SELECT current_database(), current_user;" 2>/dev/null - -# Try with redflag credentials -echo "Trying with redflag user:" -docker exec redflag-postgres psql -U redflag -d redflag -c "SELECT current_database(), current_user;" 2>/dev/null - -echo -echo "3. Listing databases:" -docker exec redflag-postgres psql -U postgres -c "\l" 2>/dev/null - -echo -echo "4. Checking tables in redflag database:" -docker exec redflag-postgres psql -U postgres -d redflag -c "\dt" 2>/dev/null || echo "Failed to list tables" - -echo -echo "5. Checking migration status:" -docker exec redflag-postgres psql -U postgres -d redflag -c "SELECT version, applied_at FROM schema_migrations ORDER BY version;" 2>/dev/null || echo "No schema_migrations table found" - -echo -echo "6. Checking users table:" -docker exec redflag-postgres psql -U postgres -d redflag -c "SELECT id, username, email, created_at FROM users LIMIT 5;" 2>/dev/null || echo "Users table not found" - -echo -echo "7. Checking for security_* tables:" -docker exec redflag-postgres psql -U postgres -d redflag -c "\dt security_*" 2>/dev/null || echo "No security_* tables found" - -echo -echo "8. Checking agent_commands table for signature column:" -docker exec redflag-postgres psql -U postgres -d redflag -c "\d agent_commands" 2>/dev/null | grep signature || echo "Signature column not found" - -echo -echo "9. Checking recent logs from server:" -docker logs redflag-server 2>&1 | tail -20 - -echo -echo "10. Password configuration check:" -echo "From docker-compose.yml POSTGRES_PASSWORD:" -grep "POSTGRES_PASSWORD:" docker-compose.yml -echo "From config/.env POSTGRES_PASSWORD:" -grep "POSTGRES_PASSWORD:" config/.env -echo "From config/.env REDFLAG_DB_PASSWORD:" -grep "REDFLAG_DB_PASSWORD:" config/.env \ No newline at end of file diff --git a/discord/.env.example b/discord/.env.example deleted file mode 100644 index 7fc84df..0000000 --- a/discord/.env.example +++ /dev/null @@ -1,24 +0,0 @@ -# Discord Configuration Template -# Copy this file to .env and fill in your actual values - -# Discord Bot Configuration -DISCORD_BOT_TOKEN=your_bot_token_here -DISCORD_SERVER_ID=your_server_id_here -DISCORD_APPLICATION_ID=your_app_id_here -DISCORD_PUBLIC_KEY=your_public_key_here - -# Server Management -SERVER_NAME=RedFlag Security -ADMIN_ROLE_ID=your_admin_role_id_here - -# Channel IDs (to be filled after creation) -GENERAL_CHANNEL_ID= -ANNOUNCEMENTS_CHANNEL_ID= -SECURITY_ALERTS_CHANNEL_ID= -DEV_CHAT_CHANNEL_ID= -BUG_REPORTS_CHANNEL_ID= - -# Category IDs (to be filled after creation) -COMMUNITY_CATEGORY_ID= -DEVELOPMENT_CATEGORY_ID= -SECURITY_CATEGORY_ID= \ No newline at end of file diff --git a/discord/.gitignore b/discord/.gitignore deleted file mode 100644 index d5e510c..0000000 --- a/discord/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -# Environment files -.env -.env.local -.env.*.local - -# Python -__pycache__/ -*.pyc -*.pyo -*.pyd -.env -venv/ -.venv/ -env/ -venv.bak/ -venv/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# Logs -*.log -logs/ - -# OS -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/discord/discord_manager.py b/discord/discord_manager.py deleted file mode 100755 index fb16043..0000000 --- a/discord/discord_manager.py +++ /dev/null @@ -1,1046 +0,0 @@ -#!/usr/bin/env python3 -""" -RedFlag Discord Management Bot -Interactive Discord server management with secure configuration -""" - -import discord -import asyncio -import logging -import sys -from typing import Optional, Dict, List -from discord.ext import commands -from discord import app_commands -from env_manager import discord_env - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -class DiscordManager: - """Interactive Discord server manager""" - - def __init__(self): - self.bot_token = discord_env.get_required('DISCORD_BOT_TOKEN') - self.server_id = int(discord_env.get_required('DISCORD_SERVER_ID')) - self.application_id = discord_env.get_required('DISCORD_APPLICATION_ID') - self.public_key = discord_env.get_required('DISCORD_PUBLIC_KEY') - - # Bot setup with required intents - intents = discord.Intents.default() - intents.guilds = True - intents.message_content = True - - # Initialize bot - self.bot = commands.Bot( - command_prefix='!', - intents=intents, - help_command=None # We'll create custom help - ) - - self.setup_events() - self.setup_commands() - - def setup_events(self): - """Setup bot event handlers""" - - @self.bot.event - async def on_ready(): - logger.info(f'✅ Bot logged in as {self.bot.user}') - logger.info(f'Serving server: {self.bot.user.name} (ID: {self.bot.user.id})') - - # Sync commands to guild specifically (more reliable) - guild = self.bot.get_guild(self.server_id) - # Sync commands globally - await self.bot.tree.sync() - logger.info('✅ Commands synced globally') - - # Get server info - guild = self.bot.get_guild(self.server_id) - if guild: - logger.info(f'✅ Connected to server: {guild.name}') - await self.print_server_status(guild) - else: - logger.error('❌ Could not find server!') - - @self.bot.event - async def on_command_error(ctx, error): - """Handle command errors""" - logger.error(f'Command error in {ctx.command}: {error}') - if isinstance(error, commands.MissingRequiredArgument): - await ctx.send(f'❌ Missing required argument: `{error.param}`') - elif isinstance(error, commands.BadArgument): - await ctx.send(f'❌ Invalid argument: {error}') - else: - await ctx.send(f'❌ An error occurred: {error}') - - @self.bot.event - async def on_interaction_error(interaction, error): - """Handle interaction errors""" - logger.error(f'Interaction error: {error}') - if interaction.response.is_done(): - await interaction.followup.send(f'❌ An error occurred: {error}', ephemeral=True) - else: - await interaction.response.send_message(f'❌ An error occurred: {error}', ephemeral=True) - - def setup_commands(self): - """Setup slash commands""" - - # Server management commands - @self.bot.tree.command(name="status", description="Show current Discord server status") - async def cmd_status(interaction: discord.Interaction): - await self.cmd_status(interaction) - - @self.bot.tree.command(name="create-channels", description="Create standard RedFlag channels") - async def cmd_create_channels(interaction: discord.Interaction): - await self.cmd_create_channels(interaction) - - @self.bot.tree.command(name="send-message", description="Send a message to a channel") - @app_commands.describe(channel="Channel to send message to", message="Message to send") - async def cmd_send_message(interaction: discord.Interaction, channel: str, message: str): - await self.cmd_send_message(interaction, channel, message) - - @self.bot.tree.command(name="list-channels", description="List all channels and categories") - async def cmd_list_channels(interaction: discord.Interaction): - await self.cmd_list_channels(interaction) - - @self.bot.tree.command(name="create-category", description="Create a channel category") - @app_commands.describe(name="Category name") - async def cmd_create_category(interaction: discord.Interaction, name: str): - await self.cmd_create_category(interaction, name) - - @self.bot.tree.command(name="create-test-channel", description="Create one simple test channel") - async def cmd_create_test_channel(interaction: discord.Interaction): - await self.cmd_create_test_channel(interaction) - - @self.bot.tree.command(name="create-roles", description="Create RedFlag community roles") - async def cmd_create_roles(interaction: discord.Interaction): - await self.cmd_create_roles(interaction) - - @self.bot.tree.command(name="role-menu", description="Show interactive role assignment menu") - async def cmd_role_menu(interaction: discord.Interaction): - await self.cmd_role_menu(interaction) - - @self.bot.tree.command(name="assign-lead-dev", description="Assign RedFlag Lead Dev role *(Admin only)*") - async def cmd_assign_lead_dev(interaction: discord.Interaction, user: discord.Member): - await self.cmd_assign_lead_dev(interaction, user) - - @self.bot.tree.command(name="setup-welcome", description="Setup welcome channel with message and role selector *(Admin only)*") - async def cmd_setup_welcome(interaction: discord.Interaction): - await self.cmd_setup_welcome(interaction) - - @self.bot.tree.command(name="create-version-channels", description="Create version-related channels *(Admin only)*") - async def cmd_create_version_channels(interaction: discord.Interaction): - await self.cmd_create_version_channels(interaction) - - @self.bot.tree.command(name="sync-commands", description="Force sync commands *(Admin only)*") - async def cmd_sync_commands(interaction: discord.Interaction): - await self.cmd_sync_commands(interaction) - - @self.bot.tree.command(name="create-redflag-channels", description="Create RedFlag homelab management channels") - async def cmd_create_redflag_channels(interaction: discord.Interaction): - await self.cmd_create_redflag_channels(interaction) - - @self.bot.tree.command(name="test", description="Test command") - async def cmd_test(interaction: discord.Interaction): - await interaction.response.send_message("✅ Test command works!", ephemeral=True) - - @self.bot.tree.command(name="create-welcome-banner", description="Create a welcome banner in a channel") - @app_commands.describe(channel="Channel to create banner in") - async def cmd_create_welcome_banner(interaction: discord.Interaction, channel: discord.TextChannel): - await self.cmd_create_welcome_banner(interaction, channel) - - @self.bot.tree.command(name="list-commands", description="List all available bot commands") - async def cmd_list_commands_debug(interaction: discord.Interaction): - await self.cmd_list_commands_debug(interaction) - - @self.bot.tree.command(name="help", description="Show available commands") - async def cmd_help(interaction: discord.Interaction): - await self.cmd_help(interaction) - - async def cmd_status(self, interaction: discord.Interaction): - """Show server status""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - embed = discord.Embed( - title="📊 RedFlag Discord Server Status", - color=discord.Color.blue(), - description=f"Server: **{guild.name}**" - ) - - embed.add_field(name="👥 Members", value=str(guild.member_count), inline=True) - embed.add_field(name="💬 Channels", value=str(len(guild.channels)), inline=True) - embed.add_field(name="🎭 Roles", value=str(len(guild.roles)), inline=True) - embed.add_field(name="📅 Created", value=guild.created_at.strftime("%Y-%m-%d"), inline=True) - embed.add_field(name="👑 Owner", value=f"<@{guild.owner_id}>", inline=True) - embed.add_field(name="🚀 Boost Level", value=str(guild.premium_tier), inline=True) - - await interaction.response.send_message(embed=embed) - - async def cmd_create_channels(self, interaction: discord.Interaction): - """Create standard RedFlag channels""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - - results = [] - - # Create categories first - try: - # Community category - community_cat = await guild.create_category_channel("🌍 Community") - discord_env.update_category_ids("community", str(community_cat.id)) - results.append("✅ Community category") - - # Development category - dev_cat = await guild.create_category_channel("💻 Development") - discord_env.update_category_ids("development", str(dev_cat.id)) - results.append("✅ Development category") - - # Security category - security_cat = await guild.create_category_channel("🔒 Security") - discord_env.update_category_ids("security", str(security_cat.id)) - results.append("✅ Security category") - - except Exception as e: - logger.error(f"Error creating categories: {e}") - results.append(f"❌ Categories: {e}") - - # Create channels (with small delays to avoid rate limits) - await asyncio.sleep(1) - - # Community channels - try: - general = await guild.create_text_channel( - "general", - category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))), - reason="Community general discussion" - ) - discord_env.update_channel_ids("general", str(general.id)) - results.append("✅ #general") - - announcements = await guild.create_text_channel( - "announcements", - category=discord.Object(id=int(discord_env.get('COMMUNITY_CATEGORY_ID', 0))), - reason="Project announcements" - ) - discord_env.update_channel_ids("announcements", str(announcements.id)) - results.append("✅ #announcements") - - except Exception as e: - logger.error(f"Error creating community channels: {e}") - results.append(f"❌ Community channels: {e}") - - await asyncio.sleep(1) - - # Security channels - try: - security_alerts = await guild.create_text_channel( - "security-alerts", - category=discord.Object(id=int(discord_env.get('SECURITY_CATEGORY_ID', 0))), - reason="Security alerts and notifications" - ) - discord_env.update_channel_ids("security-alerts", str(security_alerts.id)) - results.append("✅ #security-alerts") - - except Exception as e: - logger.error(f"Error creating security channels: {e}") - results.append(f"❌ Security channels: {e}") - - await asyncio.sleep(1) - - # Development channels - try: - dev_chat = await guild.create_text_channel( - "dev-chat", - category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))), - reason="Development discussions" - ) - discord_env.update_channel_ids("dev-chat", str(dev_chat.id)) - results.append("✅ #dev-chat") - - bug_reports = await guild.create_text_channel( - "bug-reports", - category=discord.Object(id=int(discord_env.get('DEVELOPMENT_CATEGORY_ID', 0))), - reason="Bug reports and issues" - ) - discord_env.update_channel_ids("bug-reports", str(bug_reports.id)) - results.append("✅ #bug-reports") - - except Exception as e: - logger.error(f"Error creating development channels: {e}") - results.append(f"❌ Development channels: {e}") - - # Send results - embed = discord.Embed( - title="🔧 Channel Creation Results", - color=discord.Color.green() if "❌" not in str(results) else discord.Color.red(), - description="\n".join(results) - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - async def cmd_send_message(self, interaction: discord.Interaction, channel: str, message: str): - """Send a message to a specific channel""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Find channel by name - target_channel = discord.utils.get(guild.text_channels, name=channel.lower()) - if not target_channel: - await interaction.response.send_message(f"❌ Channel '{channel}' not found!", ephemeral=True) - return - - try: - await target_channel.send(message) - await interaction.response.send_message( - f"✅ Message sent to #{channel}!", ephemeral=True - ) - except Exception as e: - logger.error(f"Error sending message: {e}") - await interaction.response.send_message( - f"❌ Failed to send message: {e}", ephemeral=True - ) - - async def cmd_list_channels(self, interaction: discord.Interaction): - """List all channels and categories""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - embed = discord.Embed( - title="📋 Server Channels", - color=discord.Color.blue() - ) - - # List categories - categories = [c for c in guild.categories if c.name] - if categories: - category_text = "\n".join([f"**{c.name}** (ID: {c.id})" for c in categories]) - embed.add_field(name="📂 Categories", value=category_text or "None", inline=False) - - # List text channels - text_channels = [c for c in guild.text_channels if c.category] - if text_channels: - channel_text = "\n".join([f"#{c.name} (ID: {c.id})" for c in text_channels]) - embed.add_field(name="💬 Text Channels", value=channel_text or "None", inline=False) - - # List voice channels - voice_channels = [c for c in guild.voice_channels if c.category] - if voice_channels: - voice_text = "\n".join([f"🎤 {c.name} (ID: {c.id})" for c in voice_channels]) - embed.add_field(name="🎤 Voice Channels", value=voice_text or "None", inline=False) - - await interaction.response.send_message(embed=embed, ephemeral=True) - - async def cmd_create_category(self, interaction: discord.Interaction, name: str): - """Create a new category""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - try: - category = await guild.create_category_channel(name) - await interaction.response.send_message( - f"✅ Created category: **{name}** (ID: {category.id})", - ephemeral=True - ) - except Exception as e: - logger.error(f"Error creating category: {e}") - await interaction.response.send_message( - f"❌ Failed to create category: {e}", - ephemeral=True - ) - - async def cmd_create_test_channel(self, interaction: discord.Interaction): - """Create one simple test channel""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - try: - # Create a simple text channel - test_channel = await guild.create_text_channel( - "test-channel", - reason="Testing bot channel creation" - ) - - await interaction.response.send_message( - f"✅ Created test channel: **#{test_channel.name}**", - ephemeral=True - ) - - except Exception as e: - logger.error(f"Error creating test channel: {e}") - await interaction.response.send_message( - f"❌ Failed to create test channel: {e}", - ephemeral=True - ) - - async def cmd_create_redflag_channels(self, interaction: discord.Interaction): - """Create RedFlag development/support Discord channels""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - results = [] - - try: - # Create categories for community Discord - welcome_cat = await guild.create_category_channel("👋 Welcome & Info") - results.append("✅ Welcome & Info category") - - support_cat = await guild.create_category_channel("💬 Support & Help") - results.append("✅ Support & Help category") - - dev_cat = await guild.create_category_channel("🔧 Development") - results.append("✅ Development category") - - community_cat = await guild.create_category_channel("🌍 Community") - results.append("✅ Community category") - - await asyncio.sleep(1) - - # Welcome & Info channels - rules = await guild.create_text_channel( - "rules-and-info", - category=welcome_cat, - reason="Community rules and project information" - ) - results.append("✅ #rules-and-info") - - announcements = await guild.create_text_channel( - "announcements", - category=welcome_cat, - reason="Project announcements and releases" - ) - results.append("✅ #announcements") - - await asyncio.sleep(1) - - # Support & Help channels - general_support = await guild.create_text_channel( - "general-support", - category=support_cat, - reason="General RedFlag support and questions" - ) - results.append("✅ #general-support") - - installation = await guild.create_text_channel( - "installation-help", - category=support_cat, - reason="Help with RedFlag installation and setup" - ) - results.append("✅ #installation-help") - - bug_reports = await guild.create_text_channel( - "bug-reports", - category=support_cat, - reason="Bug reports and troubleshooting" - ) - results.append("✅ #bug-reports") - - await asyncio.sleep(1) - - # Development channels - general_dev = await guild.create_text_channel( - "general-development", - category=dev_cat, - reason="General development discussions" - ) - results.append("✅ #general-development") - - feature_requests = await guild.create_text_channel( - "feature-requests", - category=dev_cat, - reason="Feature requests and ideas" - ) - results.append("✅ #feature-requests") - - code_review = await guild.create_text_channel( - "code-review", - category=dev_cat, - reason="Code review and development collaboration" - ) - results.append("✅ #code-review") - - await asyncio.sleep(1) - - # Community channels - general_chat = await guild.create_text_channel( - "general-chat", - category=community_cat, - reason="Off-topic community chat" - ) - results.append("✅ #general-chat") - - homelab = await guild.create_text_channel( - "homelab-showcase", - category=community_cat, - reason="Share your homelab setups and RedFlag deployments" - ) - results.append("✅ #homelab-showcase") - - # Update .env with important channel IDs - discord_env.update_channel_ids("announcements", str(announcements.id)) - discord_env.update_channel_ids("general-support", str(general_support.id)) - discord_env.update_channel_ids("bug-reports", str(bug_reports.id)) - discord_env.update_channel_ids("general-development", str(general_dev.id)) - - except Exception as e: - logger.error(f"Error creating RedFlag community channels: {e}") - results.append(f"❌ Error: {e}") - - embed = discord.Embed( - title="🏠 RedFlag Community Discord Setup", - color=discord.Color.green() if "❌" not in str(results) else discord.Color.red(), - description="Created RedFlag development/support community channels:\n\n" + "\n".join(results) - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - async def cmd_create_roles(self, interaction: discord.Interaction): - """Create RedFlag community roles""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Only allow administrators to create roles - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ Only administrators can create roles!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - results = [] - - # Define RedFlag roles - redflag_roles = { - "🚩 RedFlag Lead Dev": discord.Color.red(), - "🛠 Backend Dev": discord.Color.blue(), - "🎨 Frontend Dev": discord.Color.green(), - "🔍 QA Tester": discord.Color.orange(), - "💬 Community Helper": discord.Color.purple(), - "👤 User": discord.Color.greyple(), - "👀 Lurker": discord.Color.dark_grey(), - } - - for role_name, role_color in redflag_roles.items(): - try: - # Check if role already exists - existing_role = discord.utils.get(guild.roles, name=role_name) - if existing_role: - results.append(f"⚠️ {role_name} already exists") - continue - - # Create the role - role = await guild.create_role( - name=role_name, - color=role_color, - reason="RedFlag community role creation", - mentionable=True - ) - results.append(f"✅ Created {role_name}") - - # Store role ID in .env for future reference - safe_name = role_name.replace("🚩 ", "").replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", "").lower().replace(" ", "_") - discord_env._config[f"ROLE_{safe_name.upper()}_ID"] = str(role.id) - - except Exception as e: - logger.error(f"Error creating role {role_name}: {e}") - results.append(f"❌ Failed to create {role_name}: {e}") - - embed = discord.Embed( - title="🎭 Role Creation Results", - color=discord.Color.green() if "❌" not in str(results) else discord.Color.red(), - description="\n".join(results) - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - async def cmd_role_menu(self, interaction: discord.Interaction): - """Show interactive role assignment menu""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Create the view with role buttons - view = discord.ui.View(timeout=180) # 3 minutes timeout - - # Available roles for self-assignment (excluding Lead Dev) - available_roles = [ - ("🛠 Backend Dev", discord.Color.blue()), - ("🎨 Frontend Dev", discord.Color.green()), - ("🔍 QA Tester", discord.Color.orange()), - ("💬 Community Helper", discord.Color.purple()), - ("👤 User", discord.Color.greyple()), - ("👀 Lurker", discord.Color.dark_grey()), - ] - - # Create buttons for each role - for role_name, role_color in available_roles: - button = discord.ui.Button( - label=role_name.replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", ""), - emoji=role_name.split()[0], # Get the emoji - style=discord.ButtonStyle.secondary - ) - - async def button_callback(interaction: discord.Interaction, current_role_name=role_name): - await self.handle_role_assignment(interaction, current_role_name) - - button.callback = button_callback - view.add_item(button) - - embed = discord.Embed( - title="🎭 Choose Your RedFlag Role", - description="Click a button below to assign yourself a role. You can change your role anytime!", - color=discord.Color.blue() - ) - embed.add_field( - name="🚩 RedFlag Lead Dev", - value="This role is assigned by administrators only", - inline=False - ) - embed.set_footer(text="You can only have one role at a time. Click again to change roles.") - - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - - async def cmd_assign_lead_dev(self, interaction: discord.Interaction, user: discord.Member): - """Assign RedFlag Lead Dev role (admin only)""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Only allow administrators to assign Lead Dev role - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ Only administrators can assign the Lead Dev role!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - - # Find the Lead Dev role - lead_role = discord.utils.get(guild.roles, name="🚩 RedFlag Lead Dev") - if not lead_role: - await interaction.followup.send("❌ Lead Dev role not found! Please create roles first.", ephemeral=True) - return - - try: - # Remove existing RedFlag roles from the user - redflag_role_prefixes = ["🚩 ", "🛠 ", "🎨 ", "🔍 ", "💬 ", "👤 ", "👀 "] - current_roles = [role for role in user.roles if any(role.name.startswith(prefix) for prefix in redflag_role_prefixes)] - - if current_roles: - await user.remove_roles(*current_roles, reason="Assigned Lead Dev role") - - # Assign Lead Dev role - await user.add_roles(lead_role, reason="Assigned by admin") - await interaction.followup.send(f"✅ Assigned **🚩 RedFlag Lead Dev** to {user.mention}", ephemeral=True) - - except Exception as e: - logger.error(f"Error assigning Lead Dev role: {e}") - await interaction.followup.send(f"❌ Failed to assign role: {e}", ephemeral=True) - - async def cmd_setup_welcome(self, interaction: discord.Interaction): - """Setup welcome channel with message and role selector""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Only allow administrators - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ Only administrators can setup the welcome channel!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - results = [] - - try: - # Find the general channel (try multiple names) - general_channel = None - possible_names = ["general", "🏠127.0.0.1", "🏠localhost", "welcome", "welcome-and-info"] - - for name in possible_names: - general_channel = discord.utils.get(guild.text_channels, name=name) - if general_channel: - break - - if not general_channel: - # If no specific channel found, just use the first text channel (any category) - logger.info(f"Using first available text channel: {guild.text_channels[0].name}") - general_channel = guild.text_channels[0] - logger.info(f"Selected channel: {general_channel.name} (Category: {general_channel.category.name if general_channel.category else 'No category'})") - - if not general_channel: - await interaction.followup.send("❌ Could not find any text channel to use!", ephemeral=True) - return - - # Rename the channel to localhost with house emoji - await general_channel.edit(name="🏠localhost", reason="Setup welcome channel") - results.append("✅ Renamed general to 🏠localhost") - - # Create welcome message with role selector - welcome_embed = discord.Embed( - title="🏠 Welcome to RedFlag", - description="**Self-hosted update management for homelabs**", - color=discord.Color.blue() - ) - - welcome_embed.add_field( - name="⚠️ ALPHA SOFTWARE", - value="This is experimental software in active development. Features may be broken, bugs are expected, and breaking changes happen frequently. Use at your own risk, preferably on test systems only.", - inline=False - ) - - welcome_embed.add_field( - name="🤝 Community & Support", - value="""**Discord Maintenance:** Full disclosure - Discord community management isn't my strongest area. If we grow over 100 users, I'll be looking to vet a moderator to help keep things organized. - -**Response Times:** I *should* get alerts and will try to respond timely, but this place is a community for us all to grow and share in. - -**Community Guidelines:** Small requests that are slightly off-topic are totally fine. We're building a community around homelabs, update management, and practical solutions - not a corporate support channel.""", - inline=False - ) - - welcome_embed.add_field( - name="🚀 Get Started", - value="1. **Choose Your Role** below - This helps us know how you're using RedFlag\n2. **Introduce Yourself** in #general-chat\n3. **Share Your Setup** in #homelab-showcase\n4. **Ask Questions** in #general-support", - inline=False - ) - - welcome_embed.set_footer(text="RedFlag - Simple, Honest, Homelab-first") - welcome_embed.set_thumbnail(url=guild.icon.url if guild.icon else None) - - # Create role selector view - view = discord.ui.View(timeout=None) # Persistent view - - # Available roles for self-assignment - available_roles = [ - ("🛠 Backend Dev", discord.Color.blue()), - ("🎨 Frontend Dev", discord.Color.green()), - ("🔍 QA Tester", discord.Color.orange()), - ("💬 Community Helper", discord.Color.purple()), - ("👤 User", discord.Color.greyple()), - ("👀 Lurker", discord.Color.dark_grey()), - ] - - # Create buttons for each role - for role_name, role_color in available_roles: - button = discord.ui.Button( - label=role_name.replace("🛠 ", "").replace("🎨 ", "").replace("🔍 ", "").replace("💬 ", "").replace("👤 ", "").replace("👀 ", ""), - emoji=role_name.split()[0], - style=discord.ButtonStyle.secondary, - custom_id=f"role_select_{role_name.replace(' ', '_').replace('🛠', '').replace('🎨', '').replace('🔍', '').replace('💬', '').replace('👤', '').replace('👀', '')}" - ) - - async def button_callback(interaction: discord.Interaction, current_role_name=role_name): - await self.handle_role_assignment(interaction, current_role_name) - - button.callback = button_callback - view.add_item(button) - - # Set channel topic with important info - topic = "🏠 Welcome! Use /role-menu to choose your role. RedFlag: Self-hosted update management for homelabs. ALPHA SOFTWARE - expect bugs!" - await general_channel.edit(topic=topic, reason="Set welcome channel topic") - - # Send the welcome message - await general_channel.send(embed=welcome_embed, view=view) - results.append("✅ Posted welcome message with role selector and channel topic") - - except Exception as e: - logger.error(f"Error setting up welcome channel: {e}") - results.append(f"❌ Error: {e}") - - embed = discord.Embed( - title="🏠 Welcome Channel Setup Complete", - color=discord.Color.green() if "❌" not in str(results) else discord.Color.red(), - description="\n".join(results) - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - async def cmd_create_version_channels(self, interaction: discord.Interaction): - """Create version-related channels""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Only allow administrators - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ Only administrators can create version channels!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - results = [] - - try: - # Create version category - version_cat = await guild.create_category_channel("📦 Version Management") - results.append("✅ Version Management category") - - await asyncio.sleep(1) - - # Main version channel - main_version = await guild.create_text_channel( - "🎯main", - category=version_cat, - reason="Main stable version discussion" - ) - results.append("✅ #main (stable version)") - - # Tagged versions channel - tagged_versions = await guild.create_text_channel( - "🏷️tagged", - category=version_cat, - reason="Tagged release versions discussion" - ) - results.append("✅ #tagged (release versions)") - - # Unstable dev channel - unstable_dev = await guild.create_text_channel( - "🔮unstable-developer", - category=version_cat, - reason="Unstable developer branch discussion" - ) - results.append("✅ #unstable-developer (dev branch)") - - # Update .env with channel IDs - discord_env.update_channel_ids("main_version", str(main_version.id)) - discord_env.update_channel_ids("tagged_versions", str(tagged_versions.id)) - discord_env.update_channel_ids("unstable_dev", str(unstable_dev.id)) - - except Exception as e: - logger.error(f"Error creating version channels: {e}") - results.append(f"❌ Error: {e}") - - embed = discord.Embed( - title="📦 Version Channels Created", - color=discord.Color.green() if "❌" not in str(results) else discord.Color.red(), - description="Created version management channels:\n\n" + "\n".join(results) - ) - - await interaction.followup.send(embed=embed, ephemeral=True) - - async def cmd_sync_commands(self, interaction: discord.Interaction): - """Force sync commands""" - # Only allow administrators - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ Only administrators can sync commands!", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - - try: - # Sync commands globally - synced = await self.bot.tree.sync() - await interaction.followup.send(f"✅ Synced {len(synced)} commands globally!", ephemeral=True) - logger.info(f"Manually synced {len(synced)} commands") - except Exception as e: - logger.error(f"Error syncing commands: {e}") - await interaction.followup.send(f"❌ Failed to sync commands: {e}", ephemeral=True) - - async def handle_role_assignment(self, interaction: discord.Interaction, role_name: str): - """Handle role assignment from button click""" - guild = self.bot.get_guild(self.server_id) - if not guild: - await interaction.response.send_message("❌ Could not find server!", ephemeral=True) - return - - # Find the role - target_role = discord.utils.get(guild.roles, name=role_name) - if not target_role: - await interaction.response.send_message("❌ Role not found! Please ask an admin to create roles first.", ephemeral=True) - return - - # Get all RedFlag roles (for removal) - redflag_role_prefixes = ["🚩 ", "🛠 ", "🎨 ", "🔍 ", "💬 ", "👤 ", "👀 "] - current_roles = [role for role in interaction.user.roles if any(role.name.startswith(prefix) for prefix in redflag_role_prefixes)] - - try: - # Remove existing RedFlag roles - if current_roles: - await interaction.user.remove_roles(*current_roles, reason="Role change via bot") - - # Add new role - await interaction.user.add_roles(target_role, reason="Self-assigned via bot") - - # Update the original message to show success - await interaction.response.edit_message( - content=f"✅ Successfully assigned role: **{role_name}**", - view=None # Remove buttons after selection - ) - - except Exception as e: - logger.error(f"Error assigning role {role_name}: {e}") - await interaction.response.send_message(f"❌ Failed to assign role: {e}", ephemeral=True) - - async def cmd_create_welcome_banner(self, interaction: discord.Interaction, channel: discord.TextChannel): - """Create a welcome banner in a channel""" - try: - # Check if user has admin permissions - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message("❌ This command requires Administrator permissions.", ephemeral=True) - return - - await interaction.response.defer() - - # Create simple welcome embed - embed = discord.Embed( - title="🏠 RedFlag", - description="Self-hosted update management for homelabs", - color=discord.Color.red() - ) - embed.add_field( - name="Links", - value="[GitHub](https://github.com/Fimeg/RedFlag) • [Issues](https://github.com/Fimeg/RedFlag/issues)", - inline=False - ) - embed.set_thumbnail(url="https://raw.githubusercontent.com/Fimeg/RedFlag/main/website/public/favicon.svg") - - # Send and pin the welcome message - message = await channel.send(embed=embed) - await message.pin() - - await interaction.followup.send(f"✅ Created welcome banner in #{channel.name}!", ephemeral=True) - logger.info(f"Created welcome banner in #{channel.name}") - - except Exception as e: - logger.error(f"Error converting announcement channel: {e}") - await interaction.followup.send(f"❌ Error converting channel: {e}", ephemeral=True) - - async def cmd_list_commands_debug(self, interaction: discord.Interaction): - """List all registered commands for debugging""" - try: - commands = self.bot.tree.get_commands(guild=discord.Object(id=self.server_id)) - command_list = [] - - for cmd in commands: - if hasattr(cmd, 'name') and hasattr(cmd, 'description'): - command_list.append(f"**/{cmd.name}** - {cmd.description}") - - embed = discord.Embed( - title="🔍 Registered Commands Debug", - description=f"Found {len(command_list)} commands:", - color=discord.Color.gold() - ) - - if command_list: - embed.add_field(name="Available Commands", value="\n".join(command_list), inline=False) - else: - embed.description = "No commands found!" - - await interaction.response.send_message(embed=embed, ephemeral=True) - - except Exception as e: - await interaction.response.send_message(f"❌ Error listing commands: {e}", ephemeral=True) - - async def cmd_help(self, interaction: discord.Interaction): - """Show help information""" - embed = discord.Embed( - title="🤖 RedFlag Discord Bot Help", - description="Interactive Discord server management commands", - color=discord.Color.blue() - ) - - commands_info = [ - ("`/status`", "📊 Show server status"), - ("`/create-channels`", "🔧 Create standard channels"), - ("`/create-redflag-channels`", "🏠 Create RedFlag community channels"), - ("`/create-roles`", "🎭 Create RedFlag community roles *(Admin only)*"), - ("`/setup-welcome`", "🏠 Setup welcome channel with role selector *(Admin only)*"), - ("`/create-version-channels`", "📦 Create version management channels *(Admin only)*"), - ("`/role-menu`", "🎮 Show interactive role assignment menu"), - ("`/assign-lead-dev`", "🚩 Assign Lead Dev role *(Admin only)*"), - ("`/list-channels`", "📋 List all channels"), - ("`/send-message`", "💬 Send message to channel"), - ("`/create-category`", "📂 Create new category"), - ("`/create-test-channel`", "🧪 Create one test channel"), - ("`/help`", "❓ Show this help"), - ] - - for cmd, desc in commands_info: - embed.add_field(name=cmd, value=desc, inline=False) - - embed.add_field( - name="🛡️ Security Features", - value="✅ Secure configuration management\n✅ Token protection\n✅ Rate limiting", - inline=False - ) - - embed.add_field( - name="🔄 Live Updates", - value="Configuration changes are saved instantly to .env file", - inline=False - ) - - embed.set_footer(text="RedFlag Security Discord Management v1.0") - - await interaction.response.send_message(embed=embed, ephemeral=True) - - async def print_server_status(self, guild): - """Print detailed server status to console""" - print(f"\n{'='*60}") - print(f"🚀 REDFLAG DISCORD SERVER STATUS") - print(f"{'='*60}") - print(f"Server Name: {guild.name}") - print(f"Server ID: {guild.id}") - print(f"Members: {guild.member_count}") - print(f"Channels: {len(guild.channels)}") - print(f"Roles: {len(guild.roles)}") - print(f"Owner: <@{guild.owner_id}>") - print(f"Created: {guild.created_at}") - print(f"Boost Level: {guild.premium_tier}") - print(f"{'='*60}\n") - - async def run(self): - """Start the bot""" - try: - # Connect to Discord - await self.bot.start(self.bot_token) - - except discord.errors.LoginFailure: - logger.error("❌ Invalid Discord bot token!") - logger.error("Please check your DISCORD_BOT_TOKEN in .env file") - sys.exit(1) - except Exception as e: - logger.error(f"❌ Failed to start bot: {e}") - sys.exit(1) - -def main(): - """Main entry point""" - print("🚀 Starting RedFlag Discord Management Bot...") - - # Check configuration - if not discord_env.is_configured(): - print("❌ Discord not configured!") - print("Please:") - print("1. Copy .env.example to .env") - print("2. Fill in your Discord bot token and server ID") - print("3. Run this script again") - sys.exit(1) - - # Create and run bot - bot = DiscordManager() - asyncio.run(bot.run()) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/discord/env_manager.py b/discord/env_manager.py deleted file mode 100755 index 5c46bb0..0000000 --- a/discord/env_manager.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -""" -Secure Discord Environment Manager -Handles loading Discord configuration from .env without exposing secrets -""" - -import os -import logging -from typing import Optional, Dict, Any -from dotenv import load_dotenv - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -class DiscordEnvManager: - """Secure environment manager for Discord configuration""" - - def __init__(self, env_file: str = ".env"): - self.env_file = env_file - self._config = {} - self._load_config() - - def _load_config(self): - """Load configuration from .env file""" - try: - load_dotenv(self.env_file) - self._config = { - 'DISCORD_BOT_TOKEN': os.getenv('DISCORD_BOT_TOKEN'), - 'DISCORD_SERVER_ID': os.getenv('DISCORD_SERVER_ID'), - 'DISCORD_APPLICATION_ID': os.getenv('DISCORD_APPLICATION_ID'), - 'DISCORD_PUBLIC_KEY': os.getenv('DISCORD_PUBLIC_KEY'), - 'SERVER_NAME': os.getenv('SERVER_NAME', 'RedFlag Security'), - 'ADMIN_ROLE_ID': os.getenv('ADMIN_ROLE_ID'), - 'GENERAL_CHANNEL_ID': os.getenv('GENERAL_CHANNEL_ID'), - 'ANNOUNCEMENTS_CHANNEL_ID': os.getenv('ANNOUNCEMENTS_CHANNEL_ID'), - 'SECURITY_ALERTS_CHANNEL_ID': os.getenv('SECURITY_ALERTS_CHANNEL_ID'), - 'DEV_CHAT_CHANNEL_ID': os.getenv('DEV_CHAT_CHANNEL_ID'), - 'BUG_REPORTS_CHANNEL_ID': os.getenv('BUG_REPORTS_CHANNEL_ID'), - 'COMMUNITY_CATEGORY_ID': os.getenv('COMMUNITY_CATEGORY_ID'), - 'DEVELOPMENT_CATEGORY_ID': os.getenv('DEVELOPMENT_CATEGORY_ID'), - 'SECURITY_CATEGORY_ID': os.getenv('SECURITY_CATEGORY_ID'), - } - - # Validate required fields - required_fields = ['DISCORD_BOT_TOKEN', 'DISCORD_SERVER_ID'] - missing = [field for field in required_fields if not self._config.get(field)] - if missing: - logger.error(f"Missing required environment variables: {missing}") - raise ValueError(f"Missing required fields: {missing}") - - logger.info("✅ Discord configuration loaded successfully") - - except Exception as e: - logger.error(f"Failed to load Discord configuration: {e}") - raise - - def get(self, key: str, default: Any = None) -> Optional[str]: - """Get configuration value""" - return self._config.get(key, default) - - def get_required(self, key: str) -> str: - """Get required configuration value""" - value = self._config.get(key) - if not value: - raise ValueError(f"Required environment variable {key} is not set") - return value - - def update_channel_ids(self, channel_name: str, channel_id: str): - """Update channel ID in config""" - channel_key = f"{channel_name.upper()}_CHANNEL_ID" - self._config[channel_key] = channel_id - - # Also update in file - self._update_env_file(channel_key, channel_id) - - def update_category_ids(self, category_name: str, category_id: str): - """Update category ID in config""" - category_key = f"{category_name.upper()}_CATEGORY_ID" - self._config[category_key] = category_id - - # Also update in file - self._update_env_file(category_key, category_id) - - def _update_env_file(self, key: str, value: str): - """Update .env file with new value""" - try: - env_path = os.path.join(os.path.dirname(__file__), self.env_file) - - # Read current file - if os.path.exists(env_path): - with open(env_path, 'r') as f: - lines = f.readlines() - else: - lines = [] - - # Update or add the line - updated = False - for i, line in enumerate(lines): - if line.startswith(f"{key}="): - lines[i] = f"{key}={value}\n" - updated = True - break - - if not updated: - lines.append(f"{key}={value}\n") - - # Write back to file - with open(env_path, 'w') as f: - f.writelines(lines) - - logger.info(f"✅ Updated {key} in {self.env_file}") - - except Exception as e: - logger.error(f"Failed to update {key} in {self.env_file}: {e}") - - def is_configured(self) -> bool: - """Check if the Discord bot is properly configured""" - return ( - self.get('DISCORD_BOT_TOKEN') and - self.get('DISCORD_SERVER_ID') and - self.get('DISCORD_APPLICATION_ID') - ) - - def mask_sensitive_info(self, text: str) -> str: - """Mask sensitive information in logs""" - sensitive_words = ['TOKEN', 'KEY'] - masked_text = text - - for word in sensitive_words: - if f"{word}_ID" not in masked_text: # Don't mask channel IDs - # Find and mask the value - import re - pattern = rf'{word}=\w+' - replacement = f'{word}=***MASKED***' - masked_text = re.sub(pattern, replacement, masked_text) - - return masked_text - -# Global instance for easy access -discord_env = DiscordEnvManager() - -# Convenience functions -def get_discord_config(): - """Get Discord configuration""" - return discord_env - -def is_discord_ready(): - """Check if Discord is ready for use""" - return discord_env.is_configured() \ No newline at end of file diff --git a/discord/requirements.txt b/discord/requirements.txt deleted file mode 100644 index 0f8ef0b..0000000 --- a/discord/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -discord.py>=2.4.0 -python-dotenv>=1.0.0 -aiohttp>=3.8.0 -asyncio-mqtt>=0.16.0 \ No newline at end of file diff --git a/discord/setup.py b/discord/setup.py deleted file mode 100644 index fb58709..0000000 --- a/discord/setup.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 -""" -RedFlag Discord Setup Assistant -Helps configure Discord bot for server management -""" - -import os -import sys -from dotenv import load_dotenv - -def setup_discord(): - """Interactive Discord setup""" - print("🚀 RedFlag Discord Bot Setup Assistant") - print("=" * 50) - - # Check if .env exists - env_file = ".env" - if not os.path.exists(env_file): - print(f"📝 Creating {env_file} from template...") - - if os.path.exists(".env.example"): - import shutil - shutil.copy(".env.example", env_file) - print(f"✅ Created {env_file} from .env.example") - else: - # Create basic .env file - with open(env_file, 'w') as f: - f.write("# Discord Bot Configuration\n") - f.write("DISCORD_BOT_TOKEN=your_bot_token_here\n") - f.write("DISCORD_SERVER_ID=your_server_id_here\n") - f.write("DISCORD_APPLICATION_ID=your_app_id_here\n") - f.write("DISCORD_PUBLIC_KEY=your_public_key_here\n") - f.write("\n# Server Settings\n") - f.write("SERVER_NAME=RedFlag Security\n") - f.write("ADMIN_ROLE_ID=\n") - print(f"✅ Created basic {env_file}") - - # Load environment - load_dotenv(env_file) - - print("\n📋 Discord Configuration Checklist:") - print("1. ✅ Discord Developer Portal: https://discord.com/developers/applications") - print("2. ✅ Create Application: Click 'New Application'") - print("3. ✅ Create Bot: Go to 'Bot' → 'Add Bot'") - print("4. ✅ Enable Privileged Intents:") - print(" - ✅ Server Members Intent") - print(" - ✅ Server Management Intent") - print(" - ✅ Message Content Intent") - print("5. ✅ OAuth2 URL Generator:") - print(" - ✅ Scope: bot") - print(" - ✅ Scope: applications.commands") - print(" - ✅ Permissions: Administrator (or specific)") - print("6. ✅ Invite Bot to Server") - print("7. ✅ Copy Values Below:") - - print("\n🔑 Required Discord Information:") - print("From your Discord Developer Portal, copy these values:") - print("-" * 50) - - # Get user input (with masking) - def get_sensitive_input(prompt, key): - value = input(f"{prompt}: ").strip() - if value: - # Update .env file - update_env_file(key, value) - # Show masked version - masked_value = value[:8] + "..." + value[-4:] if len(value) > 12 else value - print(f"✅ {key}: {masked_value}") - return value - - def update_env_file(key, value): - """Update .env file with value""" - env_path = os.path.join(os.path.dirname(__file__), env_file) - - # Read current file - with open(env_path, 'r') as f: - lines = f.readlines() - - # Update or add the line - updated = False - for i, line in enumerate(lines): - if line.startswith(f"{key}="): - lines[i] = f"{key}={value}\n" - updated = True - break - - if not updated: - lines.append(f"{key}={value}\n") - - # Write back to file - with open(env_path, 'w') as f: - f.writelines(lines) - - # Get required values - bot_token = get_sensitive_input("Discord Bot Token", "DISCORD_BOT_TOKEN") - server_id = get_sensitive_input("Discord Server ID", "DISCORD_SERVER_ID") - app_id = get_sensitive_input("Discord Application ID", "DISCORD_APPLICATION_ID") - public_key = get_sensitive_input("Discord Public Key", "DISCORD_PUBLIC_KEY") - - print("-" * 50) - print("🎉 Configuration Complete!") - print("\n📝 Next Steps:") - print("1. Run the Discord bot:") - print(" cd /home/memory/Desktop/Projects/RedFlag/discord") - print(" python discord_manager.py") - print("\n2. Available Commands (slash commands):") - print(" • /status - Show server status") - print(" • /create-channels - Create standard channels") - print(" • /list-channels - List all channels") - print(" • /send-message - Send message to channel") - print(" • /create-category - Create new category") - print(" • /help - Show all commands") - print("\n🔒 Security Note:") - print("• Your bot token is stored locally in .env") - print("• Never share the .env file") - print("• The bot only has Administrator permissions you grant it") - print("• All actions are logged locally") - -def main(): - """Main setup function""" - setup_discord() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/fix_agent_permissions.sh b/fix_agent_permissions.sh deleted file mode 100644 index f73f70d..0000000 --- a/fix_agent_permissions.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash - -# Fix RedFlag Agent Permissions Script -# This script fixes the systemd service permissions for the agent - -set -e - -echo "🔧 RedFlag Agent Permission Fix Script" -echo "======================================" -echo "" - -# Check if running as root or with sudo -if [ "$EUID" -ne 0 ]; then - echo "This script needs sudo privileges to modify systemd service files." - echo "You'll be prompted for your password." - echo "" - exec sudo "$0" "$@" -fi - -echo "✅ Running with sudo privileges" -echo "" - -# Step 1: Check current systemd service -echo "📋 Step 1: Checking current systemd service..." -SERVICE_FILE="/etc/systemd/system/redflag-agent.service" - -if [ ! -f "$SERVICE_FILE" ]; then - echo "❌ Service file not found: $SERVICE_FILE" - exit 1 -fi - -echo "✅ Service file found: $SERVICE_FILE" -echo "" - -# Step 2: Check if ReadWritePaths is already configured -echo "📋 Step 2: Checking current service configuration..." -if grep -q "ReadWritePaths=" "$SERVICE_FILE"; then - echo "✅ ReadWritePaths already configured" - grep "ReadWritePaths=" "$SERVICE_FILE" -else - echo "⚠️ ReadWritePaths not found - needs to be added" -fi -echo "" - -# Step 3: Backup original service file -echo "💾 Step 3: Creating backup of service file..." -cp "$SERVICE_FILE" "${SERVICE_FILE}.backup.$(date +%Y%m%d_%H%M%S)" -echo "✅ Backup created" -echo "" - -# Step 4: Add ReadWritePaths to service file -echo "🔧 Step 4: Adding ReadWritePaths to service file..." - -# Check if [Service] section exists -if ! grep -q "^\[Service\]" "$SERVICE_FILE"; then - echo "❌ [Service] section not found in service file" - exit 1 -fi - -# Add ReadWritePaths after [Service] section if not already present -if ! grep -q "ReadWritePaths=/var/lib/redflag" "$SERVICE_FILE"; then - # Use sed to add the line after [Service] - sed -i '/^\[Service\]/a ReadWritePaths=/var/lib/redflag /etc/redflag /var/log/redflag' "$SERVICE_FILE" - echo "✅ ReadWritePaths added to service file" -else - echo "✅ ReadWritePaths already present" -fi -echo "" - -# Step 5: Show the updated service file -echo "📄 Step 5: Updated service file:" -echo "--------------------------------" -grep -A 20 "^\[Service\]" "$SERVICE_FILE" | head -25 -echo "--------------------------------" -echo "" - -# Step 6: Create necessary directories -echo "📁 Step 6: Creating necessary directories..." -mkdir -p /var/lib/redflag/migration_backups -mkdir -p /var/log/redflag -mkdir -p /etc/redflag - -echo "✅ Directories created/verified" -echo "" - -# Step 7: Set proper permissions -echo "🔐 Step 7: Setting permissions..." -if id "redflag-agent" &>/dev/null; then - chown -R redflag-agent:redflag-agent /var/lib/redflag - chown -R redflag-agent:redflag-agent /var/log/redflag - echo "✅ Permissions set for redflag-agent user" -else - echo "⚠️ redflag-agent user not found - skipping permission setting" -fi -echo "" - -# Step 8: Reload systemd -echo "🔄 Step 8: Reloading systemd..." -systemctl daemon-reload -sleep 2 -echo "✅ Systemd reloaded" -echo "" - -# Step 9: Restart the agent -echo "🚀 Step 9: Restarting redflag-agent service..." -systemctl restart redflag-agent -sleep 3 -echo "✅ Service restarted" -echo "" - -# Step 10: Check service status -echo "📊 Step 10: Checking service status..." -echo "--------------------------------" -systemctl status redflag-agent --no-pager -n 10 -echo "--------------------------------" -echo "" - -# Step 11: Check logs -echo "📝 Step 11: Recent logs..." -echo "--------------------------------" -journalctl -u redflag-agent -n 20 --no-pager -echo "--------------------------------" -echo "" - -echo "🎉 Script completed!" -echo "" -echo "Next steps:" -echo "1. Wait 30 seconds for agent to stabilize" -echo "2. Run: sudo journalctl -u redflag-agent -f" -echo "3. Check if agent registers successfully" -echo "4. Verify in UI: http://localhost:3000/agents" -echo "" -echo "If the agent still fails, check:" -echo "- Database connection in /etc/redflag/config.json" -echo "- Network connectivity to aggregator-server" -echo "- Token validity in the database" \ No newline at end of file diff --git a/install.sh b/install.sh deleted file mode 100755 index 792a93f..0000000 --- a/install.sh +++ /dev/null @@ -1,383 +0,0 @@ -#!/bin/bash -set -e - -# RedFlag Agent Installation Script -# This script installs the RedFlag agent as a systemd service with proper security hardening - -REDFLAG_SERVER="http://localhost:8080" -AGENT_USER="redflag-agent" -AGENT_HOME="/var/lib/redflag-agent" -AGENT_BINARY="/usr/local/bin/redflag-agent" -SUDOERS_FILE="/etc/sudoers.d/redflag-agent" -SERVICE_FILE="/etc/systemd/system/redflag-agent.service" -CONFIG_DIR="/etc/redflag" -STATE_DIR="/var/lib/redflag" - -echo "=== RedFlag Agent Installation ===" -echo "" - -# Check if running as root -if [ "$EUID" -ne 0 ]; then - echo "ERROR: This script must be run as root (use sudo)" - exit 1 -fi - -# Detect architecture -ARCH=$(uname -m) -case "$ARCH" in - x86_64) - DOWNLOAD_ARCH="amd64" - ;; - aarch64|arm64) - DOWNLOAD_ARCH="arm64" - ;; - *) - echo "ERROR: Unsupported architecture: $ARCH" - echo "Supported: x86_64 (amd64), aarch64 (arm64)" - exit 1 - ;; -esac - -echo "Detected architecture: $ARCH (using linux-$DOWNLOAD_ARCH)" -echo "" - -# Step 1: Create system user -echo "Step 1: Creating system user..." -if id "$AGENT_USER" &>/dev/null; then - echo "✓ User $AGENT_USER already exists" -else - useradd -r -s /bin/false -d "$AGENT_HOME" -m "$AGENT_USER" - echo "✓ User $AGENT_USER created" -fi - -# Create home directory if it doesn't exist -if [ ! -d "$AGENT_HOME" ]; then - mkdir -p "$AGENT_HOME" - chown "$AGENT_USER:$AGENT_USER" "$AGENT_HOME" - echo "✓ Home directory created" -fi - -# Stop existing service if running (to allow binary update) -if systemctl is-active --quiet redflag-agent 2>/dev/null; then - echo "" - echo "Existing service detected - stopping to allow update..." - systemctl stop redflag-agent - sleep 2 - echo "✓ Service stopped" -fi - -# Step 2: Download agent binary -echo "" -echo "Step 2: Downloading agent binary..." -echo "Downloading from ${REDFLAG_SERVER}/api/v1/downloads/linux-${DOWNLOAD_ARCH}..." - -# Download to temporary file first (to avoid root permission issues) -TEMP_FILE="/tmp/redflag-agent-${DOWNLOAD_ARCH}" -echo "Downloading to temporary file: $TEMP_FILE" - -# Try curl first (most reliable) -if curl -sL "${REDFLAG_SERVER}/api/v1/downloads/linux-${DOWNLOAD_ARCH}" -o "$TEMP_FILE"; then - echo "✓ Download successful, moving to final location" - mv "$TEMP_FILE" "${AGENT_BINARY}" - chmod 755 "${AGENT_BINARY}" - chown root:root "${AGENT_BINARY}" - echo "✓ Agent binary downloaded and installed" -else - echo "✗ Download with curl failed" - # Fallback to wget if available - if command -v wget >/dev/null 2>&1; then - echo "Trying wget fallback..." - if wget -q "${REDFLAG_SERVER}/api/v1/downloads/linux-${DOWNLOAD_ARCH}" -O "$TEMP_FILE"; then - echo "✓ Download successful with wget, moving to final location" - mv "$TEMP_FILE" "${AGENT_BINARY}" - chmod 755 "${AGENT_BINARY}" - chown root:root "${AGENT_BINARY}" - echo "✓ Agent binary downloaded and installed (using wget fallback)" - else - echo "ERROR: Failed to download agent binary" - echo "Both curl and wget failed" - echo "Please ensure ${REDFLAG_SERVER} is accessible" - # Clean up temp file if it exists - rm -f "$TEMP_FILE" - exit 1 - fi - else - echo "ERROR: Failed to download agent binary" - echo "curl failed and wget is not available" - echo "Please ensure ${REDFLAG_SERVER} is accessible" - # Clean up temp file if it exists - rm -f "$TEMP_FILE" - exit 1 - fi -fi - -# Clean up temp file if it still exists -rm -f "$TEMP_FILE" - -# Set SELinux context for binary if SELinux is enabled -if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then - echo "SELinux detected, setting file context for binary..." - restorecon -v "${AGENT_BINARY}" 2>/dev/null || true - echo "✓ SELinux context set for binary" -fi - -# Step 3: Install sudoers configuration -echo "" -echo "Step 3: Installing sudoers configuration..." -cat > "$SUDOERS_FILE" <<'SUDOERS_EOF' -# RedFlag Agent minimal sudo permissions -# This file grants the redflag-agent user limited sudo access for package management -# Generated automatically during RedFlag agent installation - -# APT package management commands (Debian/Ubuntu) -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get update -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get upgrade -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install --dry-run --yes * - -# DNF package management commands (RHEL/Fedora/Rocky/Alma) -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf makecache -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf upgrade -y * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install --assumeno --downloadonly * - -# Docker operations -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker pull * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker image inspect * -redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker manifest inspect * - -# Directory operations for RedFlag -redflag-agent ALL=(root) NOPASSWD: /bin/mkdir -p /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/mkdir -p /var/lib/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chown redflag-agent:redflag-agent /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chown redflag-agent:redflag-agent /var/lib/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chmod 755 /etc/redflag -redflag-agent ALL=(root) NOPASSWD: /bin/chmod 755 /var/lib/redflag - -# Migration operations (for existing installations) -redflag-agent ALL=(root) NOPASSWD: /bin/mv /etc/aggregator /etc/redflag.backup.* -redflag-agent ALL=(root) NOPASSWD: /bin/mv /var/lib/aggregator/* /var/lib/redflag/ -redflag-agent ALL=(root) NOPASSWD: /bin/rmdir /var/lib/aggregator 2>/dev/null || true -redflag-agent ALL=(root) NOPASSWD: /bin/rmdir /etc/aggregator 2>/dev/null || true -SUDOERS_EOF - -chmod 440 "$SUDOERS_FILE" - -# Validate sudoers file -if visudo -c -f "$SUDOERS_FILE" &>/dev/null; then - echo "✓ Sudoers configuration installed and validated" -else - echo "ERROR: Sudoers configuration is invalid" - rm -f "$SUDOERS_FILE" - exit 1 -fi - -# Step 4: Create configuration and state directories -echo "" -echo "Step 4: Creating configuration and state directories..." -mkdir -p "$CONFIG_DIR" -chown "$AGENT_USER:$AGENT_USER" "$CONFIG_DIR" -chmod 755 "$CONFIG_DIR" - -# Create state directory for acknowledgment tracking (v0.1.19+) -mkdir -p "$STATE_DIR" -chown "$AGENT_USER:$AGENT_USER" "$STATE_DIR" -chmod 755 "$STATE_DIR" -echo "✓ Configuration and state directories created" - -# Set SELinux context for directories if SELinux is enabled -if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then - echo "Setting SELinux context for directories..." - restorecon -Rv "$CONFIG_DIR" "$STATE_DIR" 2>/dev/null || true - echo "✓ SELinux context set for directories" -fi - -# Step 5: Install systemd service -echo "" -echo "Step 5: Installing systemd service..." -cat > "$SERVICE_FILE" < " REGISTRATION_TOKEN - else - echo "" - echo "IMPORTANT: Registration token required!" - echo "" - echo "Since you're running this via pipe, you need to:" - echo "" - echo "Option 1 - One-liner with token:" - echo " curl -sfL ${REDFLAG_SERVER}/api/v1/install/linux | sudo bash -s -- YOUR_TOKEN" - echo "" - echo "Option 2 - Download and run interactively:" - echo " curl -sfL ${REDFLAG_SERVER}/api/v1/install/linux -o install.sh" - echo " chmod +x install.sh" - echo " sudo ./install.sh" - echo "" - echo "Skipping registration for now." - echo "Please register manually after installation." - fi -fi - -# Check if agent is already registered -if [ -f "$CONFIG_DIR/config.json" ]; then - echo "" - echo "[INFO] Agent already registered - configuration file exists" - echo "[INFO] Skipping registration to preserve agent history" - echo "[INFO] If you need to re-register, delete: $CONFIG_DIR/config.json" - echo "" -elif [ -n "$REGISTRATION_TOKEN" ]; then - echo "" - echo "Registering agent..." - - # Create config file and register - cat > "$CONFIG_DIR/config.json" <