diff --git a/PveSensorsV2/PVEMod_Collectors/systemInformation.pm b/PveSensorsV2/PVEMod_Collectors/systemInformation.pm new file mode 100644 index 0000000..de43892 --- /dev/null +++ b/PveSensorsV2/PVEMod_Collectors/systemInformation.pm @@ -0,0 +1,96 @@ +package PVE::PVEMod::Collector::SystemInformation; + +use strict; +use warnings; +use Exporter 'import'; + +use PVE::PVEMod::Config qw(%config); +use PVE::PVEMod::Utils qw(debug); + +our @EXPORT_OK = qw( + get_system_information_data +); + +# ============================================================================ +# System Information — one-time dmidecode call +# ============================================================================ + +sub get_system_information_data { + unless ($config{system_info}{enabled}) { + debug(__LINE__, "System information collection is disabled"); + return {}; + } + + my $raw_type = $config{system_info}{type}; + + # Taint-safe: only allow type 1 (System) or 2 (Baseboard/Motherboard) + my $type; + if (defined $raw_type && $raw_type =~ /^([12])$/) { + $type = $1; + } else { + debug(__LINE__, "Invalid system_info type '${\($raw_type // 'undef')}', defaulting to 1"); + $type = 1; + } + + debug(__LINE__, "Collecting system information via dmidecode -t $type"); + + return _get_system_info($type); +} + +# ============================================================================ +# Internal — run dmidecode and parse output +# ============================================================================ + +sub _get_system_info { + my ($type) = @_; + + my $output = `/usr/sbin/dmidecode -t $type 2>/dev/null`; + + unless (defined $output && length($output) > 0) { + debug(__LINE__, "No output from dmidecode -t $type"); + return {}; + } + + my %fields; + my @field_order; + + for my $line (split /\n/, $output) { + if ($line =~ /^\s+(Manufacturer|Product Name|Serial Number):\s*(.+)$/) { + my ($key, $value) = ($1, $2); + $value =~ s/^\s+|\s+$//g; + + my $field_key = lc($key); + $field_key =~ s/ /_/g; + + unless (exists $fields{$field_key}) { + push @field_order, $field_key; + $fields{$field_key} = $value; + } + } + } + + unless (%fields) { + debug(__LINE__, "No recognised fields found in dmidecode output"); + return {}; + } + + # Build display string: "Manufacturer: X | Product Name: Y | Serial Number: Z" + my %pretty_key = ( + manufacturer => 'Manufacturer', + product_name => 'Product Name', + serial_number => 'Serial Number', + ); + + my @parts; + for my $key (@field_order) { + my $label = $pretty_key{$key} // $key; + push @parts, "$label: $fields{$key}"; + } + $fields{display_string} = join(' | ', @parts); + + debug(__LINE__, "System information: $fields{display_string}"); + + return \%fields; +} + +1; diff --git a/PveSensorsV2/PVEMod_Config.pm b/PveSensorsV2/PVEMod_Config.pm index 664cb89..dbc215b 100644 --- a/PveSensorsV2/PVEMod_Config.pm +++ b/PveSensorsV2/PVEMod_Config.pm @@ -60,6 +60,10 @@ our %config = ( enabled => 1, device_name => 'ups@192.168.3.2', }, + system_info => { + enabled => 1, + type => 1, # 1 = System (dmidecode -t 1), 2 = Baseboard/Motherboard (dmidecode -t 2) + }, paths => { working_dir => '/run/pveproxy/pve-mod', }, diff --git a/PveSensorsV2/PveMod_SensorInfo.pm b/PveSensorsV2/PveMod_SensorInfo.pm index 3c99107..d0b71fc 100644 --- a/PveSensorsV2/PveMod_SensorInfo.pm +++ b/PveSensorsV2/PveMod_SensorInfo.pm @@ -6,11 +6,13 @@ use warnings; use PVE::PVEMod::Config qw(%config $VERSION $stats_dir $sensors_state_file $ups_state_file); use PVE::PVEMod::Utils qw(debug safe_read_json); use PVE::PVEMod::ProcessManager qw(pve_mod_starter notify_pve_mod_worker); +use PVE::PVEMod::Collector::SystemInformation qw(get_system_information_data); # Per-endpoint state caches (module-level, reset on worker restart) -my $graphics_cache = { data => {}, mtime => 0 }; -my $sensors_cache = { data => '{}', mtime => 0 }; -my $ups_cache = { data => '{}', mtime => 0 }; +my $graphics_cache = { data => {}, mtime => 0 }; +my $sensors_cache = { data => '{}', mtime => 0 }; +my $ups_cache = { data => '{}', mtime => 0 }; +my $system_info_cache = undef; # ============================================================================ @@ -197,5 +199,17 @@ sub get_pve_mod_version { return $VERSION; } +sub get_system_information { + debug(__LINE__, "get_system_information called"); + + if (defined $system_info_cache) { + debug(__LINE__, "Returning cached system information"); + return $system_info_cache; + } + + $system_info_cache = get_system_information_data(); + + return $system_info_cache; +} 1; diff --git a/PveSensorsV2/pve-mod-gui-sensors-v2.sh b/PveSensorsV2/pve-mod-gui-sensors-v2.sh index 099a1d2..3773ab6 100644 --- a/PveSensorsV2/pve-mod-gui-sensors-v2.sh +++ b/PveSensorsV2/pve-mod-gui-sensors-v2.sh @@ -526,7 +526,6 @@ function install_mod { msgb "\n=== Installing sensor info module ===" install_sensor_monitor_module insert_sensor_monitor_into_pve - insert_system_info_into_pve ## Historical GPU data ## if [[ "$ENABLE_GPU_HISTORY" == true ]]; then @@ -607,6 +606,7 @@ install_sensor_monitor_module() { "PVEMod_Collectors/LmSensors.pm:$PVEMOD_COLLECTOR_DIR/LmSensors.pm" "PVEMod_Collectors/Amd.pm:$PVEMOD_COLLECTOR_DIR/Amd.pm" "PVEMod_Collectors/Ups.pm:$PVEMOD_COLLECTOR_DIR/Ups.pm" + "PVEMod_Collectors/systemInformation.pm:$PVEMOD_COLLECTOR_DIR/SystemInformation.pm" ) for entry in "${module_files[@]}"; do local src="$PVEMOD_SOURCES_DIR/${entry%%:*}" @@ -622,6 +622,8 @@ install_sensor_monitor_module() { intel_enabled=$([[ "$ENABLE_INTEL_GPU_INFO" = true ]] && echo 1 || echo 0) nvidia_enabled=$([[ "$ENABLE_NVIDIA_GPU_INFO" = true ]] && echo 1 || echo 0) ups_enabled=$([[ "$ENABLE_UPS" = true ]] && echo 1 || echo 0) + system_info_enabled=$([[ "$ENABLE_SYSTEM_INFO" = true ]] && echo 1 || echo 0) + system_info_type="${SYSTEM_INFO_TYPE:-1}" # Determine UPS device name local ups_device="${upsConnection:-ups@localhost}" @@ -633,6 +635,11 @@ install_sensor_monitor_module() { /enabled =>/ s/=> [01],/=> $ups_enabled,/ /device_name =>/ s|=> '[^']*',|=> '$ups_device',| " "$PVEMOD_CONFIG_FILE" + # Patch system_info config block + sed -i " + /system_info/,/}/{/enabled =>/ s/=> [01],/=> $system_info_enabled,/} + /type[[:space:]]*=>/ s/=> [12],/=> $system_info_type,/ + " "$PVEMOD_CONFIG_FILE" if [[ $? -eq 0 ]]; then info "Config.pm patched successfully." @@ -653,34 +660,12 @@ insert_sensor_monitor_into_pve() { $res->{PveMod_JsonSensorInfo} = PVE::API2::PVEMod_SensorInfo::get_sensors_info();\ $res->{PveMod_graphicsInfo} = PVE::API2::PVEMod_SensorInfo::get_pve_mod_version();\ $res->{PveMod_upsInfo} = PVE::API2::PVEMod_SensorInfo::get_ups_info();\ + $res->{pveMod_sensorInfo_systemInfo} = PVE::API2::PVEMod_SensorInfo::get_system_information();\ ' "$NODES_PM_FILE" #endregion PveSensorInfoMod heredoc info "Sensor data retriever added to \"$NODES_PM_FILE\"." } -# Collect system information -insert_system_info_into_pve() { - local output_file="$1" - local systemInfoCmd - - if [[ $ENABLE_SYSTEM_INFO == false ]]; then - return - fi - - systemInfoCmd=$(dmidecode -t "${SYSTEM_INFO_TYPE}" \ - | awk -F': ' '/Manufacturer|Product Name|Serial Number/ {print $1": "$2}' \ - | awk '{$1=$1};1' \ - | sed 's/$/ |/' \ - | paste -sd " " - \ - | sed 's/ |$//') - #region system info heredoc - sed -i "/my \$dinfo = df('\/', 1);/i\\ - # Add system information to response\\ - \$res->{pveMod_sensorInfo_systemInfo} = \"$(echo "$systemInfoCmd")\";\\ -" "$NODES_PM_FILE" - #endregion system info heredoc - info "System information retriever added to \"$output_file\"." -} #endregion node info insertion #region UI Module Installation