diff --git a/incus/tools.func b/incus/tools.func index 4696a13..4ae3abb 100644 --- a/incus/tools.func +++ b/incus/tools.func @@ -273,7 +273,7 @@ motd_ssh() { local ip ip=$(get_lxc_ip) cat </etc/motd - 🚀 Incus Container: $(hostname) + 🚀 Incus Container: $(uname -n) 🖥️ OS: $(grep ^PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"') 📡 IP Address: ${ip} EOF diff --git a/lib/system.func b/lib/system.func index 764ccc0..41e6eec 100644 --- a/lib/system.func +++ b/lib/system.func @@ -2098,8 +2098,11 @@ verify_gpg_fingerprint() { create_self_signed_cert() { local APP_NAME="${1:-${APPLICATION}}" local EXTRA_SAN="${2:-}" - local HOSTNAME="$(hostname -f)" - local IP="$(hostname -I | awk '{print $1}')" + # RHEL-family minimal images ship no hostname binary, which left CN and SAN empty. + local HOSTNAME IP + HOSTNAME="$(hostname -f 2>/dev/null || uname -n)" + IP="$(hostname -I 2>/dev/null | awk '{print $1}')" + [[ -n "$IP" ]] || IP="$(ip -4 route get 1 2>/dev/null | sed -n 's/.* src \([0-9.]\+\).*/\1/p' | head -1)" local APP_NAME_LC=$(echo "${APP_NAME,,}" | tr -d ' ') local CERT_DIR="/etc/ssl/${APP_NAME_LC}" local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key" diff --git a/lxc/install.func b/lxc/install.func index 16c9a4d..c523828 100644 --- a/lxc/install.func +++ b/lxc/install.func @@ -1519,7 +1519,7 @@ EOF echo -e "${GATEWAY:-}${YW:-}Provided by: ${GN:-}community-scripts ORG ${YW:-}| GitHub: ${GN:-}https://github.com/community-scripts/ProxmoxVE${CL:-}" echo "" echo -e "${OS:-}${YW:-}OS: ${GN:-}${os_name} - Version: ${os_version}${CL:-}" -echo -e "${HOSTNAME:-}${YW:-}Hostname: ${GN:-}\$(hostname)${CL:-}" +echo -e "${HOSTNAME:-}${YW:-}Hostname: ${GN:-}\$(uname -n)${CL:-}" echo -e "${INFO:-}${YW:-}IP Address: ${GN:-}\$(ip -4 route get 1 2>/dev/null | sed -n 's/.* src \\([0-9.]\\+\\).*/\\1/p' | head -1)${CL:-}" EOF diff --git a/pve/backend.func b/pve/backend.func index d4700b8..7f2c11d 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -393,8 +393,9 @@ $PCT_OPTIONS_STRING" # PVE derives ostype from the template name, then rejects its own value when # reading the config back - which breaks every later pct call. + # Amazon Linux also needs it to be created at all: PVE resolves no ostype for ID 'amzn'. case "${var_os:-}" in - openeuler | rockylinux | almalinux) + openeuler | rockylinux | almalinux | amazonlinux) PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING -ostype centos" ;; @@ -1132,7 +1133,7 @@ EOF # 3. /etc/profile.d/99-pve-console-term.sh: re-asserts TERM=linux AFTER all other # profile.d scripts, so readline never thinks CPR is supported (fixes R;80R garbage) case "$var_os" in - fedora | rocky | rockylinux | alma | almalinux | centos | openeuler | opensuse | archlinux | arch) + fedora | rocky | rockylinux | alma | almalinux | centos | openeuler | amazonlinux | oraclelinux | opensuse | archlinux | arch) pct exec "$CTID" -- bash -c ' mkdir -p /etc/systemd/system/console-getty.service.d printf "[Service]\nEnvironment=TERM=linux\n" >/etc/systemd/system/console-getty.service.d/pve-console-term.conf @@ -1161,7 +1162,7 @@ PROFILE # Ensure curl is present for install.func bootstrap (most templates have it, but be safe) case "$var_os" in - fedora | rocky | rockylinux | alma | almalinux | centos | openeuler) + fedora | rocky | rockylinux | alma | almalinux | centos | openeuler | amazonlinux | oraclelinux) pct exec "$CTID" -- bash -c "command -v curl >/dev/null 2>&1 || (command -v dnf >/dev/null 2>&1 && dnf install -y curl >/dev/null 2>&1) || (command -v yum >/dev/null 2>&1 && yum install -y curl >/dev/null 2>&1)" || true ;; opensuse) @@ -2387,24 +2388,119 @@ create_lxc_container() { ARCH="$(dpkg --print-architecture)" - # Maps OS type + version to the release variant name used by ARM64 template sources. - arm64_template_variant() { - case "$1:$2" in - debian:12) echo "bookworm" ;; - debian:13) echo "trixie" ;; - debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;; + # A distro appears in the catalog under whichever variant names it actually + # builds. Nearly all use "default"; Gentoo publishes only openrc and systemd, + # so a hardcoded "default" filter made it permanently unfindable. First match + # wins, so the init system the engine expects leads. + _lxc_catalog_variants() { + case "${1,,}" in + gentoo) echo "openrc systemd" ;; + *) echo "default" ;; + esac + } - ubuntu:24.04) echo "noble" ;; - ubuntu:26.04) echo "questing" ;; - ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;; + # Pulls the catalog index to $1. Kept apart from the lookup so an unreachable + # catalog reports as a network failure instead of "no such image". + _lxc_catalog_fetch() { + command -v jq >/dev/null 2>&1 || { + $STD apt-get update + $STD apt-get install -y jq || return 1 + } + curl -fsSL --max-time 30 -o "${1:?index path}" \ + "https://images.linuxcontainers.org/streams/v1/images.json" + } - alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;; + # No version asked for. The catalog carries no stable/latest marker -- Debian + # lists forky (14, unreleased) beside trixie -- so picking by build date would + # hand out a testing release. Use the codenames the UI already defaults to, + # and otherwise only answer when the distro ships a single release at all. + _lxc_catalog_default_release() { + local os="${1,,}" arch="$2" variant="$3" index="$4" + case "$os" in + debian) + printf '%s' "${DEBIAN_DEFAULT_CODENAME:-}" + return + ;; + ubuntu) + printf '%s' "${UBUNTU_DEFAULT_CODENAME:-}" + return + ;; + alpine) + printf '%s' "${ALPINE_DEFAULT_VERSION:-}" + return + ;; + esac + jq -r --arg os "$os" --arg arch "$arch" --arg variant "$variant" ' + [ .products[] + | select(.arch == $arch and .variant == $variant) + | select((.os | ascii_downcase) == $os) + | .release ] | unique + | if length == 1 then .[0] else empty end + ' "$index" 2>/dev/null + } - *) return 1 ;; + # Echoes the catalog path of a usable rootfs image for os/version/arch. + # Prefers the plain tarball. A squashfs counts as usable -- it just has to be + # unpacked first -- and is what Nixos ships instead of a tarball. + # var_version has to keep matching the pveam template name (archlinux-base, devuan-5), + # and the catalog names those releases differently. + _lxc_catalog_release_alias() { + case "${1,,}:${2}" in + archlinux:base) printf 'current' ;; + devuan:5 | devuan:5.0) printf 'daedalus' ;; + *) printf '%s' "$2" ;; esac } - # Downloads an ARM64 LXC rootfs template to $1. + # CentOS publishes 10 as "10-Stream". Only a suffix after a dash counts, so asking for 4 + # never lands on 43. + _lxc_catalog_suffixed_release() { + jq -r --arg os "${1,,}" --arg ver "$2" --arg arch "$3" --arg variant "$4" ' + [ .products[] + | select(.arch == $arch and .variant == $variant) + | select((.os | ascii_downcase) == $os) + | select(.release | startswith($ver + "-")) | .release ] | unique | last // empty + ' "$5" 2>/dev/null + } + + _lxc_catalog_path() { + local os="${1,,}" ver="$2" arch="$3" index="${4:?index path}" + local variant want path + for variant in $(_lxc_catalog_variants "$os"); do + want="$(_lxc_catalog_release_alias "$os" "$ver")" + [[ -z "$want" ]] && want="$(_lxc_catalog_default_release "$os" "$arch" "$variant" "$index")" + [[ -z "$want" ]] && continue + path=$(jq -r --arg os "$os" --arg ver "$want" --arg arch "$arch" --arg variant "$variant" ' + .products | to_entries[] + | select(.value.arch == $arch and .value.variant == $variant) + | select((.value.os | ascii_downcase) == $os) + | select(.value.release == $ver or ((.value.aliases // "") | split(",") | index($os + "/" + $ver))) + | .value.versions | to_entries | sort_by(.key) | last | .value.items + | (.["root.tar.xz"].path // .["root.squashfs"].path // empty) + ' "$index" 2>/dev/null | head -1) + if [[ -n "$path" ]]; then + printf '%s' "$path" + return 0 + fi + want="$(_lxc_catalog_suffixed_release "$os" "$want" "$arch" "$variant" "$index")" + [[ -z "$want" ]] && continue + path=$(jq -r --arg os "$os" --arg ver "$want" --arg arch "$arch" --arg variant "$variant" ' + .products | to_entries[] + | select(.value.arch == $arch and .value.variant == $variant) + | select((.value.os | ascii_downcase) == $os) + | select(.value.release == $ver) + | .value.versions | to_entries | sort_by(.key) | last | .value.items + | (.["root.tar.xz"].path // .["root.squashfs"].path // empty) + ' "$index" 2>/dev/null | head -1) + if [[ -n "$path" ]]; then + printf '%s' "$path" + return 0 + fi + done + return 1 + } + + # Downloads a linuxcontainers rootfs template to $1. # Does Proxmox offer this OS/version/arch? Local first, then the catalog. _pveam_offers_template() { local search pattern @@ -2429,8 +2525,52 @@ create_lxc_container() { grep -qE "^${search}.*${pattern}" } + # pct wants a rootfs tarball, and a few distros (Nixos) publish only a + # squashfs. Unpacking beside the destination rather than in /tmp keeps a + # multi-GB rootfs off the root filesystem, and xz -1 because this recompresses + # what upstream already compressed once -- speed beats a smaller cache file. + _lxc_squashfs_to_tarball() { + local url="$1" dest="$2" sqfs rootdir rc=0 + + command -v unsquashfs >/dev/null 2>&1 || { + $STD apt-get update + $STD apt-get install -y squashfs-tools || { + msg_error "squashfs-tools is required to unpack the ${PCT_OSTYPE} template" + return 1 + } + } + + sqfs="${dest%.tar.xz}.squashfs" + if ! curl -fsSL -o "$sqfs" "$url"; then + rm -f "$sqfs" + msg_error "Failed to download template from: $url" + return 1 + fi + + rootdir="$(mktemp -d "${dest%/*}/.unsquash.XXXXXX")" || { + rm -f "$sqfs" + msg_error "Cannot create unpack directory next to $dest" + return 1 + } + + if ! $STD unsquashfs -f -d "$rootdir" "$sqfs"; then + msg_error "Failed to unpack squashfs image" + rc=1 + else + tar -C "$rootdir" -cf - . | xz -1 -T0 -c >"$dest" + if ((PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0)); then + msg_error "Failed to repack squashfs image as a tarball" + rc=1 + fi + fi + + rm -rf "$rootdir" "$sqfs" + ((rc == 0)) || rm -f "$dest" + return "$rc" + } + # Only reached when pveam has nothing for this arch. - download_arm64_template() { + download_catalog_template() { local dest="$1" url mkdir -p "$(dirname "$dest")" || { @@ -2438,17 +2578,19 @@ create_lxc_container() { exit 217 } - url="https://jenkins.linuxcontainers.org/job/image-${PCT_OSTYPE}/architecture=arm64,release=${CUSTOM_TEMPLATE_VARIANT},variant=default/lastStableBuild/artifact/rootfs.tar.xz" + url="https://images.linuxcontainers.org/${CUSTOM_TEMPLATE_PATH}" - msg_info "Downloading ${PCT_OSTYPE^} ${CUSTOM_TEMPLATE_VARIANT} ARM64 template" + msg_info "Downloading ${PCT_OSTYPE^} ${CUSTOM_TEMPLATE_VARIANT} template (${ARCH})" local patched="${dest%/*}/.${dest##*/}.patched" ( flock -x 200 if [[ -f "$patched" ]] && [[ -s "$dest" ]] && xz -t "$dest" 2>/dev/null; then exit 0 fi - if ! curl -fsSL -o "$dest" "$url"; then - msg_error "Failed to download ARM64 template from: $url" + if [[ "$url" == *.squashfs ]]; then + _lxc_squashfs_to_tarball "$url" "$dest" || exit 208 + elif ! curl -fsSL -o "$dest" "$url"; then + msg_error "Failed to download template from: $url" exit 208 fi if ! tar -tJf "$dest" 2>/dev/null | grep -q '/etc/network/$'; then @@ -2460,7 +2602,7 @@ create_lxc_container() { rm -rf "$fixdir" else rm -rf "$fixdir" "$tmptar" - msg_error "Failed to patch ARM64 template (missing /etc/network)" + msg_error "Failed to patch template (missing /etc/network)" exit 208 fi fi @@ -2470,13 +2612,13 @@ create_lxc_container() { if [[ $dl_rc -ne 0 ]]; then exit "$dl_rc" fi - msg_ok "Downloaded ARM64 LXC template" + msg_ok "Downloaded LXC template from linuxcontainers.org" } download_template() { local dest="${1:-$TEMPLATE_PATH}" - if [[ "$ARCH" == "arm64" ]]; then - download_arm64_template "$dest" + if [[ -n "$CUSTOM_TEMPLATE_PATH" ]]; then + download_catalog_template "$dest" else pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 || { msg_error "Failed to download template '$TEMPLATE' to storage '$TEMPLATE_STORAGE'" @@ -2640,17 +2782,28 @@ create_lxc_container() { # Template discovery & validation # ------------------------------------------------------------------------------ CUSTOM_TEMPLATE_VARIANT="" + CUSTOM_TEMPLATE_PATH="" - if [[ "$ARCH" == "arm64" ]] && ! _pveam_offers_template; then - msg_info "No Proxmox template for ${PCT_OSTYPE} on ${ARCH}, using linuxcontainers.org" + if ! _pveam_offers_template; then + msg_info "No Proxmox template for ${PCT_OSTYPE} ${PCT_OSVERSION:-} on ${ARCH}, trying linuxcontainers.org" - CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant "$PCT_OSTYPE" "${PCT_OSVERSION:-}") || { - msg_error "No ARM64 template mapping for ${PCT_OSTYPE} ${PCT_OSVERSION:-latest}" - exit 225 - } + _lxc_catalog_index="$(mktemp)" + if ! _lxc_catalog_fetch "$_lxc_catalog_index"; then + msg_warn "Could not reach the linuxcontainers.org image catalog" + elif ! CUSTOM_TEMPLATE_PATH=$(_lxc_catalog_path "$PCT_OSTYPE" "${PCT_OSVERSION:-}" "$ARCH" "$_lxc_catalog_index"); then + CUSTOM_TEMPLATE_PATH="" + msg_warn "linuxcontainers.org has no ${PCT_OSTYPE} ${PCT_OSVERSION:-latest} for ${ARCH}" + fi + rm -f "$_lxc_catalog_index" + fi - TEMPLATE="${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-rootfs.tar.xz" - TEMPLATE_SOURCE="custom-arm64" + # A catalog miss falls through to pveam rather than ending the run: asking for + # a version that does not exist is far more common than asking for a distro + # nobody publishes, and only the pveam path can offer what this host does have. + if [[ -n "$CUSTOM_TEMPLATE_PATH" ]]; then + CUSTOM_TEMPLATE_VARIANT=$(awk -F/ '{print $3}' <<<"$CUSTOM_TEMPLATE_PATH") + TEMPLATE="${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-${ARCH}-rootfs.tar.xz" + TEMPLATE_SOURCE="custom-catalog" # Resolve template path TEMPLATE_PATH="$(pvesm path "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" 2>/dev/null || true)" @@ -2666,11 +2819,11 @@ create_lxc_container() { # Download if missing, too small, or corrupt if [[ ! -f "$TEMPLATE_PATH" ]]; then - download_arm64_template "$TEMPLATE_PATH" + download_catalog_template "$TEMPLATE_PATH" elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]] || ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then msg_warn "Local template invalid - re-downloading." rm -f "$TEMPLATE_PATH" - download_arm64_template "$TEMPLATE_PATH" + download_catalog_template "$TEMPLATE_PATH" else msg_ok "Template ${BL}$TEMPLATE${CL} found locally." fi @@ -2983,7 +3136,7 @@ create_lxc_container() { download_template msg_ok "Template downloaded" elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ "$ARCH" == "arm64" || -n "$ONLINE_TEMPLATE" ]]; then + if [[ -n "$CUSTOM_TEMPLATE_PATH" || -n "$ONLINE_TEMPLATE" ]]; then msg_info "Template appears corrupted – re-downloading" rm -f "$TEMPLATE_PATH" download_template @@ -2993,26 +3146,27 @@ create_lxc_container() { fi fi - # openEuler templates are detected by PVE as ostype 'centos' but ship without - # /etc/redhat-release, which makes PVE::LXC::Setup::post_create_hook abort with - # "can't open '/etc/redhat-release' - No such file or directory". - # Patch the cached template once to inject a CentOS-compatible release file. - if [[ "${var_os:-}" == "openeuler" ]]; then - if ! tar -tf "$TEMPLATE_PATH" 2>/dev/null | grep -qE '^\.?/?etc/redhat-release$'; then - msg_info "Patching openEuler template (adding /etc/redhat-release)" + # Both run as ostype centos, whose PVE plugin reads a /etc/redhat-release neither ships. + if [[ "${var_os:-}" == "openeuler" || "${var_os:-}" == "amazonlinux" ]]; then + local _oe_rel="" + _oe_rel="$(tar -xOf "$TEMPLATE_PATH" --wildcards '*etc/redhat-release' 2>/dev/null | head -1)" + # The content, not just the file: that plugin rejects every version outside 5..10. + if [[ ! "$_oe_rel" =~ release[[:space:]]+([5-9]|10)([.[:space:]]|$) ]]; then + msg_info "Patching ${var_os} template (/etc/redhat-release)" local _oe_tmp _oe_tmp=$(mktemp -d) || _oe_tmp="" if [[ -n "$_oe_tmp" ]] && mkdir -p "$_oe_tmp/etc"; then - # Content is parsed by PVE's CentOS setup plugin (expects " release ") - local _oe_ver="${var_version:-25.03}" - echo "CentOS Linux release ${_oe_ver} (openEuler)" >"$_oe_tmp/etc/redhat-release" + # 10, not the real version: both images configure their own network via systemd-networkd. + echo "CentOS Linux release 10 (${var_os})" >"$_oe_tmp/etc/redhat-release" if xz -dc "$TEMPLATE_PATH" >"$_oe_tmp/template.tar" 2>/dev/null && + { tar --delete -f "$_oe_tmp/template.tar" ./etc/redhat-release 2>/dev/null || + tar --delete -f "$_oe_tmp/template.tar" etc/redhat-release 2>/dev/null || true; } && tar -C "$_oe_tmp" --append -f "$_oe_tmp/template.tar" etc/redhat-release 2>/dev/null && xz -f "$_oe_tmp/template.tar" 2>/dev/null && mv -f "$_oe_tmp/template.tar.xz" "$TEMPLATE_PATH"; then - msg_ok "Patched openEuler template" + msg_ok "Patched ${var_os} template" else - msg_warn "Failed to patch openEuler template – pct create may fail in post_create_hook" + msg_warn "Failed to patch ${var_os} template – pct create may fail in post_create_hook" fi rm -rf "$_oe_tmp" fi @@ -3069,8 +3223,8 @@ create_lxc_container() { if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then msg_ok "Trying local storage fallback" msg_info "Downloading template to local" - if [[ "$ARCH" == "arm64" ]]; then - download_arm64_template "$LOCAL_TEMPLATE_PATH" + if [[ -n "$CUSTOM_TEMPLATE_PATH" ]]; then + download_catalog_template "$LOCAL_TEMPLATE_PATH" else pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 fi