44# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
55
66if ! command -v curl >/dev/null 2>&1; then
7- apk update && apk add curl >/dev/null 2>&1
7+ apk update && apk add curl >/dev/null 2>&1
88fi
99source "$(dirname "${BASH_SOURCE[0]}")/core.func"
1010source "$(dirname "${BASH_SOURCE[0]}")/error-handler.func"
1111load_functions
1212catch_errors
1313
14+ # Persist diagnostics setting inside container (exported from build.func)
15+ # so addon scripts running later can find the user's choice
16+ if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
17+ mkdir -p /usr/local/community-scripts
18+ echo "DIAGNOSTICS=${DIAGNOSTICS:-no}" >/usr/local/community-scripts/diagnostics
19+ fi
20+
1421# Get LXC IP address (must be called INSIDE container, after network is up)
1522get_lxc_ip
1623
24+ # ------------------------------------------------------------------------------
25+ # post_progress_to_api()
26+ #
27+ # - Lightweight progress ping from inside the container
28+ # - Updates the existing telemetry record status
29+ # - Arguments:
30+ # * $1: status (optional, default: "configuring")
31+ # - Signals that the installation is actively progressing (not stuck)
32+ # - Fire-and-forget: never blocks or fails the script
33+ # - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
34+ # ------------------------------------------------------------------------------
35+ post_progress_to_api() {
36+ command -v curl &>/dev/null || return 0
37+ [[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
38+ [[ -z "${RANDOM_UUID:-}" ]] && return 0
39+
40+ local progress_status="${1:-configuring}"
41+
42+ curl -fsS -m 5 -X POST "https://telemetry.community-scripts.org/telemetry" \
43+ -H "Content-Type: application/json" \
44+ -d "{\"random_id\":\"${RANDOM_UUID}\",\"execution_id\":\"${EXECUTION_ID:-${RANDOM_UUID}}\",\"type\":\"lxc\",\"nsapp\":\"${app:-unknown}\",\"status\":\"${progress_status}\"}" &>/dev/null || true
45+ }
46+
1747# This function enables IPv6 if it's not disabled and sets verbose mode
1848verb_ip6() {
19- set_std_mode # Set STD mode based on VERBOSE
20-
21- if [ "${IPV6_METHOD:-}" = "disable" ]; then
22- msg_info "Disabling IPv6 (this may affect some services)"
23- $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1
24- $STD sysctl -w net.ipv6.conf.default.disable_ipv6=1
25- $STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1
26- mkdir -p /etc/sysctl.d
27- $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF
49+ set_std_mode # Set STD mode based on VERBOSE
50+
51+ if [ "${IPV6_METHOD:-}" = "disable" ]; then
52+ msg_info "Disabling IPv6 (this may affect some services)"
53+ $STD sysctl -w net.ipv6.conf.all.disable_ipv6=1
54+ $STD sysctl -w net.ipv6.conf.default.disable_ipv6=1
55+ $STD sysctl -w net.ipv6.conf.lo.disable_ipv6=1
56+ mkdir -p /etc/sysctl.d
57+ $STD tee /etc/sysctl.d/99-disable-ipv6.conf >/dev/null <<EOF
2858net.ipv6.conf.all.disable_ipv6 = 1
2959net.ipv6.conf.default.disable_ipv6 = 1
3060net.ipv6.conf.lo.disable_ipv6 = 1
3161EOF
32- $STD rc-update add sysctl default
33- msg_ok "Disabled IPv6"
34- fi
35- }
36-
37- set -Eeuo pipefail
38- trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
39- trap on_exit EXIT
40- trap on_interrupt INT
41- trap on_terminate TERM
42-
43- error_handler() {
44- local exit_code="$1"
45- local line_number="$2"
46- local command="$3"
47-
48- if [[ "$exit_code" -eq 0 ]]; then
49- return 0
50- fi
51-
52- printf "\e[?25h"
53- 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"
54- exit "$exit_code"
55- }
56-
57- on_exit() {
58- local exit_code="$?"
59- [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
60- exit "$exit_code"
61- }
62-
63- on_interrupt() {
64- echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
65- exit 130
66- }
67-
68- on_terminate() {
69- echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
70- exit 143
62+ $STD rc-update add sysctl default
63+ msg_ok "Disabled IPv6"
64+ fi
7165}
7266
7367# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection
7468setting_up_container() {
75- msg_info "Setting up Container OS"
76- while [ $i -gt 0 ]; do
77- if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" != "" ]; then
78- break
79- fi
80- echo 1>&2 -en "${CROSS}${RD} No Network! "
81- sleep $RETRY_EVERY
82- i=$((i - 1))
83- done
84-
85- if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then
86- echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
87- echo -e "${NETWORK}Check Network Settings"
88- exit 1
89- fi
90- msg_ok "Set up Container OS"
91- msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}"
69+ msg_info "Setting up Container OS"
70+ while [ $i -gt 0 ]; do
71+ if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" != "" ]; then
72+ break
73+ fi
74+ echo 1>&2 -en "${CROSS}${RD} No Network! "
75+ sleep $RETRY_EVERY
76+ i=$((i - 1))
77+ done
78+
79+ if [ "$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1)" = "" ]; then
80+ echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}"
81+ echo -e "${NETWORK}Check Network Settings"
82+ exit 121
83+ fi
84+ msg_ok "Set up Container OS"
85+ msg_ok "Network Connected: ${BL}$(ip addr show | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1 | tail -n1)${CL}"
86+ post_progress_to_api
9287}
9388
9489# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
9590network_check() {
96- set +e
97- trap - ERR
98- 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
99- ipv4_status="${GN}✔${CL} IPv4"
100- else
101- ipv4_status="${RD}✖${CL} IPv4"
102- read -r -p "Internet NOT connected. Continue anyway? <y/N> " prompt
103- if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
104- echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
105- else
106- echo -e "${NETWORK}Check Network Settings"
107- exit 1
108- fi
109- fi
110- RESOLVEDIP=$(getent hosts github.com | awk '{ print $1 }')
111- if [[ -z "$RESOLVEDIP" ]]; then
112- msg_error "Internet: ${ipv4_status} DNS Failed"
113- else
114- msg_ok "Internet: ${ipv4_status} DNS: ${BL}${RESOLVEDIP}${CL}"
115- fi
116- set -e
117- trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
91+ set +e
92+ trap - ERR
93+ ipv4_connected=false
94+
95+ # Check IPv4 connectivity to Cloudflare, Google & Quad9 DNS servers
96+ 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
97+ msg_ok "IPv4 Internet Connected"
98+ ipv4_connected=true
99+ else
100+ msg_error "IPv4 Internet Not Connected"
101+ fi
102+
103+ if [[ $ipv4_connected == false ]]; then
104+ read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt
105+ if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
106+ echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
107+ else
108+ echo -e "${NETWORK}Check Network Settings"
109+ exit 122
110+ fi
111+ fi
112+
113+ # DNS resolution checks for GitHub-related domains
114+ GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
115+ GIT_STATUS="Git DNS:"
116+ DNS_FAILED=false
117+
118+ for HOST in "${GIT_HOSTS[@]}"; do
119+ RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1)
120+ if [[ -z "$RESOLVEDIP" ]]; then
121+ GIT_STATUS+="$HOST:($DNSFAIL)"
122+ DNS_FAILED=true
123+ else
124+ GIT_STATUS+=" $HOST:($DNSOK)"
125+ fi
126+ done
127+
128+ if [[ "$DNS_FAILED" == true ]]; then
129+ fatal "$GIT_STATUS"
130+ else
131+ msg_ok "$GIT_STATUS"
132+ fi
133+
134+ set -e
135+ trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
118136}
119137
120- # This function updates the Container OS by running apt-get update and upgrade
138+ # This function updates the Container OS by running apk upgrade with mirror fallback
121139update_os() {
122- msg_info "Updating Container OS"
123- $STD apk -U upgrade
124- source "$(dirname "${BASH_SOURCE[0]}")/tools.func"
125- msg_ok "Updated Container OS"
140+ msg_info "Updating Container OS"
141+ if ! $STD apk -U upgrade; then
142+ msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..."
143+ local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org"
144+ local apk_ok=false
145+ for m in $(printf '%s\n' $alpine_mirrors | shuf); do
146+ if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then
147+ msg_custom "${INFO}" "${YW}" "Attempting mirror: ${m}"
148+ cat <<EOF >/etc/apk/repositories
149+ http://$m/alpine/latest-stable/main
150+ http://$m/alpine/latest-stable/community
151+ EOF
152+ if $STD apk -U upgrade; then
153+ msg_ok "CDN set to ${m}: tests passed"
154+ apk_ok=true
155+ break
156+ else
157+ msg_warn "Mirror ${m} failed"
158+ fi
159+ fi
160+ done
161+ if [[ "$apk_ok" != true ]]; then
162+ msg_error "All Alpine mirrors failed. Check network or try again later."
163+ exit 1
164+ fi
165+ fi
166+ local tools_content
167+ tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || {
168+ msg_error "Failed to download tools.func"
169+ exit 115
170+ }
171+ source /dev/stdin <<<"$tools_content"
172+ if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then
173+ msg_error "tools.func loaded but incomplete — missing expected functions"
174+ exit 115
175+ fi
176+ msg_ok "Updated Container OS"
177+ post_progress_to_api
126178}
127179
128180# This function modifies the message of the day (motd) and SSH settings
129181motd_ssh() {
130- echo "export TERM='xterm-256color'" >>/root/.bashrc
131-
132- PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
133- echo "echo -e \"\"" >"$PROFILE_FILE"
134- echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE"
135- 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"
136- echo "echo \"\"" >>"$PROFILE_FILE"
137- echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE"
138- echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
139- echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE"
140-
141- # Configure SSH if enabled
142- if [[ "${SSH_ROOT}" == "yes" ]]; then
143- # Enable sshd service
144- $STD rc-update add sshd
145- # Allow root login via SSH
146- sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
147- # Start the sshd service
148- $STD /etc/init.d/sshd start
149- fi
182+ echo "export TERM='xterm-256color'" >>/root/.bashrc
183+
184+ PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
185+ echo "echo -e \"\"" >"$PROFILE_FILE"
186+ echo -e "echo -e \"${BOLD}${APPLICATION} LXC Container${CL}"\" >>"$PROFILE_FILE"
187+ 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"
188+ echo "echo \"\"" >>"$PROFILE_FILE"
189+ echo -e "echo -e \"${TAB}${OS}${YW} OS: ${GN}\$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '\"') - Version: \$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '\"')${CL}\"" >>"$PROFILE_FILE"
190+ echo -e "echo -e \"${TAB}${HOSTNAME}${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE"
191+ echo -e "echo -e \"${TAB}${INFO}${YW} IP Address: ${GN}\$(ip -4 addr show eth0 | awk '/inet / {print \$2}' | cut -d/ -f1 | head -n 1)${CL}\"" >>"$PROFILE_FILE"
192+
193+ # Configure SSH if enabled
194+ if [[ "${SSH_ROOT}" == "yes" ]]; then
195+ # Enable sshd service
196+ $STD rc-update add sshd
197+ # Allow root login via SSH
198+ sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config
199+ # Start the sshd service
200+ $STD /etc/init.d/sshd start
201+ fi
202+ post_progress_to_api
150203}
151204
152205# Validate Timezone for some LXC's
153206validate_tz() {
154- [[ -f "/usr/share/zoneinfo/$1" ]]
207+ [[ -f "/usr/share/zoneinfo/$1" ]]
155208}
156209
157210# This function customizes the container and enables passwordless login for the root user
158211customize() {
159- if [[ "$PASSWORD" == "" ]]; then
160- msg_info "Customizing Container"
161- passwd -d root >/dev/null 2>&1
212+ if [[ "$PASSWORD" == "" ]]; then
213+ msg_info "Customizing Container"
214+ passwd -d root >/dev/null 2>&1
162215
163- # Ensure agetty is available
164- apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1
216+ # Ensure agetty is available
217+ apk add --no-cache --force-broken-world util-linux >/dev/null 2>&1
165218
166- # Create persistent autologin boot script
167- mkdir -p /etc/local.d
168- cat <<'EOF' >/etc/local.d/autologin.start
219+ # Create persistent autologin boot script
220+ mkdir -p /etc/local.d
221+ cat <<'EOF' >/etc/local.d/autologin.start
169222#!/bin/sh
170223sed -i 's|^tty1::respawn:.*|tty1::respawn:/sbin/agetty --autologin root --noclear tty1 38400 linux|' /etc/inittab
171224kill -HUP 1
172225EOF
173- touch /root/.hushlogin
226+ touch /root/.hushlogin
174227
175- chmod +x /etc/local.d/autologin.start
176- rc-update add local >/dev/null 2>&1
228+ chmod +x /etc/local.d/autologin.start
229+ rc-update add local >/dev/null 2>&1
177230
178- # Apply autologin immediately for current session
179- /etc/local.d/autologin.start
231+ # Apply autologin immediately for current session
232+ /etc/local.d/autologin.start
180233
181- msg_ok "Customized Container"
182- fi
234+ msg_ok "Customized Container"
235+ fi
183236
184- echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
185- chmod +x /usr/bin/update
186-
187- }
237+ echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update
238+ chmod +x /usr/bin/update
239+ post_progress_to_api
240+ }
0 commit comments