Jellyfin container can't check network status #1629

Closed
opened 2026-02-05 01:48:36 +03:00 by OVERLORD · 14 comments
Owner

Originally created by @sander1946 on GitHub (Sep 16, 2025).

Have you read and understood the above guidelines?

yes

📜 What is the name of the script you are using?

Jellyfin

📂 What was the exact command used to execute the script?

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/jellyfin.sh)"

⚙️ What settings are you using?

  • Default Settings
  • Advanced Settings

🖥️ Which Linux distribution are you using?

Debian 12

📈 Which Proxmox version are you on?

9.0.6

📝 Provide a clear and concise description of the issue.

I have run the installation script several times with different configuration. But it always fails when trying to get the LXC network status. When checking my DHCP server, it also does get an IP (that is the one I am using in the output screenshot)
I have run other community scripts that do not have this problem.

🔄 Steps to reproduce the issue.

  1. Past the installation command in the terminal
  2. use the default settings
  3. wait for it to finish setting up the container
  4. Have it fail when checking the container's connection

Paste the full error output (if available).

Image Image

🖼️ Additional context (optional).

Verbose logging did not seem to output anything more

Originally created by @sander1946 on GitHub (Sep 16, 2025). ### ✅ Have you read and understood the above guidelines? yes ### 📜 What is the name of the script you are using? Jellyfin ### 📂 What was the exact command used to execute the script? bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/jellyfin.sh)" ### ⚙️ What settings are you using? - [x] Default Settings - [x] Advanced Settings ### 🖥️ Which Linux distribution are you using? Debian 12 ### 📈 Which Proxmox version are you on? 9.0.6 ### 📝 Provide a clear and concise description of the issue. I have run the installation script several times with different configuration. But it always fails when trying to get the LXC network status. When checking my DHCP server, it also does get an IP (that is the one I am using in the output screenshot) I have run other community scripts that do not have this problem. ### 🔄 Steps to reproduce the issue. 1. Past the installation command in the terminal 2. use the default settings 3. wait for it to finish setting up the container 4. Have it fail when checking the container's connection ### ❌ Paste the full error output (if available). <img width="1282" height="1419" alt="Image" src="https://github.com/user-attachments/assets/1396a95f-fbb4-4a7b-a3e0-c4fa14e393d4" /> <img width="1249" height="783" alt="Image" src="https://github.com/user-attachments/assets/8ee99c4b-bc8c-4698-bd07-f8b4b467ba46" /> ### 🖼️ Additional context (optional). Verbose logging did not seem to output anything more
OVERLORD added the bug label 2026-02-05 01:48:36 +03:00
Author
Owner

@MickLesk commented on GitHub (Sep 16, 2025):

3 times -> Instant network

Image

maybe update your system (host) and reboot as first try

@MickLesk commented on GitHub (Sep 16, 2025): 3 times -> Instant network <img width="732" height="316" alt="Image" src="https://github.com/user-attachments/assets/01289df9-f836-479d-80dc-c50ba935225f" /> maybe update your system (host) and reboot as first try
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

maybe update your system (host) and reboot as first try

A reboot did not help sadly, I have also updated the system, this also did not seem to help. There is also no change in the output.
Also, I'm not restarting it again, it legit takes 10 minutes for the server to start going back online. It's a full on 2U server....

@sander1946 commented on GitHub (Sep 16, 2025): > maybe update your system (host) and reboot as first try A reboot did not help sadly, I have also updated the system, this also did not seem to help. There is also no change in the output. *Also, I'm not restarting it again, it legit takes 10 minutes for the server to start going back online. It's a full on 2U server....*
Author
Owner

@MickLesk commented on GitHub (Sep 16, 2025):

And Default doesnt Work to?

Can you Run the bash call with Set -x before?

Or just an ubuntu.sh instead of jelly

@MickLesk commented on GitHub (Sep 16, 2025): And Default doesnt Work to? Can you Run the bash call with Set -x before? Or just an ubuntu.sh instead of jelly
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

That also does not seem to work, all the other scripts that I have ran today are all Debian, it seems to be due to Ubuntu

@sander1946 commented on GitHub (Sep 16, 2025): That also does not seem to work, all the other scripts that I have ran today are all Debian, it seems to be due to Ubuntu
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

This is the output when running the jellyfin script with -x

