diff --git a/web/actions/projects.ts b/web/actions/projects.ts index 3d1c983a..63319b52 100644 --- a/web/actions/projects.ts +++ b/web/actions/projects.ts @@ -555,6 +555,7 @@ export async function deleteService( const reusableBackupIds: string[] = []; if (!runningDeployment || !runningDeployment.containerId) { + const latestBackupIds: string[] = []; for (const volume of volumes) { const latestBackup = await db .select({ id: volumeBackups.id }) @@ -569,17 +570,19 @@ export async function deleteService( .limit(1) .then((r) => r[0]); - if (!latestBackup) { - throw new Error( - "Stateful service must be running long enough to create a recoverable backup before deletion", - ); - } + if (latestBackup) latestBackupIds.push(latestBackup.id); + } + if (latestBackupIds.length === volumes.length) { await db .update(volumeBackups) .set({ isDeletionBackup: true }) - .where(eq(volumeBackups.id, latestBackup.id)); - reusableBackupIds.push(latestBackup.id); + .where(inArray(volumeBackups.id, latestBackupIds)); + reusableBackupIds.push(...latestBackupIds); + } else if (!service.lockedServerId) { + throw new Error( + "Stateful service must have a locked server or completed backups before deletion", + ); } } diff --git a/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/configuration/page.tsx b/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/configuration/page.tsx index 77e9a995..6e1f93db 100644 --- a/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/configuration/page.tsx +++ b/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/configuration/page.tsx @@ -164,11 +164,12 @@ export default function ConfigurationPage() { <>

- - No completed backup is available for every volume. + + This service is not currently running. {" "} - Delete will fail unless the service is running so a - fresh deletion backup can be created. + {service.lockedServerId + ? "A fresh backup will be created from its stopped volumes before deletion." + : "No server or completed backup is available for its volumes, so deletion cannot start."} )} diff --git a/web/lib/inngest/functions/service-deletion-workflow.ts b/web/lib/inngest/functions/service-deletion-workflow.ts index 847359db..15bed589 100644 --- a/web/lib/inngest/functions/service-deletion-workflow.ts +++ b/web/lib/inngest/functions/service-deletion-workflow.ts @@ -117,9 +117,16 @@ export const serviceDeletionWorkflow = inngest.createFunction( const createdBackupIds = await step.run( "start-delete-backups", async () => { - const deployment = setup.runningDeployment; - if (!deployment?.containerId) { - throw new Error("No active deployment found for deletion backup"); + const backupTarget = setup.runningDeployment?.containerId + ? setup.runningDeployment + : setup.service.lockedServerId + ? { + serverId: setup.service.lockedServerId, + containerId: null, + } + : null; + if (!backupTarget) { + throw new Error("No server found for deletion backup"); } const ids: string[] = []; @@ -133,16 +140,16 @@ export const serviceDeletionWorkflow = inngest.createFunction( volumeId: volume.id, volumeName: volume.name, serviceId, - serverId: deployment.serverId, + serverId: backupTarget.serverId, status: "pending", storagePath, isDeletionBackup: true, }); - await enqueueWork(deployment.serverId, "backup_volume", { + await enqueueWork(backupTarget.serverId, "backup_volume", { backupId, serviceId, - containerId: deployment.containerId, + containerId: backupTarget.containerId, volumeName: volume.name, storagePath, storageConfig: {