mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-03 12:06:38 +00:00
feat(oci): run official container images as native LXC containers
Adds the OCI manager: an engine that turns a Docker Compose file into an LXC definition, a catalog of 365 applications drawn from LinuxServer.io and other container image sources, and a per-instance registry recording what each container was built from. Reachable from the main menu. Catalog text is translated like every other string in the project: the taglines go through translate() and land in lang/*.json, so the entries read in all eight languages instead of only English. Translation cache builder: - a failed translation leaves the key absent rather than writing English, which previously made the string count as translated forever - a result identical to a 3+ word source is rejected, catching a provider that silently returns the text it was given - strings that are nothing but glossary terms keep their source spelling instead of being discarded as failures - no backoff between attempts when the provider is deterministic - application names are protected so "HAOS One" survives translation - argos joins the provider list, and the workflow reads the OCI sources Audit & Report: - findings that moved in the wrong direction between runs are reported alongside the ones that improved - an accepted risk can carry a review date and is flagged when it falls due - backup checks explain in plain language what they looked at and what to do next Monitor: - disks can be excluded from periodic reads, and an idle disk says so instead of showing a stale temperature - per-disk identity survives a controller or enclosure change - scheduled Borg backups resolve their SSH key from the repository entry - PVE upgrades log the package list and the resulting dpkg changes The web build no longer copies scripts/ into public/: the documentation links to GitHub, so nothing read that folder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
die() {
|
||||
printf 'ERROR: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
find_snippet_storage() {
|
||||
local storage
|
||||
if pvesm status --content snippets 2>/dev/null \
|
||||
| awk 'NR > 1 && $1 == "local" && $3 == "active" {found=1} END {exit !found}'; then
|
||||
printf 'local'
|
||||
return
|
||||
fi
|
||||
storage=$(pvesm status --content snippets 2>/dev/null \
|
||||
| awk 'NR > 1 && $3 == "active" {print $1; exit}')
|
||||
[[ -n $storage ]] || die "No active storage supports snippets"
|
||||
printf '%s' "$storage"
|
||||
}
|
||||
|
||||
install_hook() {
|
||||
local main_id=${1:?missing main VMID} source_config=${2:?missing lifecycle JSON}
|
||||
local storage hook_volume hook_path target_config
|
||||
[[ $main_id =~ ^[0-9]+$ ]] || die "Invalid main VMID"
|
||||
jq -e '
|
||||
.schema == 1 and
|
||||
(.dependencies | type == "array") and
|
||||
(.dependencies | length > 0) and
|
||||
(all(.dependencies[];
|
||||
(.vmid | type == "number") and
|
||||
(.label | type == "string") and
|
||||
(.healthcheck.type | IN("exec", "http", "running")) and
|
||||
(.healthcheck.timeout_seconds | type == "number")
|
||||
))
|
||||
' "$source_config" >/dev/null || die "Invalid dependency contract"
|
||||
|
||||
storage=$(find_snippet_storage)
|
||||
hook_volume="${storage}:snippets/proxmenux-stack-dependencies.sh"
|
||||
hook_path=$(pvesm path "$hook_volume")
|
||||
install -D -m 0755 "$0" "$hook_path"
|
||||
|
||||
target_config="/etc/pve/priv/proxmenux-stack-${main_id}.json"
|
||||
umask 077
|
||||
cat "$source_config" >"$target_config"
|
||||
pct set "$main_id" --hookscript "$hook_volume" >/dev/null
|
||||
printf 'Proxmox hookscript installed: CT %s starts its dependencies through %s\n' \
|
||||
"$main_id" "$hook_volume"
|
||||
}
|
||||
|
||||
dependency_is_healthy() {
|
||||
local id=$1 healthcheck=$2 type url
|
||||
type=$(jq -r '.type' <<<"$healthcheck")
|
||||
case "$type" in
|
||||
running)
|
||||
[[ $(pct status "$id" 2>/dev/null || true) == "status: running" ]]
|
||||
;;
|
||||
exec)
|
||||
local -a command=()
|
||||
mapfile -t command < <(jq -r '.argv[]' <<<"$healthcheck")
|
||||
((${#command[@]} > 0)) || return 1
|
||||
pct exec "$id" -- "${command[@]}" >/dev/null 2>&1
|
||||
;;
|
||||
http)
|
||||
url=$(jq -r '.url' <<<"$healthcheck")
|
||||
curl -fsS --max-time 3 "$url" >/dev/null 2>&1
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
start_dependencies() {
|
||||
local main_id=$1 config="/etc/pve/priv/proxmenux-stack-${1}.json"
|
||||
local encoded dependency id label healthcheck timeout elapsed
|
||||
[[ -r $config ]] || die "Missing dependency contract for main CT $main_id"
|
||||
|
||||
exec 9>"/run/lock/proxmenux-stack-${main_id}.lock"
|
||||
flock 9
|
||||
while IFS= read -r encoded; do
|
||||
[[ -n $encoded ]] || continue
|
||||
dependency=$(base64 -d <<<"$encoded")
|
||||
id=$(jq -r '.vmid' <<<"$dependency")
|
||||
label=$(jq -r '.label' <<<"$dependency")
|
||||
healthcheck=$(jq -c '.healthcheck' <<<"$dependency")
|
||||
timeout=$(jq -r '.healthcheck.timeout_seconds' <<<"$dependency")
|
||||
[[ $id =~ ^[0-9]+$ && $timeout =~ ^[0-9]+$ && $timeout -gt 0 ]] \
|
||||
|| die "Invalid dependency in $config"
|
||||
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
|
||||
printf 'Starting dependency %s (CT %s)...\n' "$label" "$id"
|
||||
pct start "$id" || die "Could not start $label (CT $id)"
|
||||
else
|
||||
printf 'Dependency %s (CT %s) was already running.\n' "$label" "$id"
|
||||
fi
|
||||
|
||||
elapsed=0
|
||||
while (( elapsed < timeout )); do
|
||||
if dependency_is_healthy "$id" "$healthcheck"; then
|
||||
printf 'Dependency %s (CT %s): ready.\n' "$label" "$id"
|
||||
break
|
||||
fi
|
||||
[[ $(pct status "$id" 2>/dev/null || true) == "status: running" ]] \
|
||||
|| die "$label (CT $id) stopped while starting"
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
done
|
||||
(( elapsed < timeout )) \
|
||||
|| die "$label (CT $id) did not pass its health check within ${timeout}s"
|
||||
done < <(jq -r '.dependencies[] | @base64' "$config")
|
||||
}
|
||||
|
||||
if [[ ${1:-} == "--install" ]]; then
|
||||
shift
|
||||
install_hook "$@"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
vmid=${1:?missing VMID}
|
||||
phase=${2:?missing lifecycle phase}
|
||||
case "$phase" in
|
||||
pre-start) start_dependencies "$vmid" ;;
|
||||
post-start|pre-stop|post-stop) ;;
|
||||
*) die "Unknown lifecycle phase: $phase" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user