Click to expand ```Bash error_handler() { source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) printf "\e[?25h" local exit_code="$?" local line_number="$1" local command="$2" local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" echo -e "\n$error_message" if "$line_number" -eq 51 ; then echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n" post_update_to_api "failed" "No error message, script ran in silent mode" else post_update_to_api "failed" "${command}" fi }

This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection

setting_up_container() {
msg_info "Setting up Container OS"
for ((i = RETRY_NUM; i > 0; i--)); do
if [ "$(hostname -I)" != "" ]; then
break
fi
echo 1>&2 -en "${CROSS}${RD} No Network! "
sleep $RETRY_EVERY
done
if [ "$(hostname -I)" = "" ]; then
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
echo -e "${NETWORK}Check Network Settings"
exit 1
fi
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
systemctl disable -q --now systemd-networkd-wait-online.service
msg_ok "Set up Container OS"
#msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)"
msg_ok "Network Connected: ${BL}$(hostname -I)"
}

This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected

This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected

network_check() {
set +e
trap - ERR
ipv4_connected=false
ipv6_connected=false
sleep 1

Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers.

if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then
msg_ok "IPv4 Internet Connected"
ipv4_connected=true
else
msg_error "IPv4 Internet Not Connected"
fi

Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers.

if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then
msg_ok "IPv6 Internet Connected"
ipv6_connected=true
else
msg_error "IPv6 Internet Not Connected"
fi

If both IPv4 and IPv6 checks fail, prompt the user

if ; then
read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt
if [[ "{prompt,,}" =~ ^(y|yes) ]]; then
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else
echo -e "${NETWORK}Check Network Settings"
exit 1
fi
fi

DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)

GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
GIT_STATUS="Git DNS:"
DNS_FAILED=false

for HOST in "${GIT_HOSTS[@]}"; do
RESOLVEDIP=$(getent hosts "HOST" | awk '\''{ print $1 }'\'' | grep -E '\''(^([0-9]{1,3}\.){3}[0-9]{1,3})|(^[a-fA-F0-9:]+$)''' | head -n1)
if -z "$RESOLVEDIP" ; then
GIT_STATUS+="$HOST:($DNSFAIL)"
DNS_FAILED=true
else
GIT_STATUS+=" $HOST:($DNSOK)"
fi
done

if ; then
fatal "$GIT_STATUS"
else
msg_ok "$GIT_STATUS"
fi

set -e
trap '''error_handler $LINENO "$BASH_COMMAND"''' ERR
}

This function updates the Container OS by running apt-get update and upgrade

update_os() {
msg_info "Updating Container OS"
if ; then
echo '''Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";''' >/etc/apt/apt.conf.d/00aptproxy
cat </usr/local/bin/apt-proxy-detect.sh
#!/bin/bash
if nc -w1 -z "${CACHER_IP}" 3142; then
echo -n "http://${CACHER_IP}:3142"
else
echo -n "DIRECT"
fi
EOF
chmod +x /usr/local/bin/apt-proxy-detect.sh
fi
$STD apt-get update
$STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
msg_ok "Updated Container OS"

source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
}

This function modifies the message of the day (motd) and SSH settings

motd_ssh() {

Set terminal to 256-color mode

grep -qxF "export TERM='''xterm-256color'''" /root/.bashrc || echo "export TERM='''xterm-256color'''" >>/root/.bashrc

Get OS information (Debian / Ubuntu)

if [ -f "/etc/os-release" ]; then
OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '''"''')
OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '''"''')
elif [ -f "/etc/debian_version" ]; then
OS_NAME="Debian"
OS_VERSION=$(cat /etc/debian_version)
fi

PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
echo "echo -e """ >"$PROFILE_FILE"
echo -e "echo -e "${BOLD}${APPLICATION} LXC Container${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}"" >>"$PROFILE_FILE"
echo "echo """ >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${OS}${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${HOSTNAME}${YW} Hostname: ${GN}$(hostname)${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${INFO}${YW} IP Address: ${GN}$(hostname -I | awk '''{print $1}''')${CL}"" >>"$PROFILE_FILE"

Disable default MOTD scripts

chmod -x /etc/update-motd.d/*

if ; then
sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
systemctl restart sshd
fi
}

This function customizes the container by modifying the getty service and enabling auto-login for the root user

customize() {
if ; then
msg_info "Customizing Container"
GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf"
mkdir -p $(dirname $GETTY_OVERRIDE)
cat <$GETTY_OVERRIDE
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 $TERM
EOF
systemctl daemon-reload
systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed '''s/.d//''')
msg_ok "Customized Container"
fi
echo "bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)"" >/usr/bin/update
chmod +x /usr/bin/update

if -n "${SSH_AUTHORIZED_KEY}" ; then
mkdir -p /root/.ssh
echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
fi
}'

  • FUNCTIONS_FILE_PATH='# Copyright (c) 2021-2025 tteck

Author: tteck (tteckster)

Co-Author: MickLesk

License: MIT

https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE

if ! command -v curl >/dev/null 2>&1; then
printf "\r\e[2K%b" '''\033[93m Setup Source \033[m''' >&2
apt-get update >/dev/null 2>&1
apt-get install -y curl >/dev/null 2>&1
fi
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
load_functions

This function enables IPv6 if it'''s not disabled and sets verbose mode

verb_ip6() {
set_std_mode # Set STD mode based on VERBOSE

if [ "$DISABLEIPV6" == "yes" ]; then
echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf
$STD sysctl -p
fi
}

This function sets error handling options and defines the error_handler function to handle errors

catch_errors() {
set -Eeuo pipefail
trap '''error_handler $LINENO "$BASH_COMMAND"''' ERR
}

This function handles errors

error_handler() {
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
printf "\e[?25h"
local exit_code="$?"
local line_number="$1"
local command="$2"
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
echo -e "\n$error_message"
if "$line_number" -eq 51 ; then
echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n"
post_update_to_api "failed" "No error message, script ran in silent mode"
else
post_update_to_api "failed" "${command}"
fi
}

This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection

setting_up_container() {
msg_info "Setting up Container OS"
for ((i = RETRY_NUM; i > 0; i--)); do
if [ "$(hostname -I)" != "" ]; then
break
fi
echo 1>&2 -en "${CROSS}${RD} No Network! "
sleep $RETRY_EVERY
done
if [ "$(hostname -I)" = "" ]; then
echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
echo -e "${NETWORK}Check Network Settings"
exit 1
fi
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
systemctl disable -q --now systemd-networkd-wait-online.service
msg_ok "Set up Container OS"
#msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)"
msg_ok "Network Connected: ${BL}$(hostname -I)"
}

This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected

This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected

network_check() {
set +e
trap - ERR
ipv4_connected=false
ipv6_connected=false
sleep 1

Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers.

if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then
msg_ok "IPv4 Internet Connected"
ipv4_connected=true
else
msg_error "IPv4 Internet Not Connected"
fi

Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers.

if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then
msg_ok "IPv6 Internet Connected"
ipv6_connected=true
else
msg_error "IPv6 Internet Not Connected"
fi

If both IPv4 and IPv6 checks fail, prompt the user

if ; then
read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt
if [[ "{prompt,,}" =~ ^(y|yes) ]]; then
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else
echo -e "${NETWORK}Check Network Settings"
exit 1
fi
fi

DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)

GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
GIT_STATUS="Git DNS:"
DNS_FAILED=false

for HOST in "${GIT_HOSTS[@]}"; do
RESOLVEDIP=$(getent hosts "HOST" | awk '\''{ print $1 }'\'' | grep -E '\''(^([0-9]{1,3}\.){3}[0-9]{1,3})|(^[a-fA-F0-9:]+$)''' | head -n1)
if -z "$RESOLVEDIP" ; then
GIT_STATUS+="$HOST:($DNSFAIL)"
DNS_FAILED=true
else
GIT_STATUS+=" $HOST:($DNSOK)"
fi
done

if ; then
fatal "$GIT_STATUS"
else
msg_ok "$GIT_STATUS"
fi

set -e
trap '''error_handler $LINENO "$BASH_COMMAND"''' ERR
}

This function updates the Container OS by running apt-get update and upgrade

update_os() {
msg_info "Updating Container OS"
if ; then
echo '''Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";''' >/etc/apt/apt.conf.d/00aptproxy
cat </usr/local/bin/apt-proxy-detect.sh
#!/bin/bash
if nc -w1 -z "${CACHER_IP}" 3142; then
echo -n "http://${CACHER_IP}:3142"
else
echo -n "DIRECT"
fi
EOF
chmod +x /usr/local/bin/apt-proxy-detect.sh
fi
$STD apt-get update
$STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
msg_ok "Updated Container OS"

source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
}

This function modifies the message of the day (motd) and SSH settings

motd_ssh() {

Set terminal to 256-color mode

grep -qxF "export TERM='''xterm-256color'''" /root/.bashrc || echo "export TERM='''xterm-256color'''" >>/root/.bashrc

Get OS information (Debian / Ubuntu)

if [ -f "/etc/os-release" ]; then
OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '''"''')
OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '''"''')
elif [ -f "/etc/debian_version" ]; then
OS_NAME="Debian"
OS_VERSION=$(cat /etc/debian_version)
fi

PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
echo "echo -e """ >"$PROFILE_FILE"
echo -e "echo -e "${BOLD}${APPLICATION} LXC Container${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}"" >>"$PROFILE_FILE"
echo "echo """ >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${OS}${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${HOSTNAME}${YW} Hostname: ${GN}$(hostname)${CL}"" >>"$PROFILE_FILE"
echo -e "echo -e "${TAB}${INFO}${YW} IP Address: ${GN}$(hostname -I | awk '''{print $1}''')${CL}"" >>"$PROFILE_FILE"

Disable default MOTD scripts

chmod -x /etc/update-motd.d/*

if ; then
sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
systemctl restart sshd
fi
}

This function customizes the container by modifying the getty service and enabling auto-login for the root user

customize() {
if ; then
msg_info "Customizing Container"
GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf"
mkdir -p $(dirname $GETTY_OVERRIDE)
cat <$GETTY_OVERRIDE
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 $TERM
EOF
systemctl daemon-reload
systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed '''s/.d//''')
msg_ok "Customized Container"
fi
echo "bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)"" >/usr/bin/update
chmod +x /usr/bin/update

if -n "${SSH_AUTHORIZED_KEY}" ; then
mkdir -p /root/.ssh
echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
fi
}'

  • export DIAGNOSTICS=no

  • DIAGNOSTICS=no

  • export RANDOM_UUID=548260d8-3951-4a12-a992-b4f7d4ba8f3f

  • RANDOM_UUID=548260d8-3951-4a12-a992-b4f7d4ba8f3f

  • export CACHER=

  • CACHER=

  • export CACHER_IP=

  • CACHER_IP=

  • export tz=Europe/Amsterdam

  • tz=Europe/Amsterdam

  • export APPLICATION=Jellyfin

  • APPLICATION=Jellyfin

  • export app=jellyfin

  • app=jellyfin

  • export PASSWORD=

  • PASSWORD=

  • export VERBOSE=yes

  • VERBOSE=yes

  • export SSH_ROOT=no

  • SSH_ROOT=no

  • export SSH_AUTHORIZED_KEY

  • export CTID=106

  • CTID=106

  • export CTTYPE=1

  • CTTYPE=1

  • export ENABLE_FUSE=yes

  • ENABLE_FUSE=yes

  • export ENABLE_TUN=yes

  • ENABLE_TUN=yes

  • export PCT_OSTYPE=ubuntu

  • PCT_OSTYPE=ubuntu

  • export PCT_OSVERSION=24.04

  • PCT_OSVERSION=24.04

  • export PCT_DISK_SIZE=8

  • PCT_DISK_SIZE=8

  • export 'PCT_OPTIONS=
    -features keyctl=1,nesting=1,fuse=1
    -hostname jellyfin
    -tags community-script;media

    -net0 name=eth0,bridge=vmbr0,ip=dhcp
    -onboot 1
    -cores 2
    -memory 2048
    -unprivileged 1

    '

  • PCT_OPTIONS='
    -features keyctl=1,nesting=1,fuse=1
    -hostname jellyfin
    -tags community-script;media

    -net0 name=eth0,bridge=vmbr0,ip=dhcp
    -onboot 1
    -cores 2
    -memory 2048
    -unprivileged 1

    '
    ++ curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/create_lxc.sh

  • bash -c '#!/usr/bin/env bash

Copyright (c) 2021-2025 tteck

Author: tteck (tteckster)

Co-Author: MickLesk

License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE

This sets verbose mode if the global variable is set to "yes"

if [ "$VERBOSE" == "yes" ]; then set -x; fi

if command -v curl >/dev/null 2>&1; then
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via curl"
elif command -v wget >/dev/null 2>&1; then
source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via wget"
fi

This sets error handling options and defines the error_handler function to handle errors

set -Eeuo pipefail
trap '''error_handler $LINENO "$BASH_COMMAND"''' ERR
trap on_exit EXIT
trap on_interrupt INT
trap on_terminate TERM

function on_exit() {
local exit_code="$?"
-n "${lockfile:-}" && -e "$lockfile" && rm -f "$lockfile"
exit "$exit_code"
}

function error_handler() {
local exit_code="$?"
local line_number="$1"
local command="$2"
printf "\e[?25h"
echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n"
exit "$exit_code"
}

function on_interrupt() {
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
exit 130
}

function on_terminate() {
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
exit 143
}

function exit_script() {
clear
printf "\e[?25h"
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
kill 0
exit 1
}

function check_storage_support() {
local CONTENT="$1"
local -a VALID_STORAGES=()
while IFS= read -r line; do
local STORAGE_NAME
STORAGE_NAME=$(awk '''{print $1}''' <<<"$line")
-z "$STORAGE_NAME" && continue
VALID_STORAGES+=("$STORAGE_NAME")
done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk '''NR>1''')

${#VALID_STORAGES[@]} -gt 0
}

This function selects a storage pool for a given content type (e.g., rootdir, vztmpl).

function select_storage() {
local CLASS=$1 CONTENT CONTENT_LABEL

case $CLASS in
container)
CONTENT='''rootdir'''
CONTENT_LABEL='''Container'''
;;
template)
CONTENT='''vztmpl'''
CONTENT_LABEL='''Container template'''
;;
iso)
CONTENT='''iso'''
CONTENT_LABEL='''ISO image'''
;;
images)
CONTENT='''images'''
CONTENT_LABEL='''VM Disk image'''
;;
backup)
CONTENT='''backup'''
CONTENT_LABEL='''Backup'''
;;
snippets)
CONTENT='''snippets'''
CONTENT_LABEL='''Snippets'''
;;
*)
msg_error "Invalid storage class '''$CLASS'''"
return 1
;;
esac

Check for preset STORAGE variable

if [ "$CONTENT" = "rootdir" ] && [ -n "${STORAGE:-}" ]; then
if pvesm status -content "$CONTENT" | awk '''NR>1 {print $1}''' | grep -qx "$STORAGE"; then
STORAGE_RESULT="$STORAGE"
msg_info "Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL"
return 0
else
msg_error "Preset storage '''$STORAGE''' is not valid for content type '''$CONTENT'''."
return 2
fi
fi

local -A STORAGE_MAP
local -a MENU
local COL_WIDTH=0

while read -r TAG TYPE _ TOTAL USED FREE _; do
-n "$TAG" && -n "$TYPE" || continue
local STORAGE_NAME="$TAG"
local DISPLAY="${STORAGE_NAME} (${TYPE})"
local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B"
STORAGE_MAP["$DISPLAY"]="$STORAGE_NAME"
MENU+=("$DISPLAY" "$INFO" "OFF")
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
done < <(pvesm status -content "$CONTENT" | awk '''NR>1''')

if [ ${#MENU[@]} -eq 0 ]; then
msg_error "No storage found for content type '''$CONTENT'''."
return 2
fi

if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
STORAGE_INFO="${MENU[1]}"
return 0
fi

local WIDTH=$((COL_WIDTH + 42))
while true; do
local DISPLAY_SELECTED
DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts"
--title "Storage Pools"
--radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)"
16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3)

# Cancel or ESC
[[ $? -ne 0 ]] && exit_script

# Strip trailing whitespace or newline (important for storages like "storage (dir)")
DISPLAY_SELECTED=$(sed '\''s/[[:space:]]*$//'\'' <<<"$DISPLAY_SELECTED")

if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
  whiptail --msgbox "No valid storage selected. Please try again." 8 58
  continue
fi

STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}"
for ((i = 0; i < ${#MENU[@]}; i += 3)); do
  if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then
    STORAGE_INFO="${MENU[$i + 1]}"
    break
  fi
done
return 0

done
}

Test if required variables are set

"${CTID:-}" || {
msg_error "You need to set '''CTID''' variable."
exit 203
}
"${PCT_OSTYPE:-}" || {
msg_error "You need to set '''PCT_OSTYPE''' variable."
exit 204
}

Test if ID is valid

[ "$CTID" -ge "100" ] || {
msg_error "ID cannot be less than 100."
exit 205
}

Test if ID is in use

if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
echo -e "ID '''$CTID''' is already in use."
unset CTID
msg_error "Cannot use ID that is already in use."
exit 206
fi

This checks for the presence of valid Container Storage and Template Storage locations

msg_info "Validating storage"
if ! check_storage_support "rootdir"; then
msg_error "No valid storage found for '''rootdir''' [Container]"
exit 1
fi
if ! check_storage_support "vztmpl"; then
msg_error "No valid storage found for '''vztmpl''' [Template]"
exit 1
fi

#msg_info "Checking template storage"
while true; do
if select_storage template; then
TEMPLATE_STORAGE="$STORAGE_RESULT"
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
msg_ok "Storage ${BL}$TEMPLATE_STORAGE${CL} ($TEMPLATE_STORAGE_INFO) [Template]"
break
fi
done

while true; do
if select_storage container; then
CONTAINER_STORAGE="$STORAGE_RESULT"
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
msg_ok "Storage ${BL}$CONTAINER_STORAGE${CL} ($CONTAINER_STORAGE_INFO) [Container]"
break
fi
done

Check free space on selected container storage

STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '''$1 == s { print $6 }''')
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
if [ "$STORAGE_FREE" -lt "$REQUIRED_KB" ]; then
msg_error "Not enough space on '''$CONTAINER_STORAGE'''. Needed: ${PCT_DISK_SIZE:-8}G."
exit 214
fi

Check Cluster Quorum if in Cluster

if [ -f /etc/pve/corosync.conf ]; then
msg_info "Checking cluster quorum"
if ! pvecm status | awk -F''':''' '''/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'''; then

msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
exit 210

fi
msg_ok "Cluster is quorate"
fi

Update LXC template list

TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
case "$PCT_OSTYPE" in
debian | ubuntu)
TEMPLATE_PATTERN="-standard_"
;;
alpine | fedora | rocky | centos)
TEMPLATE_PATTERN="-default_"
;;
*)
TEMPLATE_PATTERN=""
;;
esac

1. Check local templates first

msg_info "Searching for template '''$TEMPLATE_SEARCH'''"
mapfile -t TEMPLATES < <(
pveam list "$TEMPLATE_STORAGE" |
awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '''$1 ~ s && $1 ~ p {print $1}''' |
sed '''s/.*///''' | sort -t - -k 2 -V
)

if [ ${#TEMPLATES[@]} -gt 0 ]; then
TEMPLATE_SOURCE="local"
else
msg_info "No local template found, checking online repository"
pveam update >/dev/null 2>&1
mapfile -t TEMPLATES < <(
pveam update >/dev/null 2>&1 &&
pveam available -section system |
sed -n "s/.($TEMPLATE_SEARCH.$TEMPLATE_PATTERN.*)/\1/p" |
sort -t - -k 2 -V
)
TEMPLATE_SOURCE="online"
fi

TEMPLATE="${TEMPLATES[-1]}"
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null ||
echo "/var/lib/vz/template/cache/$TEMPLATE")"
msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]"

4. Validate template (exists & not corrupted)

TEMPLATE_VALID=1

if [ ! -s "$TEMPLATE_PATH" ]; then
TEMPLATE_VALID=0
elif ! tar --use-compress-program=zstdcat -tf "$TEMPLATE_PATH" >/dev/null 2>&1; then
TEMPLATE_VALID=0
fi

if [ "$TEMPLATE_VALID" -eq 0 ]; then
msg_warn "Template $TEMPLATE is missing or corrupted. Re-downloading."
-f "$TEMPLATE_PATH" && rm -f "$TEMPLATE_PATH"
for attempt in {1..3}; do
msg_info "Attempt $attempt: Downloading LXC template..."
if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then
msg_ok "Template download successful."
break
fi
if [ $attempt -eq 3 ]; then
msg_error "Failed after 3 attempts. Please check network access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE"
exit 208
fi
sleep $((attempt * 5))
done
fi

msg_info "Creating LXC Container"

Check and fix subuid/subgid

grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid

Combine all options

PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|| PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}")

Secure creation of the LXC container with lock and template check

lockfile="/tmp/template.${TEMPLATE}.lock"
exec 9>"$lockfile" || {
msg_error "Failed to create lock file '''$lockfile'''."
exit 200
}
flock -w 60 9 || {
msg_error "Timeout while waiting for template lock"
exit 211
}

if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
msg_error "Container creation failed. Checking if template is corrupted or incomplete."

if ! -s "$TEMPLATE_PATH" ; then
msg_error "Template file too small or missing – re-downloading."
rm -f "$TEMPLATE_PATH"
elif ! zstdcat "$TEMPLATE_PATH" | tar -tf - &>/dev/null; then
msg_error "Template appears to be corrupted – re-downloading."
rm -f "$TEMPLATE_PATH"
else
msg_error "Template is valid, but container creation still failed."
exit 209
fi

Retry download

for attempt in {1..3}; do
msg_info "Attempt $attempt: Re-downloading template..."
if timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null; then
msg_ok "Template re-download successful."
break
fi
if [ "$attempt" -eq 3 ]; then
msg_error "Three failed attempts. Aborting."
exit 208
fi
sleep $((attempt * 5))
done

sleep 1 # I/O-Sync-Delay
msg_ok "Re-downloaded LXC Template"
fi

if ! pct list | awk '''{print $1}''' | grep -qx "$CTID"; then
msg_error "Container ID $CTID not listed in '''pct list''' – unexpected failure."
exit 215
fi

if ! grep -q '''^rootfs:''' "/etc/pve/lxc/$CTID.conf"; then
msg_error "RootFS entry missing in container config – storage not correctly assigned."
exit 216
fi

if grep -q '''^hostname:''' "/etc/pve/lxc/$CTID.conf"; then
CT_HOSTNAME=$(grep '''^hostname:''' "/etc/pve/lxc/$CTID.conf" | awk '''{print $2}''')
if [[ ! "CT_HOSTNAME" =~ ^[a-z0-9-]+ ]]; then
msg_warn "Hostname '''$CT_HOSTNAME''' contains invalid characters – may cause issues with networking or DNS."
fi
fi

msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."' 0
✔️ Storage local (Free: 65.5GB Used: 21.6GB) [Template]
✔️ Storage local-lvm (Free: 612.7GB Used: 73.5GB) [Container]
✔️ Template ubuntu-24.04-standard_24.04-2_amd64.tar.zst [local]
✔️ LXC Container 106 was successfully created.

  • LXC_CONFIG=/etc/pve/lxc/106.conf

  • '[' 1 == 0 ']'

  • VAAPI_APPS=("immich" "Channels" "Emby" "ErsatzTV" "Frigate" "Jellyfin" "Plex" "Scrypted" "Tdarr" "Unmanic" "Ollama" "FileFlows" "Open WebUI")

  • is_vaapi_app=false

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • for vaapi_app in "${VAAPI_APPS[@]}"

  • is_vaapi_app=true

  • break

  • '[' 1 == 0 ']'

  • '[' true == true ']'

  • -e /dev/dri/renderD128

  • -e /dev/dri/card0

  • echo ''

  • msg_custom '⚙️ ' '\e[96m' 'Configuring VAAPI passthrough for LXC container'

  • local 'symbol=⚙️ '

  • local 'color=\e[96m'

  • local 'msg=Configuring VAAPI passthrough for LXC container'

  • -z Configuring VAAPI passthrough for LXC container

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • echo -e '\r\033[K ⚙️ \e[96mConfiguring VAAPI passthrough for LXC container\033[m'
    ⚙️ Configuring VAAPI passthrough for LXC container

  • '[' 1 '!=' 0 ']'

  • msg_custom '⚠️ ' '\e[33m' 'Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).'

  • local 'symbol=⚠️ '

  • local 'color=\e[33m'

  • local 'msg=Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).'

  • -z Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • echo -e '\r\033[K ⚠️ \e[33mContainer is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).\033[m'
    ⚠️ Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).

  • msg_custom 'ℹ️ ' '\e[96m' 'VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).'

  • local 'symbol=ℹ️ '

  • local 'color=\e[96m'

  • local 'msg=VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).'

  • -z VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • echo -e '\r\033[K ℹ️ \e[96mVAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).\033[m'
    ℹ️ VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).

  • echo ''

  • read -rp '➤ Automatically mount all available VAAPI devices? [Y/n]: ' VAAPI_ALL
    ➤ Automatically mount all available VAAPI devices? [Y/n]: y

  • [[ y =~ ^[Yy]|^ ]]

  • '[' 1 == 0 ']'

  • -e /dev/dri/card0

  • echo 'dev0: /dev/dri/card0,gid=44'

  • -e /dev/dri/card1

  • -e /dev/dri/renderD128

  • '[' 1 == 1 ']'

  • '[' true == true ']'

  • -e /dev/dri/card0

  • echo 'dev0: /dev/dri/card0,gid=44'

  • -e /dev/dri/renderD128

  • '[' yes == yes ']'

  • cat

  • msg_info 'Starting LXC Container'

  • local 'msg=Starting LXC Container'

  • -z Starting LXC Container

  • declare -p MSG_INFO_SHOWN

  • MSG_INFO_SHOWN=()

  • declare -gA MSG_INFO_SHOWN

  • -n ''

  • MSG_INFO_SHOWN["$msg"]=1

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • SPINNER_MSG='Starting LXC Container'

  • is_verbose_mode

  • local verbose=yes

  • local tty_status

  • -t 2

  • tty_status=interactive

  • local 'HOURGLASS= '

  • printf '\r\e[2K%s %b' ' ' '\033[33mStarting LXC Container\033[m'
    Starting LXC Container+ return

  • pct start 106

  • for i in {1..10}

  • pct status 106

  • grep -q 'status: running'

  • msg_ok 'Started LXC Container'

  • local 'msg=Started LXC Container'

  • -z Started LXC Container

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • clear_line

  • tput cr

  • tput el

  • printf '%s %b\n' ' ✔️ ' '\033[1;92mStarted LXC Container\033[m'
    ✔️ Started LXC Container

  • unset 'MSG_INFO_SHOWN[Started LXC Container]'

  • break

  • '[' ubuntu '!=' alpine ']'

  • msg_info 'Waiting for network in LXC container'

  • local 'msg=Waiting for network in LXC container'

  • -z Waiting for network in LXC container

  • declare -p MSG_INFO_SHOWN

  • declare -A MSG_INFO_SHOWN

  • -n ''

  • MSG_INFO_SHOWN["$msg"]=1

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • SPINNER_MSG='Waiting for network in LXC container'

  • is_verbose_mode

  • local verbose=yes

  • local tty_status

  • -t 2

  • tty_status=interactive

  • local 'HOURGLASS= '

  • printf '\r\e[2K%s %b' ' ' '\033[33mWaiting for network in LXC container\033[m'
    Waiting for network in LXC container+ return

  • for i in {1..10}

  • pct exec 106 -- ping -c1 -W1 deb.debian.org

  • '[' 1 -lt 10 ']'

  • msg_warn 'No network in LXC yet (try 1/10) – waiting...'

  • stop_spinner

  • local pid=

  • -z ''

  • -f /tmp/.spinner.pid

  • -n ''

  • unset SPINNER_PID SPINNER_MSG

  • stty sane

  • local 'msg=No network in LXC yet (try 1/10) – waiting...'

  • echo -e '\r\033[K 💡 \033[m No network in LXC yet (try 1/10) – waiting...\033[m'
    💡 No network in LXC yet (try 1/10) – waiting...

  • sleep 3

  • for i in {1..10}

  • pct exec 106 -- ping -c1 -W1 deb.debian.org

</details>
@sander1946 commented on GitHub (Sep 16, 2025): This is the output when running the jellyfin script with -x <details> <summary>Click to expand</summary> ```Bash error_handler() { source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) printf "\e[?25h" local exit_code="$?" local line_number="$1" local command="$2" local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" echo -e "\n$error_message" if [[ "$line_number" -eq 51 ]]; then echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n" post_update_to_api "failed" "No error message, script ran in silent mode" else post_update_to_api "failed" "${command}" fi } # This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection setting_up_container() { msg_info "Setting up Container OS" for ((i = RETRY_NUM; i > 0; i--)); do if [ "$(hostname -I)" != "" ]; then break fi echo 1>&2 -en "${CROSS}${RD} No Network! " sleep $RETRY_EVERY done if [ "$(hostname -I)" = "" ]; then echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" echo -e "${NETWORK}Check Network Settings" exit 1 fi rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED systemctl disable -q --now systemd-networkd-wait-online.service msg_ok "Set up Container OS" #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" msg_ok "Network Connected: ${BL}$(hostname -I)" } # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected network_check() { set +e trap - ERR ipv4_connected=false ipv6_connected=false sleep 1 # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then msg_ok "IPv4 Internet Connected" ipv4_connected=true else msg_error "IPv4 Internet Not Connected" fi # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then msg_ok "IPv6 Internet Connected" ipv6_connected=true else msg_error "IPv6 Internet Not Connected" fi # If both IPv4 and IPv6 checks fail, prompt the user if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" else echo -e "${NETWORK}Check Network Settings" exit 1 fi fi # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") GIT_STATUS="Git DNS:" DNS_FAILED=false for HOST in "${GIT_HOSTS[@]}"; do RESOLVEDIP=$(getent hosts "$HOST" | awk '\''{ print $1 }'\'' | grep -E '\''(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)'\'' | head -n1) if [[ -z "$RESOLVEDIP" ]]; then GIT_STATUS+="$HOST:($DNSFAIL)" DNS_FAILED=true else GIT_STATUS+=" $HOST:($DNSOK)" fi done if [[ "$DNS_FAILED" == true ]]; then fatal "$GIT_STATUS" else msg_ok "$GIT_STATUS" fi set -e trap '\''error_handler $LINENO "$BASH_COMMAND"'\'' ERR } # This function updates the Container OS by running apt-get update and upgrade update_os() { msg_info "Updating Container OS" if [[ "$CACHER" == "yes" ]]; then echo '\''Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";'\'' >/etc/apt/apt.conf.d/00aptproxy cat <<EOF >/usr/local/bin/apt-proxy-detect.sh #!/bin/bash if nc -w1 -z "${CACHER_IP}" 3142; then echo -n "http://${CACHER_IP}:3142" else echo -n "DIRECT" fi EOF chmod +x /usr/local/bin/apt-proxy-detect.sh fi $STD apt-get update $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings motd_ssh() { # Set terminal to 256-color mode grep -qxF "export TERM='\''xterm-256color'\''" /root/.bashrc || echo "export TERM='\''xterm-256color'\''" >>/root/.bashrc # Get OS information (Debian / Ubuntu) if [ -f "/etc/os-release" ]; then OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\''"'\'') OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\''"'\'') elif [ -f "/etc/debian_version" ]; then OS_NAME="Debian" OS_VERSION=$(cat /etc/debian_version) fi PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" echo "echo -e \"\"" >"$PROFILE_FILE" echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" echo "echo \"\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '\''{print \$1}'\'')${CL}\"" >>"$PROFILE_FILE" # Disable default MOTD scripts chmod -x /etc/update-motd.d/* if [[ "${SSH_ROOT}" == "yes" ]]; then sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config systemctl restart sshd fi } # This function customizes the container by modifying the getty service and enabling auto-login for the root user customize() { if [[ "$PASSWORD" == "" ]]; then msg_info "Customizing Container" GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" mkdir -p $(dirname $GETTY_OVERRIDE) cat <<EOF >$GETTY_OVERRIDE [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF systemctl daemon-reload systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed '\''s/\.d//'\'') msg_ok "Customized Container" fi echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys fi }' + FUNCTIONS_FILE_PATH='# Copyright (c) 2021-2025 tteck # Author: tteck (tteckster) # Co-Author: MickLesk # License: MIT # https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE if ! command -v curl >/dev/null 2>&1; then printf "\r\e[2K%b" '\''\033[93m Setup Source \033[m'\'' >&2 apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) load_functions # This function enables IPv6 if it'\''s not disabled and sets verbose mode verb_ip6() { set_std_mode # Set STD mode based on VERBOSE if [ "$DISABLEIPV6" == "yes" ]; then echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf $STD sysctl -p fi } # This function sets error handling options and defines the error_handler function to handle errors catch_errors() { set -Eeuo pipefail trap '\''error_handler $LINENO "$BASH_COMMAND"'\'' ERR } # This function handles errors error_handler() { source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) printf "\e[?25h" local exit_code="$?" local line_number="$1" local command="$2" local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" echo -e "\n$error_message" if [[ "$line_number" -eq 51 ]]; then echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n" post_update_to_api "failed" "No error message, script ran in silent mode" else post_update_to_api "failed" "${command}" fi } # This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection setting_up_container() { msg_info "Setting up Container OS" for ((i = RETRY_NUM; i > 0; i--)); do if [ "$(hostname -I)" != "" ]; then break fi echo 1>&2 -en "${CROSS}${RD} No Network! " sleep $RETRY_EVERY done if [ "$(hostname -I)" = "" ]; then echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" echo -e "${NETWORK}Check Network Settings" exit 1 fi rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED systemctl disable -q --now systemd-networkd-wait-online.service msg_ok "Set up Container OS" #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" msg_ok "Network Connected: ${BL}$(hostname -I)" } # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected # This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected network_check() { set +e trap - ERR ipv4_connected=false ipv6_connected=false sleep 1 # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then msg_ok "IPv4 Internet Connected" ipv4_connected=true else msg_error "IPv4 Internet Not Connected" fi # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then msg_ok "IPv6 Internet Connected" ipv6_connected=true else msg_error "IPv6 Internet Not Connected" fi # If both IPv4 and IPv6 checks fail, prompt the user if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" else echo -e "${NETWORK}Check Network Settings" exit 1 fi fi # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") GIT_STATUS="Git DNS:" DNS_FAILED=false for HOST in "${GIT_HOSTS[@]}"; do RESOLVEDIP=$(getent hosts "$HOST" | awk '\''{ print $1 }'\'' | grep -E '\''(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)'\'' | head -n1) if [[ -z "$RESOLVEDIP" ]]; then GIT_STATUS+="$HOST:($DNSFAIL)" DNS_FAILED=true else GIT_STATUS+=" $HOST:($DNSOK)" fi done if [[ "$DNS_FAILED" == true ]]; then fatal "$GIT_STATUS" else msg_ok "$GIT_STATUS" fi set -e trap '\''error_handler $LINENO "$BASH_COMMAND"'\'' ERR } # This function updates the Container OS by running apt-get update and upgrade update_os() { msg_info "Updating Container OS" if [[ "$CACHER" == "yes" ]]; then echo '\''Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";'\'' >/etc/apt/apt.conf.d/00aptproxy cat <<EOF >/usr/local/bin/apt-proxy-detect.sh #!/bin/bash if nc -w1 -z "${CACHER_IP}" 3142; then echo -n "http://${CACHER_IP}:3142" else echo -n "DIRECT" fi EOF chmod +x /usr/local/bin/apt-proxy-detect.sh fi $STD apt-get update $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings motd_ssh() { # Set terminal to 256-color mode grep -qxF "export TERM='\''xterm-256color'\''" /root/.bashrc || echo "export TERM='\''xterm-256color'\''" >>/root/.bashrc # Get OS information (Debian / Ubuntu) if [ -f "/etc/os-release" ]; then OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\''"'\'') OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\''"'\'') elif [ -f "/etc/debian_version" ]; then OS_NAME="Debian" OS_VERSION=$(cat /etc/debian_version) fi PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" echo "echo -e \"\"" >"$PROFILE_FILE" echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${GATEWAY}${YW} Provided by: ${GN}community-scripts ORG ${YW}| GitHub: ${GN}https://github.com/community-scripts/ProxmoxVE${CL}\"" >>"$PROFILE_FILE" echo "echo \"\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(hostname -I | awk '\''{print \$1}'\'')${CL}\"" >>"$PROFILE_FILE" # Disable default MOTD scripts chmod -x /etc/update-motd.d/* if [[ "${SSH_ROOT}" == "yes" ]]; then sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config systemctl restart sshd fi } # This function customizes the container by modifying the getty service and enabling auto-login for the root user customize() { if [[ "$PASSWORD" == "" ]]; then msg_info "Customizing Container" GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" mkdir -p $(dirname $GETTY_OVERRIDE) cat <<EOF >$GETTY_OVERRIDE [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF systemctl daemon-reload systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed '\''s/\.d//'\'') msg_ok "Customized Container" fi echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys fi }' + export DIAGNOSTICS=no + DIAGNOSTICS=no + export RANDOM_UUID=548260d8-3951-4a12-a992-b4f7d4ba8f3f + RANDOM_UUID=548260d8-3951-4a12-a992-b4f7d4ba8f3f + export CACHER= + CACHER= + export CACHER_IP= + CACHER_IP= + export tz=Europe/Amsterdam + tz=Europe/Amsterdam + export APPLICATION=Jellyfin + APPLICATION=Jellyfin + export app=jellyfin + app=jellyfin + export PASSWORD= + PASSWORD= + export VERBOSE=yes + VERBOSE=yes + export SSH_ROOT=no + SSH_ROOT=no + export SSH_AUTHORIZED_KEY + export CTID=106 + CTID=106 + export CTTYPE=1 + CTTYPE=1 + export ENABLE_FUSE=yes + ENABLE_FUSE=yes + export ENABLE_TUN=yes + ENABLE_TUN=yes + export PCT_OSTYPE=ubuntu + PCT_OSTYPE=ubuntu + export PCT_OSVERSION=24.04 + PCT_OSVERSION=24.04 + export PCT_DISK_SIZE=8 + PCT_DISK_SIZE=8 + export 'PCT_OPTIONS= -features keyctl=1,nesting=1,fuse=1 -hostname jellyfin -tags community-script;media -net0 name=eth0,bridge=vmbr0,ip=dhcp -onboot 1 -cores 2 -memory 2048 -unprivileged 1 ' + PCT_OPTIONS=' -features keyctl=1,nesting=1,fuse=1 -hostname jellyfin -tags community-script;media -net0 name=eth0,bridge=vmbr0,ip=dhcp -onboot 1 -cores 2 -memory 2048 -unprivileged 1 ' ++ curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/create_lxc.sh + bash -c '#!/usr/bin/env bash # Copyright (c) 2021-2025 tteck # Author: tteck (tteckster) # Co-Author: MickLesk # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # This sets verbose mode if the global variable is set to "yes" # if [ "$VERBOSE" == "yes" ]; then set -x; fi if command -v curl >/dev/null 2>&1; then source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) load_functions #echo "(create-lxc.sh) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) load_functions #echo "(create-lxc.sh) Loaded core.func via wget" fi # This sets error handling options and defines the error_handler function to handle errors set -Eeuo pipefail trap '\''error_handler $LINENO "$BASH_COMMAND"'\'' ERR trap on_exit EXIT trap on_interrupt INT trap on_terminate TERM function on_exit() { local exit_code="$?" [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile" exit "$exit_code" } function error_handler() { local exit_code="$?" local line_number="$1" local command="$2" printf "\e[?25h" echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n" exit "$exit_code" } function on_interrupt() { echo -e "\n${RD}Interrupted by user (SIGINT)${CL}" exit 130 } function on_terminate() { echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}" exit 143 } function exit_script() { clear printf "\e[?25h" echo -e "\n${CROSS}${RD}User exited script${CL}\n" kill 0 exit 1 } function check_storage_support() { local CONTENT="$1" local -a VALID_STORAGES=() while IFS= read -r line; do local STORAGE_NAME STORAGE_NAME=$(awk '\''{print $1}'\'' <<<"$line") [[ -z "$STORAGE_NAME" ]] && continue VALID_STORAGES+=("$STORAGE_NAME") done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk '\''NR>1'\'') [[ ${#VALID_STORAGES[@]} -gt 0 ]] } # This function selects a storage pool for a given content type (e.g., rootdir, vztmpl). function select_storage() { local CLASS=$1 CONTENT CONTENT_LABEL case $CLASS in container) CONTENT='\''rootdir'\'' CONTENT_LABEL='\''Container'\'' ;; template) CONTENT='\''vztmpl'\'' CONTENT_LABEL='\''Container template'\'' ;; iso) CONTENT='\''iso'\'' CONTENT_LABEL='\''ISO image'\'' ;; images) CONTENT='\''images'\'' CONTENT_LABEL='\''VM Disk image'\'' ;; backup) CONTENT='\''backup'\'' CONTENT_LABEL='\''Backup'\'' ;; snippets) CONTENT='\''snippets'\'' CONTENT_LABEL='\''Snippets'\'' ;; *) msg_error "Invalid storage class '\''$CLASS'\''" return 1 ;; esac # Check for preset STORAGE variable if [ "$CONTENT" = "rootdir" ] && [ -n "${STORAGE:-}" ]; then if pvesm status -content "$CONTENT" | awk '\''NR>1 {print $1}'\'' | grep -qx "$STORAGE"; then STORAGE_RESULT="$STORAGE" msg_info "Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL" return 0 else msg_error "Preset storage '\''$STORAGE'\'' is not valid for content type '\''$CONTENT'\''." return 2 fi fi local -A STORAGE_MAP local -a MENU local COL_WIDTH=0 while read -r TAG TYPE _ TOTAL USED FREE _; do [[ -n "$TAG" && -n "$TYPE" ]] || continue local STORAGE_NAME="$TAG" local DISPLAY="${STORAGE_NAME} (${TYPE})" local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED") local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE") local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B" STORAGE_MAP["$DISPLAY"]="$STORAGE_NAME" MENU+=("$DISPLAY" "$INFO" "OFF") ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY} done < <(pvesm status -content "$CONTENT" | awk '\''NR>1'\'') if [ ${#MENU[@]} -eq 0 ]; then msg_error "No storage found for content type '\''$CONTENT'\''." return 2 fi if [ $((${#MENU[@]} / 3)) -eq 1 ]; then STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}" STORAGE_INFO="${MENU[1]}" return 0 fi local WIDTH=$((COL_WIDTH + 42)) while true; do local DISPLAY_SELECTED DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ --title "Storage Pools" \ --radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ 16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) # Cancel or ESC [[ $? -ne 0 ]] && exit_script # Strip trailing whitespace or newline (important for storages like "storage (dir)") DISPLAY_SELECTED=$(sed '\''s/[[:space:]]*$//'\'' <<<"$DISPLAY_SELECTED") if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then whiptail --msgbox "No valid storage selected. Please try again." 8 58 continue fi STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}" for ((i = 0; i < ${#MENU[@]}; i += 3)); do if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then STORAGE_INFO="${MENU[$i + 1]}" break fi done return 0 done } # Test if required variables are set [[ "${CTID:-}" ]] || { msg_error "You need to set '\''CTID'\'' variable." exit 203 } [[ "${PCT_OSTYPE:-}" ]] || { msg_error "You need to set '\''PCT_OSTYPE'\'' variable." exit 204 } # Test if ID is valid [ "$CTID" -ge "100" ] || { msg_error "ID cannot be less than 100." exit 205 } # Test if ID is in use if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then echo -e "ID '\''$CTID'\'' is already in use." unset CTID msg_error "Cannot use ID that is already in use." exit 206 fi # This checks for the presence of valid Container Storage and Template Storage locations msg_info "Validating storage" if ! check_storage_support "rootdir"; then msg_error "No valid storage found for '\''rootdir'\'' [Container]" exit 1 fi if ! check_storage_support "vztmpl"; then msg_error "No valid storage found for '\''vztmpl'\'' [Template]" exit 1 fi #msg_info "Checking template storage" while true; do if select_storage template; then TEMPLATE_STORAGE="$STORAGE_RESULT" TEMPLATE_STORAGE_INFO="$STORAGE_INFO" msg_ok "Storage ${BL}$TEMPLATE_STORAGE${CL} ($TEMPLATE_STORAGE_INFO) [Template]" break fi done while true; do if select_storage container; then CONTAINER_STORAGE="$STORAGE_RESULT" CONTAINER_STORAGE_INFO="$STORAGE_INFO" msg_ok "Storage ${BL}$CONTAINER_STORAGE${CL} ($CONTAINER_STORAGE_INFO) [Container]" break fi done # Check free space on selected container storage STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '\''$1 == s { print $6 }'\'') REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024)) if [ "$STORAGE_FREE" -lt "$REQUIRED_KB" ]; then msg_error "Not enough space on '\''$CONTAINER_STORAGE'\''. Needed: ${PCT_DISK_SIZE:-8}G." exit 214 fi # Check Cluster Quorum if in Cluster if [ -f /etc/pve/corosync.conf ]; then msg_info "Checking cluster quorum" if ! pvecm status | awk -F'\'':'\'' '\''/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'\''; then msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)." exit 210 fi msg_ok "Cluster is quorate" fi # Update LXC template list TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" case "$PCT_OSTYPE" in debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;; alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;; *) TEMPLATE_PATTERN="" ;; esac # 1. Check local templates first msg_info "Searching for template '\''$TEMPLATE_SEARCH'\''" mapfile -t TEMPLATES < <( pveam list "$TEMPLATE_STORAGE" | awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '\''$1 ~ s && $1 ~ p {print $1}'\'' | sed '\''s/.*\///'\'' | sort -t - -k 2 -V ) if [ ${#TEMPLATES[@]} -gt 0 ]; then TEMPLATE_SOURCE="local" else msg_info "No local template found, checking online repository" pveam update >/dev/null 2>&1 mapfile -t TEMPLATES < <( pveam update >/dev/null 2>&1 && pveam available -section system | sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" | sort -t - -k 2 -V ) TEMPLATE_SOURCE="online" fi TEMPLATE="${TEMPLATES[-1]}" TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || echo "/var/lib/vz/template/cache/$TEMPLATE")" msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]" # 4. Validate template (exists & not corrupted) TEMPLATE_VALID=1 if [ ! -s "$TEMPLATE_PATH" ]; then TEMPLATE_VALID=0 elif ! tar --use-compress-program=zstdcat -tf "$TEMPLATE_PATH" >/dev/null 2>&1; then TEMPLATE_VALID=0 fi if [ "$TEMPLATE_VALID" -eq 0 ]; then msg_warn "Template $TEMPLATE is missing or corrupted. Re-downloading." [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" for attempt in {1..3}; do msg_info "Attempt $attempt: Downloading LXC template..." if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then msg_ok "Template download successful." break fi if [ $attempt -eq 3 ]; then msg_error "Failed after 3 attempts. Please check network access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" exit 208 fi sleep $((attempt * 5)) done fi msg_info "Creating LXC Container" # Check and fix subuid/subgid grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid # Combine all options PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}}) [[ " ${PCT_OPTIONS[@]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}") # Secure creation of the LXC container with lock and template check lockfile="/tmp/template.${TEMPLATE}.lock" exec 9>"$lockfile" || { msg_error "Failed to create lock file '\''$lockfile'\''." exit 200 } flock -w 60 9 || { msg_error "Timeout while waiting for template lock" exit 211 } if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then msg_error "Container creation failed. Checking if template is corrupted or incomplete." if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then msg_error "Template file too small or missing – re-downloading." rm -f "$TEMPLATE_PATH" elif ! zstdcat "$TEMPLATE_PATH" | tar -tf - &>/dev/null; then msg_error "Template appears to be corrupted – re-downloading." rm -f "$TEMPLATE_PATH" else msg_error "Template is valid, but container creation still failed." exit 209 fi # Retry download for attempt in {1..3}; do msg_info "Attempt $attempt: Re-downloading template..." if timeout 120 pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null; then msg_ok "Template re-download successful." break fi if [ "$attempt" -eq 3 ]; then msg_error "Three failed attempts. Aborting." exit 208 fi sleep $((attempt * 5)) done sleep 1 # I/O-Sync-Delay msg_ok "Re-downloaded LXC Template" fi if ! pct list | awk '\''{print $1}'\'' | grep -qx "$CTID"; then msg_error "Container ID $CTID not listed in '\''pct list'\'' – unexpected failure." exit 215 fi if ! grep -q '\''^rootfs:'\'' "/etc/pve/lxc/$CTID.conf"; then msg_error "RootFS entry missing in container config – storage not correctly assigned." exit 216 fi if grep -q '\''^hostname:'\'' "/etc/pve/lxc/$CTID.conf"; then CT_HOSTNAME=$(grep '\''^hostname:'\'' "/etc/pve/lxc/$CTID.conf" | awk '\''{print $2}'\'') if [[ ! "$CT_HOSTNAME" =~ ^[a-z0-9-]+$ ]]; then msg_warn "Hostname '\''$CT_HOSTNAME'\'' contains invalid characters – may cause issues with networking or DNS." fi fi msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."' 0 ✔️ Storage local (Free: 65.5GB Used: 21.6GB) [Template] ✔️ Storage local-lvm (Free: 612.7GB Used: 73.5GB) [Container] ✔️ Template ubuntu-24.04-standard_24.04-2_amd64.tar.zst [local] ✔️ LXC Container 106 was successfully created. + LXC_CONFIG=/etc/pve/lxc/106.conf + '[' 1 == 0 ']' + VAAPI_APPS=("immich" "Channels" "Emby" "ErsatzTV" "Frigate" "Jellyfin" "Plex" "Scrypted" "Tdarr" "Unmanic" "Ollama" "FileFlows" "Open WebUI") + is_vaapi_app=false + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \i\m\m\i\c\h ]] + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \C\h\a\n\n\e\l\s ]] + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \E\m\b\y ]] + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \E\r\s\a\t\z\T\V ]] + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \F\r\i\g\a\t\e ]] + for vaapi_app in "${VAAPI_APPS[@]}" + [[ Jellyfin == \J\e\l\l\y\f\i\n ]] + is_vaapi_app=true + break + '[' 1 == 0 ']' + '[' true == true ']' + [[ -e /dev/dri/renderD128 ]] + [[ -e /dev/dri/card0 ]] + echo '' + msg_custom '⚙️ ' '\e[96m' 'Configuring VAAPI passthrough for LXC container' + local 'symbol=⚙️ ' + local 'color=\e[96m' + local 'msg=Configuring VAAPI passthrough for LXC container' + [[ -z Configuring VAAPI passthrough for LXC container ]] + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + echo -e '\r\033[K ⚙️ \e[96mConfiguring VAAPI passthrough for LXC container\033[m' ⚙️ Configuring VAAPI passthrough for LXC container + '[' 1 '!=' 0 ']' + msg_custom '⚠️ ' '\e[33m' 'Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).' + local 'symbol=⚠️ ' + local 'color=\e[33m' + local 'msg=Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).' + [[ -z Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap). ]] + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + echo -e '\r\033[K ⚠️ \e[33mContainer is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap).\033[m' ⚠️ Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap). + msg_custom 'ℹ️ ' '\e[96m' 'VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).' + local 'symbol=ℹ️ ' + local 'color=\e[96m' + local 'msg=VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).' + [[ -z VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex). ]] + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + echo -e '\r\033[K ℹ️ \e[96mVAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex).\033[m' ℹ️ VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex). + echo '' + read -rp '➤ Automatically mount all available VAAPI devices? [Y/n]: ' VAAPI_ALL ➤ Automatically mount all available VAAPI devices? [Y/n]: y + [[ y =~ ^[Yy]$|^$ ]] + '[' 1 == 0 ']' + [[ -e /dev/dri/card0 ]] + echo 'dev0: /dev/dri/card0,gid=44' + [[ -e /dev/dri/card1 ]] + [[ -e /dev/dri/renderD128 ]] + '[' 1 == 1 ']' + '[' true == true ']' + [[ -e /dev/dri/card0 ]] + echo 'dev0: /dev/dri/card0,gid=44' + [[ -e /dev/dri/renderD128 ]] + '[' yes == yes ']' + cat + msg_info 'Starting LXC Container' + local 'msg=Starting LXC Container' + [[ -z Starting LXC Container ]] + declare -p MSG_INFO_SHOWN + MSG_INFO_SHOWN=() + declare -gA MSG_INFO_SHOWN + [[ -n '' ]] + MSG_INFO_SHOWN["$msg"]=1 + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + SPINNER_MSG='Starting LXC Container' + is_verbose_mode + local verbose=yes + local tty_status + [[ -t 2 ]] + tty_status=interactive + [[ yes != \n\o ]] + local 'HOURGLASS= ⏳ ' + printf '\r\e[2K%s %b' ' ⏳ ' '\033[33mStarting LXC Container\033[m' ⏳ Starting LXC Container+ return + pct start 106 + for i in {1..10} + pct status 106 + grep -q 'status: running' + msg_ok 'Started LXC Container' + local 'msg=Started LXC Container' + [[ -z Started LXC Container ]] + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + clear_line + tput cr + tput el + printf '%s %b\n' ' ✔️ ' '\033[1;92mStarted LXC Container\033[m' ✔️ Started LXC Container + unset 'MSG_INFO_SHOWN[Started LXC Container]' + break + '[' ubuntu '!=' alpine ']' + msg_info 'Waiting for network in LXC container' + local 'msg=Waiting for network in LXC container' + [[ -z Waiting for network in LXC container ]] + declare -p MSG_INFO_SHOWN + declare -A MSG_INFO_SHOWN + [[ -n '' ]] + MSG_INFO_SHOWN["$msg"]=1 + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + SPINNER_MSG='Waiting for network in LXC container' + is_verbose_mode + local verbose=yes + local tty_status + [[ -t 2 ]] + tty_status=interactive + [[ yes != \n\o ]] + local 'HOURGLASS= ⏳ ' + printf '\r\e[2K%s %b' ' ⏳ ' '\033[33mWaiting for network in LXC container\033[m' ⏳ Waiting for network in LXC container+ return + for i in {1..10} + pct exec 106 -- ping -c1 -W1 deb.debian.org + '[' 1 -lt 10 ']' + msg_warn 'No network in LXC yet (try 1/10) – waiting...' + stop_spinner + local pid= + [[ -z '' ]] + [[ -f /tmp/.spinner.pid ]] + [[ -n '' ]] + unset SPINNER_PID SPINNER_MSG + stty sane + local 'msg=No network in LXC yet (try 1/10) – waiting...' + echo -e '\r\033[K 💡 \033[m No network in LXC yet (try 1/10) – waiting...\033[m' 💡 No network in LXC yet (try 1/10) – waiting... + sleep 3 + for i in {1..10} + pct exec 106 -- ping -c1 -W1 deb.debian.org ``` </details>
Author
Owner

