Skip to content

24/ rollback - #46

Merged
Alimedhat000 merged 9 commits into
mainfrom
24/rollback
May 29, 2026
Merged

24/ rollback#46
Alimedhat000 merged 9 commits into
mainfrom
24/rollback

Conversation

@Alimedhat000

Copy link
Copy Markdown
Owner

closes #24

Alimedhat000 and others added 7 commits May 29, 2026 07:49
Backend: prunedAt column, per-deployment subdirs, symlink activation, pruning (KEEP_COUNT=5), POST /api/deployments/:id/rollback, Caddy root -> {appId}/current

Worker: extractStaticOutput per-deployment, fix nixpacks --env-file -> --env (v1.41.0 compat)

Frontend: rollback button with confirm dialog, GitHub URL auto-parse (owner/repo/branch/subdir), subdir dropdown for coolify-examples

Infra: API mounts shipyard_sites volume

Closes #24
- mkdir -p target before docker cp (was failing with ENOENT)
- use rmSync instead of silent try/catch unlinkSync
  (avoids EEXIST when path is a directory, not a symlink)
API mounts sites volume :ro, can't write symlinks. Move filesystem
ops to worker (has :rw). API validates only, then enqueues a
type:"rollback" job. Worker runs processRollback: validates dir
exists, calls activateDeployment, updates DB.

Closes #24
feat(web): highlight active deployment in list

API now returns activeDeploymentId alongside deployments. Frontend
highlights the active row with a background tint and an ACTIVE badge.
Also fixes unused path import in rollback.ts.
- storage.test.ts: 8 tests for getDeploymentDir, getCurrentSymlinkPath,
  activateDeployment (first/re-deploy/missing dir), pruneDeployments
- rollback.test.ts: 4 tests for processRollback (not found, bad status,
  missing dir, success path)
- deployments.test.ts: updated 6 tests — API no longer does fs ops,
  only validates and returns data

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds rollback support by preserving per-deployment static artifacts, switching Caddy to serve a current symlink, and exposing rollback controls through the API and dashboard.

Changes:

  • Adds per-deployment storage helpers, rollback worker handling, pruning metadata, and Caddy root updates.
  • Adds API validation/queueing for rollback plus deployment list active-state data.
  • Adds dashboard rollback confirmation UI and documentation for the symlink activation model.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/worker/src/deployments/storage.ts Adds deployment directory, activation symlink, and pruning helpers.
packages/worker/src/deployments/rollback.ts Adds worker-side rollback processing.
packages/worker/src/deployments/strategies/nixpacks.ts Writes static artifacts into deployment-specific directories and activates via symlink.
packages/worker/src/infrastructure/caddy/config-builder.ts Points static file roots at sites/{appId}/current.
packages/worker/src/index.ts Dispatches rollback queue jobs.
packages/worker/src/config/env.ts Adds deployment retention count configuration.
packages/api/src/services/deployments.ts Returns active deployment state and validates rollback targets.
packages/api/src/routes/deployments.ts Adds POST /api/deployments/:id/rollback.
packages/api/src/routes/apps.ts Marks deploy queue jobs with type.
packages/api/src/config/env.ts Adds sites directory config for API.
packages/web/src/hooks/useDeployments.ts Supports new deployments response shape and rollback mutation.
packages/web/src/pages/Dashboard.tsx Adds rollback UI on deployment rows.
packages/shared/src/schema.ts Adds deployments.prunedAt.
packages/shared/src/types/queue.ts Adds queue job type discriminator.
drizzle/0008_nosy_wrecking_crew.sql Adds pruned_at migration.
drizzle/meta/_journal.json Registers migration 0008.
drizzle/meta/0008_snapshot.json Adds schema snapshot for migration 0008.
packages/api/test/unit/deployments.test.ts Adds rollback service unit tests.
packages/worker/test/unit/deployments/storage.test.ts Adds storage helper/pruning tests.
packages/worker/test/unit/deployments/rollback.test.ts Adds rollback worker tests.
packages/worker/test/unit/infrastructure/caddy-config.test.ts Updates Caddy root expectations.
docs/adr/0014-symlink-activation-model.md Documents symlink-based activation.
docs/adr/0013-volume-based-storage.md Updates storage ADR for per-deployment artifacts.
docs/adr/0006-static-caddy-config.md Updates static Caddy config ADR for symlink rollback.
docker-compose.yaml Mounts sites volume into API container.
CONTEXT.md Updates project context for volume storage and symlink activation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

and(
eq(deployments.appId, appId),
eq(deployments.status, "success"),
inArray(deployments.prunedAt, [null as unknown as Date]),
Comment on lines +36 to +38
fs.rmSync(symlinkPath, { force: true, recursive: true });

fs.symlinkSync(deploymentId, symlinkPath, "dir");
// Activate via symlink swap, then prune old deployments
const sitesDir = env.SITES_DIR;
activateDeployment(sitesDir, app.id, deploymentId);
await pruneDeployments(db, app.id, sitesDir, env.DEPLOYMENT_KEEP_COUNT);
Comment on lines +122 to +127
if (deployment.prunedAt) {
throw Object.assign(
new Error("Cannot rollback: deployment artifacts have been pruned"),
{ statusCode: 410 },
);
}
Comment on lines +114 to +120
if (deployment.status !== "success") {
throw Object.assign(
new Error(
`Cannot rollback: deployment has status "${deployment.status}"`,
),
{ statusCode: 422 },
);
Comment on lines +24 to +26
if (row.status !== "success") {
throw new Error(`Cannot rollback: deployment has status "${row.status}"`);
}
Comment thread packages/web/src/pages/Dashboard.tsx Outdated
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
STATUS_DOT[d.status] ?? STATUS_DOT.idle
deployments.map((d: Deployment) => {
const isRollbackable = d.status === "success" && !d.prunedAt;
- fix: use isNull() instead of IN(NULL) so prune query matches rows
- fix: atomic symlink swap via tmp + rename to avoid Caddy serving
errors
- fix: add prunedAt check in worker to close TOCTOU window
- fix: defer pruneDeployments until after status=success for correct
retention
- fix: check artifact dir on disk in API before enqueueing rollback
- fix: exclude active deployment from ROLLBACK button in UI
- fix: correct stale JSDoc return type for listDeployments

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Comment on lines +132 to +146
const depDir = path.join(getEnv().SITES_DIR, deployment.appId, deployment.id);
if (!fs.existsSync(depDir)) {
throw Object.assign(
new Error("Cannot rollback: deployment directory does not exist on disk"),
{ statusCode: 410 },
);
}

const [app] = await db
.select()
.from(apps)
.where(eq(apps.id, deployment.appId));
if (!app || app.organizationId !== orgId) {
return { deployment: null, app: null };
}
Comment on lines +113 to +116
res.json({
deployment: result.deployment,
app: result.app,
});
Comment on lines +148 to +152
if (app.activeDeploymentId === deployment.id) {
throw Object.assign(new Error("Deployment is already active"), {
statusCode: 409,
});
}
Comment on lines +117 to +123
if (deployment.status !== "success") {
throw Object.assign(
new Error(
`Cannot rollback: deployment has status "${deployment.status}"`,
),
{ statusCode: 422 },
);
<button
type="button"
onClick={() => setRollbackTarget(d.id)}
className="font-mono text-[10px] tracking-widest text-ship-buoy/40 hover:text-ship-buoy opacity-0 group-hover:opacity-100 transition-all"
@Alimedhat000
Alimedhat000 merged commit e9f6e6a into main May 29, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rollback does not restore previous deployment files

2 participants