deploy-remote: privileged rename + deep merge in migrate_data_root

rename() of a directory requires write on the directory itself; mariadb/
redis data roots are owned by the containers' mapped subuids (100998),
so the deploy user cannot move them. Use run_privileged mv (numeric
ownership preserved = rootless mapping contract) and skip the chown pass
for romm via the new keep mode — chowning mapped subuid data to the
deploy uid breaks the in-container mysql/redis users. Collisions with
rescued anonymous-volume stubs now merge one level deep instead of
stranding real data under /opt. Reverse the podman rm order so the
dependent romm container is removed before its backends.
This commit is contained in:
Frank
2026-09-01 13:42:31 -07:00
parent a18bb62d30
commit 26b0d4c357
+37 -19
View File
@@ -537,25 +537,43 @@ wait_container_healthy() {
# the unit; re-running the handler on an already-active unit restarts only the
# selected unit.
migrate_data_root() { # $1 = old /opt path, $2 = new data-root path (same fs: rename/merge)
local from="$1" to="$2" entry
migrate_data_root() { # $1=old /opt path, $2=new data root, $3=chown|keep (default chown)
local from="$1" to="$2" mode="${3:-chown}" entry sub base
[[ -d "$from" ]] || return 0
if [[ -d "$to" && -n "$(ls -A "$to" 2>/dev/null)" ]]; then
# The destination already holds seeded state (env file, rescued
# anonymous-volume data). Merge the old root into it without ever
# overwriting an existing entry, then drop the emptied source.
shopt -s dotglob
for entry in "$from"/*; do
[[ -e "$entry" ]] || continue
mv -n -- "$entry" "$to/"
done
shopt -u dotglob
rmdir -- "$from" 2>/dev/null || \
echo "WARN: $from not empty after merge (collisions kept old side); left in place" >&2
else
mv -- "$from" "$to"
# Renaming a directory needs WRITE permission on the directory being
# renamed itself. Compose-era data roots contain directories owned by
# the containers' mapped host uids (mariadb/redis land in the subuid
# range as 100998), which the deploy user cannot rename. Privileged mv
# preserves numeric ownership — the rootless mapping contract.
mkdir -p "$to"
shopt -s dotglob
for entry in "$from"/*; do
[[ -e "$entry" || -L "$entry" ]] || continue
base="${entry##*/}"
if [[ -e "$to/$base" ]]; then
# Collides with pre-seeded state (env file) or a rescued
# anonymous-volume stub: merge one level deep, never overwriting
# an existing entry.
if [[ -d "$entry" && -d "$to/$base" ]]; then
for sub in "$entry"/*; do
[[ -e "$sub" || -L "$sub" ]] || continue
run_privileged mv -n -- "$sub" "$to/$base/"
done
run_privileged rmdir -- "$entry" 2>/dev/null || \
echo "WARN: $entry not empty after merge; left in place" >&2
else
echo "WARN: collision on $to/$base kept existing; $entry left in place" >&2
fi
continue
fi
run_privileged mv -- "$entry" "$to/"
done
shopt -u dotglob
run_privileged rmdir -- "$from" 2>/dev/null || \
echo "WARN: $from not empty after migration; left in place" >&2
if [[ "$mode" == chown ]]; then
chown -R "$(id -u):$(id -g)" "$to"
fi
chown -R "$(id -u):$(id -g)" "$to"
}
deploy_romm_stack() {
@@ -580,7 +598,7 @@ deploy_romm_stack() {
local vols
vols="$(podman inspect -f '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}} {{end}}{{end}}' romm 2>/dev/null || true)"
podman stop -t 60 romm-mariadb romm-redis romm || true
podman rm romm-mariadb romm-redis romm || true
podman rm romm romm-redis romm-mariadb || true
for vol in $vols; do
tmp="$(mktemp -d)"
podman volume export --output "$tmp/v.tar" "$vol"
@@ -589,7 +607,7 @@ deploy_romm_stack() {
tar --skip-old-files -C /home/dank/Containers/romm -xf "$tmp/v.tar"
rm -rf -- "$tmp"
done
migrate_data_root /opt/romm /home/dank/Containers/romm
migrate_data_root /opt/romm /home/dank/Containers/romm keep
systemctl --user start romm-mariadb.service
wait_container_healthy romm-mariadb 120 || return 1
systemctl --user start romm-redis.service