@MickLesk commented on GitHub (Sep 16, 2025):

Then try following, add before bash:

var_version=12 var_os=debian var_verbose=yes bash....

@MickLesk commented on GitHub (Sep 16, 2025): Then try following, add before bash: var_version=12 var_os=debian var_verbose=yes bash....
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

That seems to work!!
It is connected now, and it's currently updating the container

@sander1946 commented on GitHub (Sep 16, 2025): That seems to work!! It is connected now, and it's currently updating the container
Author
Owner

@MickLesk commented on GitHub (Sep 16, 2025):

Strange. Its Just an Ubuntu Thing, but its the Same call Like Debian

@MickLesk commented on GitHub (Sep 16, 2025): Strange. Its Just an Ubuntu Thing, but its the Same call Like Debian
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

Alright, I have finished the installation, and now everything seems to be good, I will keep that I mind for myself the next time I will run a community script. Still don't know why, but I will just deal with it.

This issue can be closed!

@sander1946 commented on GitHub (Sep 16, 2025): Alright, I have finished the installation, and now everything seems to be good, I will keep that I mind for myself the next time I will run a community script. Still don't know why, but I will just deal with it. This issue can be closed!
Author
Owner

@MickLesk commented on GitHub (Sep 16, 2025):

I Ping you tomorrow, i build tomorrow an Special Test in Dev Repo, maybe this helps to get the Error

