Merge claude/2

This commit is contained in:
librelad 2026-07-21 20:10:12 +01:00
commit 728830f74d
4 changed files with 68 additions and 0 deletions

View File

@ -0,0 +1,58 @@
#!/bin/bash
# A containers-root wipe (fresh install, or a relocation of the containers root)
# removes an app's project directory but NOT the container the daemon still holds
# in its own state. Those containers are stranded: their compose file, config and
# bind-mount sources are gone, so they can never start correctly again.
#
# They are not inert. `restart: unless-stopped` makes the daemon retry them on
# every restart, and Docker materialises each missing bind-mount source before
# handing off to runc — creating an empty DIRECTORY even where the mount is a
# file. That is how a fresh install ends up with <app>.config as a directory: the
# install restarts the rootless daemon, the daemon resurrects the previous
# install's container, and the stubs it leaves behind collide with the install's
# own file copies moments later.
#
# Sweep them once the daemon is up, so the next restart has nothing to resurrect.
# Scoped to containers whose project directory sits under the LibrePortal
# containers root — unrelated containers on the host are never touched.
dockerRemoveStrandedContainers()
{
local silent_flag="${1:-silent}"
[[ -d "$containers_dir" ]] || return 0
# `systemctl restart` returns once the unit is active, which can be before
# dockerd finishes initialising. Give the socket a bounded moment to answer.
local attempt=0
until runFileOp docker info >/dev/null 2>&1; do
attempt=$((attempt + 1))
if [[ "$attempt" -ge 15 ]]; then
[[ "$silent_flag" == "loud" ]] && isNotice "Docker not responding — skipping stranded container sweep."
return 0
fi
sleep 2
done
local listing
listing=$(runFileOp docker ps -a --format '{{.Names}} {{.Label "com.docker.compose.project.working_dir"}}' 2>/dev/null)
[[ -z "$listing" ]] && return 0
local removed=0 name work_dir
while IFS=$'\t' read -r name work_dir; do
[[ -z "$name" || -z "$work_dir" ]] && continue
# Only LibrePortal's own container tree, and only when the project
# directory is provably gone.
[[ "$work_dir" == "$containers_dir"* ]] || continue
[[ -d "$work_dir" ]] && continue
if runFileOp docker rm -f "$name" >/dev/null 2>&1; then
removed=$((removed + 1))
[[ "$silent_flag" == "loud" ]] && isNotice "Removed stranded container '$name' (project dir $work_dir is gone)."
fi
done <<< "$listing"
if [[ "$removed" -gt 0 ]]; then
isSuccessful "Removed $removed stranded container(s) left without a project directory"
fi
}

View File

@ -183,6 +183,12 @@ EOL"
local result; result=$(dockerCommandRunInstallUser "systemctl --user restart docker")
checkSuccess "Reload the systemd user docker service"
# The restart above runs the daemon's container-restore pass, which
# resurrects containers left over from a previous install whose project
# directory this install already wiped. Clear them now so the next
# restart can't re-create their bind-mount sources as stub directories.
dockerRemoveStrandedContainers "loud"
local result; result=$(sudo cp $sysctl $sysctl.bak)
checkSuccess "Backing up sysctl file"

View File

@ -31,6 +31,7 @@ docker_scripts=(
"docker/command/docker_run_install.sh"
"docker/command/docker_run.sh"
"docker/command/run_privileged.sh"
"docker/command/stranded_containers.sh"
"docker/compose/copy_build_context.sh"
"docker/compose/restart_after_update.sh"
"docker/compose/setup_compose_yml.sh"

View File

@ -411,6 +411,7 @@ declare -gA LP_FN_MAP=(
[dockerPruneAppNetworks]="docker/network/network_prune.sh"
[dockerRemoveApp]="docker/app/docker/remove_app.sh"
[dockerRemoveAppImages]="docker/app/uninstall/remove_images.sh"
[dockerRemoveStrandedContainers]="docker/command/stranded_containers.sh"
[dockerRestartApp]="docker/app/docker/restart_app.sh"
[dockerRestartAppViaInstall]="docker/app/functions/function_restart_app.sh"
[dockerServiceStart]="docker/service/start_docker.sh"
@ -1416,6 +1417,7 @@ declare -gA LP_FN_ROOT=(
[dockerPruneAppNetworks]="scripts"
[dockerRemoveApp]="scripts"
[dockerRemoveAppImages]="scripts"
[dockerRemoveStrandedContainers]="scripts"
[dockerRestartApp]="scripts"
[dockerRestartAppViaInstall]="scripts"
[dockerServiceStart]="scripts"
@ -2454,6 +2456,7 @@ dockerInstallApp() { unset -f dockerInstallApp; __lpAutoload "${install_scripts_
dockerPruneAppNetworks() { unset -f dockerPruneAppNetworks; __lpAutoload "${install_scripts_dir}docker/network/network_prune.sh"; dockerPruneAppNetworks "$@"; }
dockerRemoveApp() { unset -f dockerRemoveApp; __lpAutoload "${install_scripts_dir}docker/app/docker/remove_app.sh"; dockerRemoveApp "$@"; }
dockerRemoveAppImages() { unset -f dockerRemoveAppImages; __lpAutoload "${install_scripts_dir}docker/app/uninstall/remove_images.sh"; dockerRemoveAppImages "$@"; }
dockerRemoveStrandedContainers() { unset -f dockerRemoveStrandedContainers; __lpAutoload "${install_scripts_dir}docker/command/stranded_containers.sh"; dockerRemoveStrandedContainers "$@"; }
dockerRestartApp() { unset -f dockerRestartApp; __lpAutoload "${install_scripts_dir}docker/app/docker/restart_app.sh"; dockerRestartApp "$@"; }
dockerRestartAppViaInstall() { unset -f dockerRestartAppViaInstall; __lpAutoload "${install_scripts_dir}docker/app/functions/function_restart_app.sh"; dockerRestartAppViaInstall "$@"; }
dockerServiceStart() { unset -f dockerServiceStart; __lpAutoload "${install_scripts_dir}docker/service/start_docker.sh"; dockerServiceStart "$@"; }