diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bec1d..df027e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,23 @@ range. Versions before 0.3.0 predate the GitHub releases and are reconstructed f Merge commits, automated `Tagged ` commits and version bumps are omitted, as are bullets that only restate the release note above them. +## Unreleased + +### Fixed + +- **`roll restore --force` works on a stopped environment.** The restore only ran `roll env down` + when one of the environment's containers was running. Docker refuses to remove a volume that any + container references, stopped ones included, so a stopped environment kept every volume in use + and the restore died on the first one. `stopEnvironment` in `utils/backup.sh` now takes the + environment down whenever it has containers, running or stopped, and passes `--remove-orphans`, + so containers of services the current `.env.roll` no longer enables are removed as well; volumes + are kept. `roll restore-full` shares the change. +- **A volume `roll restore --force` cannot remove now stops the restore with a reason.** The + `docker volume rm` result was never checked, so under roll's errexit the restore ended right after + `Removing existing volume: …` with exit 1 and no error message. It now prints Docker's error and + the containers still holding the volume, then exits 1 without writing into it. Volumes restored + earlier in the same run keep their new contents. + ## [0.8.5](https://github.com/epartment/rolldev/releases/tag/0.8.5) — 2026-10-07 ### Added diff --git a/docs/backup-restore.md b/docs/backup-restore.md index 23c6472..30cf452 100644 --- a/docs/backup-restore.md +++ b/docs/backup-restore.md @@ -317,6 +317,21 @@ roll restore --force docker volume rm ${ROLL_ENV_NAME}_dbdata ``` +**`--force` fails with "Could not remove volume … volume is in use":** + +Docker will not remove a volume that a container still references, and a stopped container counts. +`roll restore` runs `roll env down --remove-orphans` first whenever the environment has containers, +running or stopped, so this error means something else still holds the volume: a container from +outside this environment, or an `env down` that failed. The restore stops at that volume, before +writing into it, and names the containers holding it. Volumes restored earlier in the same run keep +their new contents, so rerun the whole restore once the containers are gone: + +```bash +# Remove the container by the name the error printed, then retry +docker rm +roll restore --force +``` + **Encrypted backup won't decrypt:** ```bash # Ensure GPG is installed diff --git a/utils/backup.sh b/utils/backup.sh index c776d13..47a1381 100644 --- a/utils/backup.sh +++ b/utils/backup.sh @@ -379,7 +379,23 @@ function restoreVolume() { if [[ $RESTORE_FORCE -eq 1 ]]; then logMessage INFO "Removing existing volume: $volume_name" logVerbose "Executing: docker volume rm $volume_name" - docker volume rm "$volume_name" >/dev/null 2>&1 + # Docker refuses to remove a volume that any container references, stopped ones + # included. Unchecked, that failure ended the restore under errexit with no message at + # all, and without errexit the idempotent `docker volume create` below would let the + # archive be extracted over the old contents and report success. Either way a volume + # that could not be removed must never be restored into, and the user needs the reason. + local rm_output + if ! rm_output="$(docker volume rm "$volume_name" 2>&1)"; then + local holders + holders="$(docker ps -a --filter volume="$volume_name" --format '{{.Names}}' 2>/dev/null | tr '\n' ' ')" || true + holders="${holders% }" + logMessage ERROR "Could not remove volume $volume_name: $rm_output" + if [[ -n "$holders" ]]; then + logMessage ERROR "Containers still holding it (running or stopped): $holders" + fi + logMessage ERROR "Remove them first - 'roll env down' for this environment's containers, 'docker rm ' for any other - then rerun the restore." + return 1 + fi else logMessage ERROR "Volume $volume_name already exists. Use --force to overwrite." return 1 @@ -754,6 +770,12 @@ function signEnvironmentCertificate() { ## Restore and restore-full disagreed here: a full restore has no .env.roll yet, because the ## configuration is inside the backup, and the env commands require one. The guard covers both, ## so the two callers no longer need separate copies. +## +## Stopped containers count: Docker will not remove a volume that any container references, running +## or not, so a stopped environment left in place made every `restore --force` fail on its first +## volume. `--remove-orphans` takes the containers of services the current .env.roll no longer +## enables as well (an Elasticsearch left behind after a switch to OpenSearch still holds esdata). +## Volumes are kept - restoreVolume decides per volume what to replace. function stopEnvironment() { logVerbose "Environment path: ${ROLL_ENV_PATH}" @@ -762,15 +784,15 @@ function stopEnvironment() { return 0 fi - logVerbose "Checking for running containers with project: ${ROLL_ENV_NAME}" - local running_containers - running_containers="$(docker ps --filter "label=com.docker.compose.project=${ROLL_ENV_NAME}" -q)" || true + logVerbose "Checking for containers with project: ${ROLL_ENV_NAME}" + local env_containers + env_containers="$(docker ps -a --filter "label=com.docker.compose.project=${ROLL_ENV_NAME}" -q)" || true - if [[ -n "${running_containers}" ]]; then + if [[ -n "${env_containers}" ]]; then logMessage INFO "Stopping environment..." - "${ROLL_DIR}/bin/roll" env down || true + "${ROLL_DIR}/bin/roll" env down --remove-orphans || true else - logVerbose "No running containers for this environment" + logVerbose "No containers for this environment" fi return 0