@MickLesk commented on GitHub (Sep 16, 2025): I Ping you tomorrow, i build tomorrow an Special Test in Dev Repo, maybe this helps to get the Error
Author
Owner

@sander1946 commented on GitHub (Sep 16, 2025):

Alright, thanks for doing that. I appreciate it!

@sander1946 commented on GitHub (Sep 16, 2025): Alright, thanks for doing that. I appreciate it!
Author
Owner

@sander1946 commented on GitHub (Sep 17, 2025):

I may have found the issue, it seems that the DNS the ubuntu containers area getting is one from tailscale, but tailscale is not even installed on it. i have no idea how it would even get it

A manually installed Ubuntu CT:
resolvectl status:

Global
         Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
  resolv.conf mode: foreign
Current DNS Server: 100.100.100.100
       DNS Servers: 100.100.100.100
        DNS Domain: tail52f553.ts.net

Link 2 (eth0)
Current Scopes: DNS
     Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
   DNS Servers: 192.168.2.254
    DNS Domain: home

Manually installed VM:
resolvectl status:
Image

I have also checked a different Ubuntu image, that does not change. Same result.

/etc/resolv.conf

# --- BEGIN PVE ---
search tail52f553.ts.net
nameserver 100.100.100.100
# --- END PVE ---

On a fresh debian CT its correct.
/etc/resolv.conf

