diff --git a/src/app/api/deploy/route.ts b/src/app/api/deploy/route.ts index 2278762..01b03aa 100644 --- a/src/app/api/deploy/route.ts +++ b/src/app/api/deploy/route.ts @@ -1,7 +1,11 @@ import { NextRequest, NextResponse } from "next/server"; import testAuth from "../../../lib/server/auth"; import { StatusCodes } from "http-status-codes"; -import { deployApp, toggleDeployment } from "@/core/deployment"; +import { + deployApp, + removeDeployment, + toggleDeployment, +} from "@/core/deployment"; import { Application, Port } from "@/lib/types"; export async function POST(req: NextRequest): Promise { @@ -59,3 +63,28 @@ export async function PUT(req: NextRequest): Promise { message: "ok", }); } + +export async function DELETE(req: NextRequest): Promise { + const deployId = req.nextUrl.searchParams.get("deployId"); + const auth = await testAuth(req); + + if (auth) { + return NextResponse.json( + { message: auth }, + { status: StatusCodes.UNAUTHORIZED }, + ); + } + + if (!deployId) { + return NextResponse.json( + { message: "Body missing deployId" }, + { status: StatusCodes.BAD_REQUEST }, + ); + } + + await removeDeployment(deployId); + + return NextResponse.json({ + message: "ok", + }); +} diff --git a/src/app/deployments/page.tsx b/src/app/deployments/page.tsx index 5d7d6d6..48e9271 100644 --- a/src/app/deployments/page.tsx +++ b/src/app/deployments/page.tsx @@ -11,7 +11,11 @@ import { import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Plus, Play, Pause, Square, RotateCcw } from "lucide-react"; -import { getDeployments, toggleDeployment } from "@/lib/dockship/deploy"; +import { + getDeployments, + removeDeployment, + toggleDeployment, +} from "@/lib/dockship/deploy"; import { getApps } from "@/lib/dockship/application"; import { cn } from "@/lib/utils"; import DeployAppDialog from "@/components/dialogs/DeployApp"; @@ -53,6 +57,11 @@ export default function DeploymentsPage() { await toggleDeployment(deployId, action); }; + const remove = async (deployId: string) => { + console.log(`Removing deployment ${deployId}`); + await removeDeployment(deployId); + }; + return ( - {}} /> + remove(dep.container)} /> diff --git a/src/core/deployment.ts b/src/core/deployment.ts index 1d0b52b..ce6ce1d 100644 --- a/src/core/deployment.ts +++ b/src/core/deployment.ts @@ -9,6 +9,7 @@ import pLimit from "p-limit"; import { randomUUID } from "crypto"; import { cloneRepo } from "./git"; import tar from "tar-fs"; +import { read, write } from "@/lib/server/file"; const docker = new Docker(); @@ -180,3 +181,21 @@ export async function toggleDeployment( console.error(`Failed to ${action} container ${containerId}:`, err); } } + +export async function removeDeployment(deployId: string) { + const apps = await read(() => [] as Application[], "data", "apps.json"); + const app = apps.find((a) => a.deployments.includes(deployId)); + if (app) { + app.deployments = app.deployments.filter((id) => id !== deployId); + await write(apps, "data", "apps.json"); + } + + try { + const container = docker.getContainer(deployId); + await container.stop(); + await container.remove({ force: true }); + await docker.pruneImages(); + } catch (err) { + console.error(`Failed to remove container ${deployId}:`, err); + } +} diff --git a/src/lib/dockship/deploy.ts b/src/lib/dockship/deploy.ts index ba194fa..25ec260 100644 --- a/src/lib/dockship/deploy.ts +++ b/src/lib/dockship/deploy.ts @@ -82,3 +82,21 @@ export async function toggleDeployment( return "ok"; } + +export async function removeDeployment(deployId: string) { + const nodes = await getNodes(); + + await Promise.all( + nodes.map( + async (node) => + await axios.delete( + `http://${node.ip}:${process.env.PORT}/api/deploy?deployId=${deployId}`, + { + headers: { Authorization: `ApiKey ${node.key}` }, + }, + ), + ), + ); + + return "ok"; +}