2024-02-15 23:04:55 -05:00
#!/usr/bin/env bash
2025-08-08 11:37:36 +02:00
set -eEuo pipefail
2024-02-15 23:04:55 -05:00
function header_info( ) {
clear
cat <<"EOF"
_______ __ __ ______ _
/ ____( _) /__ _______ _______/ /____ ____ ___ /_ __/____( _) ___ ___
/ /_ / / / _ \/ ___/ / / / ___/ __/ _ \/ __ ` __ \ / / / ___/ / __ ` __ \
/ __/ / / / __( __ ) /_/ ( __ ) /_/ __/ / / / / / / / / / / / / / / / /
/_/ /_/_/\_ __/____/\_ _, /____/\_ _/\_ __/_/ /_/ /_/ /_/ /_/ /_/_/ /_/ /_/
/____/
EOF
}
2025-08-08 11:37:36 +02:00
BL = "\033[36m"
RD = "\033[01;31m"
GN = "\033[1;92m"
CL = "\033[m"
2026-02-17 16:36:20 +01:00
# Telemetry
source <( curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
core: Execution ID & Telemetry Improvements (#12041)
* fix: send telemetry BEFORE log collection in signal handlers
- Swap ensure_log_on_host/post_update_to_api order in on_interrupt, on_terminate, api_exit_script, and inline SIGHUP/SIGINT/SIGTERM traps
- For signal exits (>128): send telemetry immediately, then best-effort log collection
- Add 2>/dev/null || true to all I/O in signal handlers to prevent SIGPIPE
- Fix on_exit: exit_code=0 now reports 'done' instead of 'failed 1'
- Root cause: pct pull hangs on dying containers blocked telemetry updates, leaving 595+ records stuck in 'installing' daily
* feat: add execution_id to all telemetry payloads
- Generate EXECUTION_ID from RANDOM_UUID in variables()
- Export EXECUTION_ID to container environment
- Add execution_id field to all 8 API payloads in api.func
- Add execution_id to post_progress_to_api in install.func and alpine-install.func
- Fallback to RANDOM_UUID when EXECUTION_ID not set (backward compat)
* fix: correct telemetry type values for PVE and addon scripts
- PVE scripts (tools/pve/*): change type 'tool' -> 'pve'
- Addon scripts (tools/addon/*): fix 4 scripts that wrongly used 'tool' -> 'addon'
(netdata, add-tailscale-lxc, add-netbird-lxc, all-templates)
- api.func: post_tool_to_api sends type='pve', default fallback 'pve'
- Aligns with PocketBase categories: lxc, vm, pve, addon
* fix: persist diagnostics opt-in inside containers for addon telemetry
- install.func + alpine-install.func: create /usr/local/community-scripts/diagnostics
inside the container when DIAGNOSTICS=yes (from build.func export)
- Enables addon scripts running later inside containers to find the opt-in
- Update init_tool_telemetry default type from 'tool' to 'pve'
* refactor: clean up diagnostics/telemetry opt-in system
- diagnostics_check(): deduplicate heredoc (was 2x 22 lines), improve whiptail
text with clear what/what-not collected, add telemetry + privacy links
- diagnostics_menu(): better UX with current status, clear enable/disable
buttons, note about existing containers
- variables(): change DIAGNOSTICS default from 'yes' to 'no' (safe: no
telemetry before user consents via diagnostics_check)
- install.func + alpine-install.func: persist BOTH yes AND no in container
so opt-out is explicit (not just missing file = no)
- Fix typo 'menue' -> 'menu' in config file comments
* fix: no pre-selection in telemetry dialog, link to telemetry-service README
- Add --defaultno so 'No, opt out' is focused by default (user must Tab to Yes)
- Change privacy link from discussions/1836 to telemetry-service#privacy--compliance
* fix: use radiolist for telemetry dialog (no pre-selection)
- Replace --yesno with --radiolist: user must actively SPACE-select an option
- Both options start as OFF (no pre-selection)
- Cancel/Exit defaults to 'no' (opt-out)
* simplify: inline telemetry dialog text like other whiptail dialogs
* improve: telemetry dialog with more detail, link to PRIVACY.md
- Add what we collect / don't collect sections back to dialog
- Link to telemetry-service/docs/PRIVACY.md instead of README anchor
- Update config file comment with same link
2026-02-18 10:24:06 +01:00
declare -f init_tool_telemetry & >/dev/null && init_tool_telemetry "fstrim" "pve"
2026-02-17 16:36:20 +01:00
2025-08-08 11:37:36 +02:00
LOGFILE = "/var/log/fstrim.log"
touch " $LOGFILE "
chmod 600 " $LOGFILE "
echo -e " \n----- $( date '+%Y-%m-%d %H:%M:%S' ) | fstrim Run by $( whoami) on $( hostname) ----- " >>" $LOGFILE "
2025-02-03 09:25:50 +01:00
2024-02-15 23:04:55 -05:00
header_info
echo "Loading..."
2025-08-08 11:37:36 +02:00
whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "About fstrim (LXC)" \
--msgbox "The 'fstrim' command releases unused blocks back to the storage device. This only makes sense for containers on SSD, NVMe, Thin-LVM, or storage with discard/TRIM support.\n\nIf your root filesystem or container disks are on classic HDDs, thick LVM, or unsupported storage types, running fstrim will have no effect.\n\nRecommended:\n- Use fstrim only on SSD, NVMe, or thin-provisioned storage with discard enabled.\n- For ZFS, ensure 'autotrim=on' is set on your pool.\n" 16 88
2024-02-15 23:04:55 -05:00
ROOT_FS = $( df -Th "/" | awk 'NR==2 {print $2}' )
if [ " $ROOT_FS " != "ext4" ] ; then
2025-08-08 11:37:36 +02:00
whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "Warning" \
--yesno " Root filesystem is not ext4 ( $ROOT_FS ).\nContinue anyway? " 12 80 || exit 1
2024-02-15 23:04:55 -05:00
fi
NODE = $( hostname)
EXCLUDE_MENU = ( )
2025-08-08 11:37:36 +02:00
STOPPED_MENU = ( )
MAX_NAME_LEN = 0
MAX_STAT_LEN = 0
# Build arrays with one pct list
mapfile -t CTLINES < <( pct list | awk 'NR>1' )
for LINE in " ${ CTLINES [@] } " ; do
CTID = $( awk '{print $1}' <<< " $LINE " )
STATUS = $( awk '{print $2}' <<< " $LINE " )
NAME = $( awk '{print $3}' <<< " $LINE " )
( ( ${# NAME } > MAX_NAME_LEN) ) && MAX_NAME_LEN = ${# NAME }
( ( ${# STATUS } > MAX_STAT_LEN) ) && MAX_STAT_LEN = ${# STATUS }
done
2025-02-03 09:25:50 +01:00
2025-08-08 11:37:36 +02:00
FMT = " %- ${ MAX_NAME_LEN } s | %- ${ MAX_STAT_LEN } s "
for LINE in " ${ CTLINES [@] } " ; do
CTID = $( awk '{print $1}' <<< " $LINE " )
STATUS = $( awk '{print $2}' <<< " $LINE " )
NAME = $( awk '{print $3}' <<< " $LINE " )
DESC = $( printf " $FMT " " $NAME " " $STATUS " )
EXCLUDE_MENU += ( " $CTID " " $DESC " "OFF" )
if [ [ " $STATUS " = = "stopped" ] ] ; then
STOPPED_MENU += ( " $CTID " " $DESC " "OFF" )
fi
done
2025-02-03 09:25:50 +01:00
excluded_containers_raw = $( whiptail --backtitle "Proxmox VE Helper Scripts" \
2025-04-01 10:25:46 +02:00
--title " Containers on $NODE " \
--checklist "\nSelect containers to skip from trimming:\n" \
2025-08-08 11:37:36 +02:00
20 $(( MAX_NAME_LEN + MAX_STAT_LEN + 20 )) 12 " ${ EXCLUDE_MENU [@] } " 3>& 1 1>& 2 2>& 3)
[ $? -ne 0 ] && exit
read -ra EXCLUDED <<< $( echo " $excluded_containers_raw " | tr -d '"' )
TO_START = ( )
if [ ${# STOPPED_MENU [@] } -gt 0 ] ; then
for ( ( i = 0; i < ${# STOPPED_MENU [@] } ; i += 3) ) ; do
CTID = " ${ STOPPED_MENU [i] } "
DESC = " ${ STOPPED_MENU [i + 1] } "
if [ [ " ${ EXCLUDED [*] } " = ~ " $CTID " ] ] ; then
continue
fi
header_info
echo -e " ${ BL } [Info] ${ GN } Container $CTID ( $DESC ) is currently stopped. ${ CL } "
read -rp "Temporarily start for fstrim? [y/N]: " answer
if [ [ " $answer " = ~ ^[ Yy] $ ] ] ; then
TO_START += ( " $CTID " )
fi
done
2025-02-03 09:25:50 +01:00
fi
2025-08-08 11:37:36 +02:00
declare -A WAS_STOPPED
for ct in " ${ TO_START [@] } " ; do
WAS_STOPPED[ " $ct " ] = 1
done
2024-02-15 23:04:55 -05:00
function trim_container( ) {
2025-08-08 11:37:36 +02:00
local container = " $1 "
local name = " $2 "
2024-02-15 23:04:55 -05:00
header_info
2024-02-19 13:13:44 -05:00
echo -e " ${ BL } [Info] ${ GN } Trimming ${ BL } $container ${ CL } \n "
2025-04-01 10:25:46 +02:00
2025-08-08 11:37:36 +02:00
local before_trim after_trim
local lv_name = " vm- ${ container } -disk-0 "
if lvs --noheadings -o lv_name 2>/dev/null | grep -qw " $lv_name " ; then
before_trim = $( lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid = " $lv_name " '$1 == ctid {gsub(/%/, "", $2); print $2}' )
[ [ -n " $before_trim " ] ] && echo -e " ${ RD } Data before trim $before_trim % ${ CL } " || echo -e " ${ RD } Data before trim: not available ${ CL } "
else
before_trim = ""
echo -e " ${ RD } Data before trim: not available (non-LVM storage) ${ CL } "
fi
2025-04-01 10:25:46 +02:00
2025-08-08 11:37:36 +02:00
local fstrim_output
fstrim_output = $( pct fstrim " $container " 2>& 1)
if echo " $fstrim_output " | grep -qi "not supported" ; then
echo -e " ${ RD } fstrim isnt supported on this storage! ${ CL } "
elif echo " $fstrim_output " | grep -Eq '([0-9]+(\.[0-9]+)?\s*[KMGT]?B)' ; then
echo -e " ${ GN } fstrim result: $fstrim_output ${ CL } "
else
echo -e " ${ RD } fstrim result: $fstrim_output ${ CL } "
fi
2025-04-01 10:25:46 +02:00
2025-08-08 11:37:36 +02:00
if lvs --noheadings -o lv_name 2>/dev/null | grep -qw " $lv_name " ; then
after_trim = $( lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid = " $lv_name " '$1 == ctid {gsub(/%/, "", $2); print $2}' )
[ [ -n " $after_trim " ] ] && echo -e " ${ GN } Data after trim $after_trim % ${ CL } " || echo -e " ${ GN } Data after trim: not available ${ CL } "
else
after_trim = ""
echo -e " ${ GN } Data after trim: not available (non-LVM storage) ${ CL } "
fi
2025-04-01 10:25:46 +02:00
2025-08-08 11:37:36 +02:00
# Logging
echo " $( date '+%Y-%m-%d %H:%M:%S' ) | CTID= $container | Name= $name | Before= ${ before_trim :- N /A } % | After= ${ after_trim :- N /A } % | fstrim: $fstrim_output " >>" $LOGFILE "
sleep 0.5
2024-02-15 23:04:55 -05:00
}
2025-08-08 11:37:36 +02:00
for LINE in " ${ CTLINES [@] } " ; do
CTID = $( awk '{print $1}' <<< " $LINE " )
STATUS = $( awk '{print $2}' <<< " $LINE " )
NAME = $( awk '{print $3}' <<< " $LINE " )
if [ [ " ${ EXCLUDED [*] } " = ~ " $CTID " ] ] ; then
2024-02-15 23:04:55 -05:00
header_info
2025-08-08 11:37:36 +02:00
echo -e " ${ BL } [Info] ${ GN } Skipping $CTID ( $NAME , excluded) ${ CL } "
sleep 0.5
continue
fi
if pct config " $CTID " | grep -q "template:" ; then
header_info
echo -e " ${ BL } [Info] ${ GN } Skipping $CTID ( $NAME , template) ${ CL } \n "
sleep 0.5
continue
fi
if [ [ " $STATUS " != "running" ] ] ; then
if [ [ -n " ${ WAS_STOPPED [ $CTID ] :- } " ] ] ; then
2024-02-15 23:04:55 -05:00
header_info
2025-08-08 11:37:36 +02:00
echo -e " ${ BL } [Info] ${ GN } Starting $CTID ( $NAME ) for trim... ${ CL } "
pct start " $CTID "
sleep 2
else
header_info
echo -e " ${ BL } [Info] ${ GN } Skipping $CTID ( $NAME , not running, not selected) ${ CL } "
sleep 0.5
2024-02-15 23:04:55 -05:00
continue
fi
2025-08-08 11:37:36 +02:00
fi
trim_container " $CTID " " $NAME "
if [ [ -n " ${ WAS_STOPPED [ $CTID ] :- } " ] ] ; then
read -rp " Stop LXC $CTID ( $NAME ) again after trim? [Y/n]: " answer
if [ [ ! " $answer " = ~ ^[ Nn] $ ] ] ; then
header_info
echo -e " ${ BL } [Info] ${ GN } Stopping $CTID ( $NAME ) again... ${ CL } "
pct stop " $CTID "
sleep 1
else
header_info
echo -e " ${ BL } [Info] ${ GN } Leaving $CTID ( $NAME ) running as requested. ${ CL } "
sleep 1
fi
2024-02-15 23:04:55 -05:00
fi
done
header_info
2025-02-03 09:25:50 +01:00
echo -e " ${ GN } Finished, LXC Containers Trimmed. ${ CL } \n "
2025-08-08 11:37:36 +02:00
echo -e " ${ BL } If you want to see the complete log: cat $LOGFILE ${ CL } "
exit 0