feat: add bot-triggered deployment via /update deploy command

- Added Docker socket mount to docker-compose.prod.yml
- Added project directory mount for git operations
- Added performDeploy, isDeployAvailable methods to UpdateService
- Added /update deploy subcommand for Discord-triggered deployments
- Added deploy-related embeds to update.view.ts
This commit is contained in:
syntaxbullet
2026-01-30 14:26:38 +01:00
parent 73531f38ae
commit ebefd8c0df
4 changed files with 236 additions and 2 deletions

View File

@@ -469,4 +469,109 @@ export class UpdateService {
}
// Don't clear rollback cache here - rollback file persists
}
// =========================================================================
// Bot-Triggered Deployment Methods
// =========================================================================
private static readonly DEPLOY_TIMEOUT_MS = 300_000; // 5 minutes for full deploy
/**
* Get the deploy directory path from environment or default
*/
static getDeployDir(): string {
return process.env.DEPLOY_DIR || "/app/deploy";
}
/**
* Check if deployment is available (docker socket accessible)
*/
static async isDeployAvailable(): Promise<boolean> {
try {
await execWithTimeout("docker --version", 5000);
return true;
} catch {
return false;
}
}
/**
* Perform a full deployment: git pull + docker compose rebuild
* This will restart the container with the new code
*
* @param onProgress - Callback for progress updates
* @returns Object with success status, output, and commit info
*/
static async performDeploy(onProgress?: (step: string) => void): Promise<{
success: boolean;
previousCommit: string;
newCommit: string;
output: string;
error?: string;
}> {
const deployDir = this.getDeployDir();
let previousCommit = "";
let newCommit = "";
let output = "";
try {
// 1. Get current commit
onProgress?.("Getting current version...");
const { stdout: currentSha } = await execWithTimeout(
`cd ${deployDir} && git rev-parse --short HEAD`,
DEFAULT_TIMEOUT_MS
);
previousCommit = currentSha.trim();
output += `📍 Current: ${previousCommit}\n`;
// 2. Pull latest changes
onProgress?.("Pulling latest code...");
const { stdout: pullOutput } = await execWithTimeout(
`cd ${deployDir} && git pull origin main`,
DEFAULT_TIMEOUT_MS
);
output += `📥 Pull: ${pullOutput.includes("Already up to date") ? "Already up to date" : "Updated"}\n`;
// 3. Get new commit
const { stdout: newSha } = await execWithTimeout(
`cd ${deployDir} && git rev-parse --short HEAD`,
DEFAULT_TIMEOUT_MS
);
newCommit = newSha.trim();
output += `📍 New: ${newCommit}\n`;
// 4. Rebuild and restart container (this will kill the current process)
onProgress?.("Rebuilding and restarting...");
// Use spawn with detached mode so the command continues after we exit
const { spawn } = await import("child_process");
const deployProcess = spawn(
"sh",
["-c", `cd ${deployDir} && docker compose -f docker-compose.prod.yml up -d --build`],
{
detached: true,
stdio: "ignore"
}
);
deployProcess.unref();
output += `🚀 Deploy triggered - container will restart\n`;
return {
success: true,
previousCommit,
newCommit,
output
};
} catch (error) {
return {
success: false,
previousCommit,
newCommit,
output,
error: error instanceof Error ? error.message : String(error)
};
}
}
}