@@ -144,6 +144,54 @@ incus_delete_container() {
144144 msg_ok " Deleted ${name} "
145145}
146146
147+ # PVE-parity: after create-phase / configure failures, offer delete (60s auto-remove).
148+ # Install-script failures already use incus_run_install_script_with_recovery.
149+ incus_offer_orphan_cleanup () {
150+ [[ -n " ${_INCUS_INSIDE_CONTAINER:- } " ]] && return 0
151+ [[ " ${INCUS_ORPHAN_HANDLED:- } " == " 1" ]] && return 0
152+ [[ " ${INCUS_CT_CREATED:- } " != " 1" ]] && return 0
153+ [[ " ${INCUS_BUILD_OK:- } " == " 1" ]] && return 0
154+ [[ -z " ${CT_NAME:- } " ]] && return 0
155+ command -v incus & > /dev/null || return 0
156+ declare -f incus_container_exists > /dev/null 2>&1 || return 0
157+ incus_container_exists " ${CT_NAME} " || return 0
158+ [[ " ${DEV_MODE_KEEP:- false} " == " true" ]] && return 0
159+
160+ INCUS_ORPHAN_HANDLED=1
161+ export INCUS_ORPHAN_HANDLED
162+
163+ # Avoid nested ERR noise while prompting / deleting
164+ set +e
165+ trap - ERR
166+
167+ echo " "
168+ msg_warn " Incomplete container ${CT_NAME} left behind (create/configure failed)"
169+ echo -e " ${YW} What would you like to do?${CL} "
170+ echo " "
171+ echo -e " ${GN} 1)${CL} Remove container and exit"
172+ echo -e " ${GN} 2)${CL} Keep container for debugging"
173+ echo " "
174+ echo -en " ${YW} Select option [1-2] (default: 1, auto-remove in 60s): ${CL} "
175+ if read -t 60 -r response < /dev/tty 2> /dev/null || read -t 60 -r response; then
176+ case " ${response:- 1} " in
177+ 2)
178+ echo -e " \n${TAB}${YW} Container ${CT_NAME} kept for debugging${CL} "
179+ echo -e " ${TAB}${INFO} Access: ${GN} incus exec ${CT_NAME} -- bash${CL} "
180+ echo -e " ${TAB}${INFO} Remove later: ${GN} incus delete ${CT_NAME} --force${CL} "
181+ ;;
182+ * )
183+ echo -e " \n${TAB}${HOLD}${YW} Removing container ${CT_NAME}${CL} "
184+ incus_delete_container " ${CT_NAME} "
185+ ;;
186+ esac
187+ else
188+ echo " "
189+ msg_info " No response - removing container ${CT_NAME} "
190+ incus_delete_container " ${CT_NAME} "
191+ msg_ok " Container ${CT_NAME} removed"
192+ fi
193+ }
194+
147195# ==============================================================================
148196# SECTION 3: SHARED UI + BACKEND
149197# ==============================================================================
@@ -477,6 +525,10 @@ api_exit_script() {
477525 if [[ -z " ${_INCUS_INSIDE_CONTAINER:- } " && " ${CONTAINER_INSTALLING:- } " == " true" && -n " ${CT_NAME:- } " ]]; then
478526 command -v incus & > /dev/null && incus stop " $CT_NAME " --force 2> /dev/null || true
479527 fi
528+ # Create/configure aborts (before install recovery menu): offer delete like PVE 60s prompt
529+ if declare -f incus_offer_orphan_cleanup > /dev/null 2>&1 ; then
530+ incus_offer_orphan_cleanup
531+ fi
480532 elif [[ " ${POST_TO_API_DONE:- } " == " true" && " ${POST_UPDATE_DONE:- } " != " true" ]]; then
481533 post_update_to_api " done" " 0" 2> /dev/null || true
482534 fi
@@ -554,7 +606,8 @@ _incus_on_err() {
554606_incus_on_signal () {
555607 local sig=" $1 " code=" $2 "
556608 post_update_to_api " failed" " $code " 2> /dev/null || true
557- # Only stop instances from the Incus host — never inside a guest update run.
609+ # Only touch instances from the Incus host — never inside a guest update run.
610+ # EXIT trap (api_exit_script) offers 60s orphan cleanup / delete.
558611 if [[ -z " ${_INCUS_INSIDE_CONTAINER:- } " ]] && command -v incus & > /dev/null && [[ -n " ${CT_NAME:- } " ]]; then
559612 incus stop " $CT_NAME " --force 2> /dev/null || true
560613 fi
0 commit comments