diff --git a/docker-compose.yml b/docker-compose.yml index 33513c8..72d9a12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: db: image: postgres:17-alpine - container_name: kyoko_db + container_name: aurora_db environment: - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} @@ -12,7 +12,7 @@ services: - ./src/db/data:/var/lib/postgresql/data - ./src/db/log:/var/log/postgresql app: - container_name: kyoko_app + container_name: aurora_app image: kyoko-app build: context: . @@ -38,7 +38,7 @@ services: command: bun run dev studio: - container_name: kyoko_studio + container_name: aurora_studio image: kyoko-app build: context: . diff --git a/src/commands/admin/reload.ts b/src/commands/admin/reload.ts new file mode 100644 index 0000000..2e6ca7c --- /dev/null +++ b/src/commands/admin/reload.ts @@ -0,0 +1,82 @@ +import { createCommand } from "@lib/utils"; +import { KyokoClient } from "@/lib/BotClient"; +import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; +import { createErrorEmbed, createSuccessEmbed, createWarningEmbed } from "@lib/embeds"; + +export const reload = createCommand({ + data: new SlashCommandBuilder() + .setName("reload") + .setDescription("Reloads all commands") + .addBooleanOption(option => + option + .setName("redeploy") + .setDescription("Pull latest changes from git and restart the bot") + .setRequired(false) + ) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + execute: async (interaction) => { + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + const redeploy = interaction.options.getBoolean("redeploy") ?? false; + + if (redeploy) { + const { exec } = await import("child_process"); + const { promisify } = await import("util"); + const { writeFile, utimes } = await import("fs/promises"); + const execAsync = promisify(exec); + + try { + await interaction.editReply({ + embeds: [createWarningEmbed("Pulling latest changes and restarting...", "Redeploy Initiated")] + }); + + const { stdout, stderr } = await execAsync("git pull"); + + if (stderr && !stdout) { + throw new Error(stderr); + } + + await interaction.editReply({ + embeds: [createSuccessEmbed(`Git Pull Output:\n\`\`\`\n${stdout}\n\`\`\`\nRestarting process...`, "Update Successful")] + }); + + // Write context for post-restart notification + await writeFile(".restart_context.json", JSON.stringify({ + channelId: interaction.channelId, + userId: interaction.user.id, + timestamp: Date.now() + })); + + // Trigger restart by touching entry point + const now = new Date(); + await utimes("src/index.ts", now, now); + + } catch (error) { + console.error(error); + await interaction.editReply({ + embeds: [createErrorEmbed(`Failed to redeploy:\n\`\`\`\n${error instanceof Error ? error.message : String(error)}\n\`\`\``, "Redeploy Failed")] + }); + } + return; + } + + try { + const start = Date.now(); + await KyokoClient.loadCommands(true); + const duration = Date.now() - start; + + // Deploy commands + await KyokoClient.deployCommands(); + + const embed = createSuccessEmbed( + `Successfully reloaded ${KyokoClient.commands.size} commands in ${duration}ms.`, + "System Reloaded" + ); + + await interaction.editReply({ embeds: [embed] }); + } catch (error) { + console.error(error); + await interaction.editReply({ embeds: [createErrorEmbed("An error occurred while reloading commands. Check console for details.", "Reload Failed")] }); + } + } +}); \ No newline at end of file diff --git a/src/commands/system/webhook.ts b/src/commands/admin/webhook.ts similarity index 100% rename from src/commands/system/webhook.ts rename to src/commands/admin/webhook.ts diff --git a/src/commands/system/reload.ts b/src/commands/system/reload.ts deleted file mode 100644 index 1391c1d..0000000 --- a/src/commands/system/reload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createCommand } from "@lib/utils"; -import { KyokoClient } from "@/lib/BotClient"; -import { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { createErrorEmbed } from "@lib/embeds"; - -export const reload = createCommand({ - data: new SlashCommandBuilder() - .setName("reload") - .setDescription("Reloads all commands") - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - execute: async (interaction) => { - await interaction.deferReply({ flags: MessageFlags.Ephemeral }); - - try { - await KyokoClient.loadCommands(true); - const embed = new EmbedBuilder() - .setTitle("✅ System Reloaded") - .setDescription(`Successfully reloaded ${KyokoClient.commands.size} commands.`) - .setColor("Green"); - - // Deploy commands - await KyokoClient.deployCommands(); - - await interaction.editReply({ embeds: [embed] }); - } catch (error) { - console.error(error); - await interaction.editReply({ embeds: [createErrorEmbed("An error occurred while reloading commands. Check console for details.", "Reload Failed")] }); - } - } -}); \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts index ba1b9ec..82fa04d 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -8,6 +8,29 @@ const event: Event = { execute: async (c) => { console.log(`Ready! Logged in as ${c.user.tag}`); schedulerService.start(); + + // Check for restart context + const { readFile, unlink } = await import("fs/promises"); + const { createSuccessEmbed } = await import("@lib/embeds"); + + try { + const contextData = await readFile(".restart_context.json", "utf-8"); + const context = JSON.parse(contextData); + + // Validate context freshness (e.g., ignore if older than 5 minutes) + if (Date.now() - context.timestamp < 5 * 60 * 1000) { + const channel = await c.channels.fetch(context.channelId); + if (channel && channel.isSendable()) { + await channel.send({ + embeds: [createSuccessEmbed("Bot is back online! Redeploy successful.", "System Online")] + }); + } + } + + await unlink(".restart_context.json"); + } catch (error) { + // Ignore errors (file not found, etc.) + } }, };