domain home
search home
nameserver 192.168.2.254
@sander1946 commented on GitHub (Sep 17, 2025): I may have found the issue, it seems that the DNS the ubuntu containers area getting is one from tailscale, but tailscale is not even installed on it. i have no idea how it would even get it A manually installed Ubuntu CT: `resolvectl status`: ```text Global Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: foreign Current DNS Server: 100.100.100.100 DNS Servers: 100.100.100.100 DNS Domain: tail52f553.ts.net Link 2 (eth0) Current Scopes: DNS Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported DNS Servers: 192.168.2.254 DNS Domain: home ``` Manually installed VM: `resolvectl status`: <img width="1556" height="705" alt="Image" src="https://github.com/user-attachments/assets/0f85aab3-9e20-460a-86ca-d400bdf5c891" /> I have also checked a different Ubuntu image, that does not change. Same result. `/etc/resolv.conf` ```conf # --- BEGIN PVE --- search tail52f553.ts.net nameserver 100.100.100.100 # --- END PVE --- ``` On a fresh debian CT its correct. `/etc/resolv.conf` ```conf domain home search home nameserver 192.168.2.254 ```
Author
Owner

@MickLesk commented on GitHub (Sep 19, 2025):

can you remove your ubuntu template /var/lib/vz/template/cache/ ?
I dont have any tailscale inside, it looks like an corrupted template (or edited by host)

