feat(projects): preserve managed volumes on project rename#2919
feat(projects): preserve managed volumes on project rename#2919NeurekaSoftware wants to merge 53 commits into
Conversation
Project rename now migrates implicit Docker Compose managed volumes to the new project-scoped names while leaving explicit volume names alone. The migration copies data safely, aborts on target conflicts, and rolls back if the project update fails.
Use the repository's existing containerd errdefs dependency for Docker not-found checks so the backend build does not require an undeclared moby errdefs module.
Treat existing new-name Compose volumes as recoverable only when they already carry the expected Compose project and volume labels. Stale partial targets are cleaned up before copying again, completed migrations no longer block saving, and rollback cleanup errors are surfaced with focused coverage.
Make project rename volume migration preflight target-name conflicts, copy with disk-space checks, and remove source volumes only after the project update is staged. Add a short-lived KV journal for interrupted rename recovery and focused coverage for recovery and safety helpers. AI-Assisted: Implemented with ChatGPT/Codex under user direction.
Remove the awk dependency from the volume copy disk-space check so the helper works with minimal images, force-remove newly created target volumes during rollback, and refresh the project editor draft after failed saves. Go tests were skipped per request. AI-assisted change.
Use BusyBox for project volume copy operations so the pre-copy disk-space check can run against the actual target volume filesystem. Keep copy helpers long enough to read failure logs, then remove them through the existing cleanup path. Go tests were skipped per request. AI-assisted change.
Resolve unknown project status from live compose services before planning a rename so stopped projects do not need a start-stop cycle and volume migration planning sees the stopped state. Keep running projects blocked and add regressions for stale stopped and stale running runtime states. Go tests skipped per request. AI-assisted change by Codex.
Plan project volume renames from the post-save compose preview, verify live stopped state before any rename, block attached source volumes, and clean copy helpers with an uncancelled timeout context.
Remove the managed volume rename note from the Unreleased section so the changelog does not include the unintended entry.
Remove the extra UpdateProject arguments from the rename-status test and apply the Internal suffix to unexported project update and compose volume helpers.
Treat missing committed target volumes as recoverable during rename journal completion so stale journals can clear. Also refactor project update and volume rename helpers to satisfy the current backend lint rules.
…s-managed-volumes
|
Ill have to look into this more, but doesnt just recreating the project do this? Alos note: the name field in arcane ui is pretty much just the name for the folder and for internal refernce of it it doesnt get passed to docker (i dont think :O) anymore or at least it shouldnt |
Just an example, my "media-server" stack has 13 volumes. Renaming the project / renaming the folder is the same as changing the stack name. Managed volumes are named So renaming my "media-server" stack to "media-services" will result in brand new volumes being created when I bring it up again, unless I manually rename all previous volumes. The exception to the stack name changing when the folder name changes is when you have a |
|
I just confirmed, having the stack name in the compose file should make the volumes stable but arcane doesn't respect that value for some reason. When using docker via the CLI, you can have a This is why this change is needed. It adds complexity to renaming projects but it ensures nothing ever breaks when managing projects via arcane. |
Reproduction StepsScenario 1: No
|
|
Arcane doesn't seem to respect scenario 2 either, which probably needs its own separate bug report. Once the bug with arcane not respecting scenario 2 is fixed, this PR should be modified to exclude migrating volumes when a project is renamed if |
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can stay up-to-date and reviewed. |
…at/rename-project-also-renames-managed-volumes # Conflicts: # backend/api/handlers/projects.go # backend/internal/services/project_service.go # frontend/src/routes/(app)/projects/[projectId]/+page.svelte
Mark project rename journals after managed source volumes are removed so recovery can observe the completed volume cleanup phase. Add regression coverage around the save-and-commit boundary.
1 similar comment
Capture Arcane runtime entrypoints separately from default commands, create copy helpers with explicit helper args, and skip expected inaccessible paths during volume capacity probes. Add regressions for entrypoint-based images and tolerant probe walks.
Preserved target volume errors now explain the data-loss avoidance behavior when source volume state is unsafe. Project file changes rely on the single apply-time revision check, and regression coverage verifies preserved targets and stale revision rollback during project rename.
Implicit project volumes now remain eligible for rename migration when preview compose content computes the new project prefix from a top-level name. The preview coverage verifies compose-editor renames still migrate nginx_data to web_data while preserving explicit-name handling.
1 similar comment
Keep project-state rename journals clear after rollback restores the database, but persist a separate cleanup record for target volumes that could not be removed. Recovery retries those cleanup records without deleting preserved target data, with regressions for retry and Docker-unavailable cleanup.
Recover source-cleanup-pending rename journals when target volumes disappear but source volumes still exist. Rollback now restores the old project state, cleans safe copied targets, and clears the journal so future project updates are not blocked.
Ignore non-string compose volume name values when deciding which volumes use explicit names. Load project file scan defaults without applying global config side effects so race-enabled backend tests can run project helpers in parallel.
Return directory rollback failures after restoring database state so recovery leaves the rename journal available for retry. Update the directory rollback regression test to cover a failed first recovery followed by a successful retry.
|
Hey @kmendell, last ping from me, I swear: this is ready for review now. This PR has changed significantly since it was first opened, so I updated the PR body to accurately reflect the current state of the branch. Greptile is passing now, and all tests pass as well. I also updated the Mermaid chart I posted earlier because rollback and recovery changed quite a bit. The updated chart is now in the PR body so everything is in one place. My work here is done unless you need anything else. Just let me know. Here is a video of everything working, including volume migration through both project renames and a Compose top-level Screencast_20260613_131934.webm |
|
ill try to look later today |
|
The first thing ill note juts by glancing, all of the project_volume files under services/ move those to pkg/projects as the only thing i wnat under services is the actual service files . Style nit for sure, but iot just makes things cleaner |
Move project volume rename planning, copy, rollback, and recovery helpers out of services into pkg/projects/volumerename. Share volume helper container utilities through pkg/libarcane/volumehelper while keeping ProjectService focused on orchestration.
Route committed project rename recovery through the existing source cleanup path and make the shared cleanup logs phase-neutral. This preserves cleanup and rollback behavior while removing the duplicate implementation that tripped dupl.
Move project volume rename code into the main projects package and update callers to use the consolidated API. Relocate current-container mount discovery into projects so dockerutil no longer depends on projects.
All done. Let me know if you need any other changes. :) |
Checklist
mainbranchWhat This PR Implements
When a stopped Arcane project is renamed, Arcane now keeps eligible Docker Compose project-scoped volumes aligned with the new project name.
For example, renaming a project from
nginxtowebcan copy data fromnginx_dataintoweb_data, so the renamed project keeps using the same application data instead of starting with a new empty volume.Fixed external volumes and volumes with a fixed Compose
name:are left alone.Fixes: #2898
Changes Made
name:is treated as the effective project name when it is usable and not interpolated.Safety And Recovery
If anything fails before the project update is committed, Arcane rolls back the project files, directory, database state, and copied target volumes when it is safe to do so.
If the original source volume is missing during rollback, Arcane preserves the copied target volume to avoid deleting the only remaining data copy.
If the project update commits but old source-volume cleanup fails, the project stays renamed and Arcane keeps a recovery journal so cleanup can be retried later.
On startup, Arcane reads rename journals and either finishes committed rename cleanup or rolls back incomplete renames based on the current project state.
Testing Done
cd backend && go test ./...pnpm -C frontend checkAI Tool Used (if applicable)
Codex assisted with implementation, test design, and PR-description cleanup.
Additional Context
Without this change, Docker Compose can create new empty project-scoped volumes after a rename. The old data may still exist in the old volume, but the renamed project no longer points at it, which can look like data loss.
Simplified Rename Flow
flowchart TD A["Rename requested"] --> B{"Project stopped?"} B -- "No" --> C["Block rename"] B -- "Yes" --> D["Find eligible managed volumes"] D --> E{"Safe to migrate?"} E -- "No" --> F["Stop before changing project state"] E -- "Yes" --> G["Copy old volume data to new volumes"] G --> H["Save project rename"] H --> I{"Save committed?"} I -- "No" --> J["Rollback files, directory, database, and safe volume changes"] I -- "Yes" --> K["Remove old source volumes"] K --> L{"Cleanup complete?"} L -- "Yes" --> M["Clear rename journal"] L -- "No" --> N["Keep journal for startup recovery"]Disclaimer Greptiles Reviews use AI, make sure to check over its work.
To better help train Greptile on our codebase, if the comment is useful and valid Like the comment, if its not helpful or invalid Dislike
To have Greptile Re-Review the changes, mention
greptileai.Greptile Summary
This PR implements volume preservation during Docker Compose project renames by introducing a write-ahead journal pattern that tracks each rename phase (
started→targets_copied→old_volumes_removed→project_state_committed) and supports full rollback/recovery on restart. The changes touch the project service, a new KV-backed journal, a new volume-copy pipeline using helper containers, a CLIinternal-volume-helpersubcommand, and compose-preview logic for pre-flight volume planning.project_rename_journal.go(729 lines) defines the rename phases andRecoverProjectRenameJournalsruns on startup and before eachUpdateProject, resuming or rolling back any interrupted rename. The rollback path correctly handles the case where both old and new directories already exist by moving the new path to a hidden conflict directory.project_volume_rename.go(777 lines) spawns helper containers to copy volume data with capacity pre-check, usingstdcopy.StdCopyfor demuxed stdout/stderr; the composename:field rename path is handled via a preview directory so the new volume names are computed before any file changes are committed.ListByPrefixnow escapes\,%, and_before appending the SQLLIKEwildcard, preventing journal-key collisions for project IDs containing those characters.Confidence Score: 4/5
This PR is safe to merge; the journal/recovery machinery is well-tested and all previously-raised concerns appear addressed.
The implementation is thorough, with 1196-line recovery tests covering all journal phases. Every specific concern from the previous review threads (compose-name rename path, SQL wildcard escaping, walk abort, stdout/stderr mixing, recovery gating non-rename updates, directory conflict on rollback) has been resolved. The one remaining edge — a Commit() failure after project_state_committed that isn't a projectRenameSourceCleanupInternalError leaves the journal stuck until the next UpdateProject or restart — is intentional by design and recovery will resume it. No new blocking issues found.
No files require special attention; project_rename_journal.go and project_volume_rename.go are the highest-risk new files but both have strong test coverage.
Comments Outside Diff (1)
backend/internal/services/project_volume_rename.go, line 2001-2013 (link)When
removedOldis non-empty (i.e.,Commit()partially succeeded by deleting some old volumes) and the restore loop accumulates arestoreErr, the function returns early before callingrollbackCreatedTargets. This leaves all the new target volumes (e.g.,web_data) alive in Docker while the database transaction has already been rolled back, so the project still refers to the old name — the new volumes become permanently orphaned and require manual cleanup.The condition is reached when
Commit()removes at least one old volume then fails on a later one, the caller's DB transaction rolls back,Rollback()is triggered via the deferred handler, and the recreation/copy of any removed volume then fails.rollbackCreatedTargetsshould be called as a best-effort cleanup even whenrestoreErr != nil.Prompt To Fix With AI
backend/internal/services/project_rename_journal.go, line 513-515 (link)UpdateProjectwhen both directory paths are externally deletedWhen neither
oldPathnornewPathexists, the function returns a hard error. This error propagates up throughrollbackProjectRenameJournalInternal→recoverProjectRenameJournalInternal→recoverProjectRenameJournalForProjectInternal, and since the journal is never cleared on this path, every subsequentUpdateProjectcall for that project will hit the same error indefinitely.This condition is reachable without any operator error: if Arcane restarts while a rename journal is in the
startedphase (crash before any directory rename occurred) and the project directory was deleted externally between the crash and restart, both paths are absent and the project becomes permanently unmodifiable via the API.Consider treating "both missing" as a warning-and-continue (the directory state is already unrecoverable regardless), rather than a hard error, so volume and database state recovery can still proceed and the journal can be cleared.
Prompt To Fix With AI
Reviews (36): Last reviewed commit: "fix(projects): keep rename journal on di..." | Re-trigger Greptile