Pre-release 1.2.7: OCI fixes, 12 new catalog applications and docs updates

Last changes before version 1.2.7.

OCI manager Apps
- Multi-container applications start while one of their containers is
  being backed up: the start hook waits until vzdump releases it and does
  not start it twice. The DHCP client of a dependency no longer keeps the
  lock of the hook, so later starts do not hang, and the watchdog waits
  while a dependency is locked. Installing or updating ProxMenux replaces
  the published copies of the hook already in place.
- Images built from scratch with a numeric user, such as ddns-updater,
  install: the container is created from a copy of the image that adds
  the /etc/passwd and /etc/group Proxmox VE needs, and the application
  runs with the user the image declares. The downloaded image stays
  unchanged. An image with a named user and no user database stops
  before anything is created, with a message that says why.

Catalog
- Twelve new applications: MDBListarr, CrossWatch, Free Games Claimer
  Remaster, Cloudreve, Poznote and AceStream HTTP Proxy, suggested in
  discussion #440, and the Dockur images of Chrony, Dnsmasq, Tor,
  Stunnel, strfry and Statping. The catalog lists 347 applications.
- Their prompts, labels and notes are translated into the seven
  languages of the menus.

Documentation
- OCI manager: architecture, lifecycle and stacks pages revised, with
  larger diagrams and new screenshots.
- Contributors: the page now thanks those developing, reviewing,
  translating and testing ProxMenux today and those who helped in its
  first steps, with new avatars.

Tests for the stack start hook, the watchdog, the user database check
of OCI images and the Tor installation summary.
This commit is contained in:
MacRimi
2026-10-25 13:17:54 +01:00
parent 2f48689087
commit fc41b04e87
103 changed files with 12799 additions and 603 deletions
+74 -4
View File
@@ -3,6 +3,19 @@ set -Eeuo pipefail
PATH=/usr/sbin:/usr/bin:/sbin:/bin
HOOK_NAME=proxmenux-stack-dependencies.sh
# Longest wait for a dependency that is being backed up, and for any other lock.
BACKUP_LOCK_WAIT=${PROXMENUX_STACK_BACKUP_LOCK_WAIT:-14400}
OTHER_LOCK_WAIT=${PROXMENUX_STACK_OTHER_LOCK_WAIT:-300}
# vzdump starts again a guest that was running before its backup right after releasing it.
RESTART_GRACE=${PROXMENUX_STACK_RESTART_GRACE:-20}
POLL=${PROXMENUX_STACK_POLL:-5}
# Earlier published versions of this script, replaced on update.
PREVIOUS_RELEASES=(
c28b69acd7cc8e66d3aec3e8b2ec86bda0e4f91193ec78549f1965f2c859c0d1
02c50ef17f2411ef22f4d382c51b34a7a0974a446d4a155ecb63fb1e805a7c59
)
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
@@ -51,7 +64,7 @@ install_hook() {
' "$source_config" >/dev/null || die "Invalid dependency contract"
storage=$(find_snippet_storage)
hook_volume="${storage}:snippets/proxmenux-stack-dependencies.sh"
hook_volume="${storage}:snippets/${HOOK_NAME}"
hook_path=$(pvesm path "$hook_volume")
install -D -m 0755 "$0" "$hook_path"
@@ -63,6 +76,57 @@ install_hook() {
"$main_id" "$hook_volume"
}
# The installed copies of an earlier release take this version; a copy
# changed by hand is left as it is.
refresh_hooks() {
local volume path current known
while IFS= read -r volume; do
path=$(pvesm path "$volume" 2>/dev/null) || continue
[[ -f $path && ! -L $path ]] || continue
current=$(sha256sum "$path" | cut -d' ' -f1)
for known in "${PREVIOUS_RELEASES[@]}"; do
if [[ $current == "$known" ]]; then
install -m 0755 "$0" "$path.new" && mv -f "$path.new" "$path"
printf 'Updated %s\n' "$path"
break
fi
done
done < <(sed -n "s/^hookscript:[[:space:]]*\([^[:space:]]*:snippets\/${HOOK_NAME//./\\.}\)[[:space:]]*$/\1/p" \
/etc/pve/lxc/*.conf 2>/dev/null | sort -u)
}
is_running() {
[[ $(pct status "$1" 2>/dev/null || true) == "status: running" ]]
}
container_lock() {
pct config "$1" 2>/dev/null | sed -n '/^lock:/{s/^lock:[[:space:]]*//p;q;}' || true
}
# A dependency stopped for its own backup stays locked until vzdump has
# saved it, and vzdump starts it again when it was running before.
wait_for_release() {
local id=$1 label=$2 lock limit waited=0
lock=$(container_lock "$id")
[[ -n $lock ]] || return 0
limit=$OTHER_LOCK_WAIT
if [[ $lock == backup ]]; then
limit=$BACKUP_LOCK_WAIT
fi
printf 'Dependency %s (CT %s) is locked (%s); waiting until it is released...\n' "$label" "$id" "$lock"
while lock=$(container_lock "$id"); [[ -n $lock ]]; do
(( waited < limit )) || die "$label (CT $id) is still locked ($lock) after ${waited}s"
sleep "$POLL"
waited=$((waited + POLL))
done
printf 'Dependency %s (CT %s) was released.\n' "$label" "$id"
waited=0
while (( waited < RESTART_GRACE )) && ! is_running "$id"; do
sleep 2
waited=$((waited + 2))
done
}
dependency_is_healthy() {
local id=$1 healthcheck=$2 type url
type=$(jq -r '.type' <<<"$healthcheck")
@@ -74,7 +138,7 @@ dependency_is_healthy() {
local -a command=()
mapfile -t command < <(jq -r '.argv[]' <<<"$healthcheck")
((${#command[@]} > 0)) || return 1
pct exec "$id" -- "${command[@]}" >/dev/null 2>&1
pct exec "$id" -- "${command[@]}" >/dev/null 2>&1 9>&-
;;
http)
url=$(jq -r '.url' <<<"$healthcheck")
@@ -103,9 +167,11 @@ start_dependencies() {
pct config "$id" >/dev/null 2>&1 \
|| die "Dependency $label (CT $id) does not exist"
if [[ $(pct status "$id" 2>/dev/null || true) != "status: running" ]]; then
is_running "$id" || wait_for_release "$id" "$label"
if ! is_running "$id"; then
printf 'Starting dependency %s (CT %s)...\n' "$label" "$id"
pct start "$id" || die "Could not start $label (CT $id)"
# What pct start leaves running, such as the DHCP client, must not keep the lock.
pct start "$id" 9>&- || is_running "$id" || die "Could not start $label (CT $id)"
else
printf 'Dependency %s (CT %s) was already running.\n' "$label" "$id"
fi
@@ -131,6 +197,10 @@ if [[ ${1:-} == "--install" ]]; then
install_hook "$@"
exit 0
fi
if [[ ${1:-} == "--refresh" ]]; then
refresh_hooks
exit 0
fi
vmid=${1:?missing VMID}
phase=${2:?missing lifecycle phase}