i think your /etc/resolv.conf have old tailscale inputs? (host)

Can you run the DEV Instance?
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/ct/ubuntu.sh)"

@MickLesk commented on GitHub (Sep 19, 2025): can you remove your ubuntu template /var/lib/vz/template/cache/ ? I dont have any tailscale inside, it looks like an corrupted template (or edited by host) i think your /etc/resolv.conf have old tailscale inputs? (host) Can you run the DEV Instance? bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/ct/ubuntu.sh)"
Author
Owner

@sander1946 commented on GitHub (Sep 30, 2025):

Sorry for not responding for so long @MickLesk .
But i have figured out what the problem is. Tailscale is overwriting the /etc/resolve.conf on the host, that then affects all containers, at least all the ubuntu containers.
This is the Tailscale page about running Tailscale on Proxmox.
According to the page there are only really 2 options to mitigate this. Both i do not really like or want to use, so for the time being i will just use Debian LXC containers instead of any ubuntu LXC containers

@sander1946 commented on GitHub (Sep 30, 2025): Sorry for not responding for so long @MickLesk . But i have figured out what the problem is. Tailscale is overwriting the /etc/resolve.conf on the host, that then affects all containers, at least all the ubuntu containers. This is the Tailscale page about running [Tailscale on Proxmox](https://tailscale.com/kb/1133/proxmox). According to the page there are only really 2 options to mitigate this. Both i do not really like or want to use, so for the time being i will just use Debian LXC containers instead of any ubuntu LXC containers
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/ProxmoxVE#1629