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: {