tr200 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +529 -0
- package/WINDOWS/TR-200-MachineReport.ps1 +512 -0
- package/bin/tr200.js +128 -0
- package/machine_report.sh +683 -0
- package/package.json +47 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# TR-200 Machine Report
|
|
3
|
+
# Copyright 2026, ES Development LLC (https://emmetts.dev)
|
|
4
|
+
# Based on original work by U.S. Graphics, LLC (BSD-3-Clause)
|
|
5
|
+
#
|
|
6
|
+
# Cross-platform system information tool
|
|
7
|
+
# Supports: Linux (all major distros), macOS 10.13+, partial BSD support
|
|
8
|
+
# Requires: Bash 4.0+ (macOS users: brew install bash)
|
|
9
|
+
|
|
10
|
+
# Global variables
|
|
11
|
+
MIN_NAME_LEN=5
|
|
12
|
+
MAX_NAME_LEN=13
|
|
13
|
+
|
|
14
|
+
MIN_DATA_LEN=20
|
|
15
|
+
MAX_DATA_LEN=32
|
|
16
|
+
|
|
17
|
+
BORDERS_AND_PADDING=7
|
|
18
|
+
|
|
19
|
+
# Basic configuration, change as needed
|
|
20
|
+
report_title="SHAUGHNESSY V DEVELOPMENT INC."
|
|
21
|
+
last_login_ip_present=0
|
|
22
|
+
zfs_present=0
|
|
23
|
+
zfs_filesystem="zroot/ROOT/os"
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# CROSS-PLATFORM COMPATIBILITY FRAMEWORK
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
# Detect operating system type
|
|
30
|
+
detect_os() {
|
|
31
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
32
|
+
echo "macos"
|
|
33
|
+
elif [[ "$OSTYPE" == "linux"* ]] || [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
34
|
+
echo "linux"
|
|
35
|
+
elif [[ "$OSTYPE" == "freebsd"* ]] || [[ "$OSTYPE" == "openbsd"* ]] || [[ "$OSTYPE" == "netbsd"* ]]; then
|
|
36
|
+
echo "bsd"
|
|
37
|
+
else
|
|
38
|
+
echo "unknown"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Check if command exists
|
|
43
|
+
command_exists() {
|
|
44
|
+
command -v "$1" &> /dev/null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Check if file exists and is readable
|
|
48
|
+
file_readable() {
|
|
49
|
+
[ -f "$1" ] && [ -r "$1" ]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Validate that a value looks like an IPv4 address (basic check)
|
|
53
|
+
is_ipv4() {
|
|
54
|
+
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Set OS type
|
|
58
|
+
OS_TYPE=$(detect_os)
|
|
59
|
+
|
|
60
|
+
# Check Bash version (warn but don't exit)
|
|
61
|
+
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
|
|
62
|
+
echo "⚠ Warning: Bash 4.0+ recommended for best compatibility" >&2
|
|
63
|
+
echo " Current version: ${BASH_VERSION}" >&2
|
|
64
|
+
if [ "$OS_TYPE" = "macos" ]; then
|
|
65
|
+
echo " macOS users: Install with 'brew install bash'" >&2
|
|
66
|
+
fi
|
|
67
|
+
echo "" >&2
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ============================================================================
|
|
71
|
+
# UTILITIES
|
|
72
|
+
# ============================================================================
|
|
73
|
+
max_length() {
|
|
74
|
+
local max_len=0
|
|
75
|
+
local len
|
|
76
|
+
|
|
77
|
+
for str in "$@"; do
|
|
78
|
+
len=${#str}
|
|
79
|
+
if (( len > max_len )); then
|
|
80
|
+
max_len=$len
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
if [ $max_len -lt $MAX_DATA_LEN ]; then
|
|
85
|
+
printf '%s' "$max_len"
|
|
86
|
+
else
|
|
87
|
+
printf '%s' "$MAX_DATA_LEN"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# All data strings must go here
|
|
92
|
+
set_current_len() {
|
|
93
|
+
CURRENT_LEN=$(max_length \
|
|
94
|
+
"$report_title" \
|
|
95
|
+
"$os_name" \
|
|
96
|
+
"$os_kernel" \
|
|
97
|
+
"$net_hostname" \
|
|
98
|
+
"$net_machine_ip" \
|
|
99
|
+
"$net_client_ip" \
|
|
100
|
+
"$net_current_user" \
|
|
101
|
+
"$cpu_model" \
|
|
102
|
+
"$cpu_cores_per_socket vCPU(s) / $cpu_sockets Socket(s)" \
|
|
103
|
+
"$cpu_hypervisor" \
|
|
104
|
+
"$cpu_freq GHz" \
|
|
105
|
+
"$cpu_1min_bar_graph" \
|
|
106
|
+
"$cpu_5min_bar_graph" \
|
|
107
|
+
"$cpu_15min_bar_graph" \
|
|
108
|
+
"$zfs_used_gb/$zfs_available_gb GB [$disk_percent%]" \
|
|
109
|
+
"$disk_bar_graph" \
|
|
110
|
+
"$zfs_health" \
|
|
111
|
+
"$root_used_gb/$root_total_gb GB [$disk_percent%]" \
|
|
112
|
+
"${mem_used_gb}/${mem_total_gb} GiB [${mem_percent}%]" \
|
|
113
|
+
"${mem_bar_graph}" \
|
|
114
|
+
"$last_login_time" \
|
|
115
|
+
"$last_login_ip" \
|
|
116
|
+
"$last_login_ip" \
|
|
117
|
+
"$sys_uptime" \
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
PRINT_HEADER() {
|
|
122
|
+
local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))
|
|
123
|
+
|
|
124
|
+
local top="┌"
|
|
125
|
+
local bottom="├"
|
|
126
|
+
for (( i = 0; i < length - 2; i++ )); do
|
|
127
|
+
top+="┬"
|
|
128
|
+
bottom+="┴"
|
|
129
|
+
done
|
|
130
|
+
top+="┐"
|
|
131
|
+
bottom+="┤"
|
|
132
|
+
|
|
133
|
+
printf '%s\n' "$top"
|
|
134
|
+
printf '%s\n' "$bottom"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
PRINT_CENTERED_DATA() {
|
|
138
|
+
local max_len=$((CURRENT_LEN+MAX_NAME_LEN-BORDERS_AND_PADDING))
|
|
139
|
+
local text="$1"
|
|
140
|
+
local total_width=$((max_len + 12))
|
|
141
|
+
|
|
142
|
+
local text_len=${#text}
|
|
143
|
+
local padding_left=$(( (total_width - text_len) / 2 ))
|
|
144
|
+
local padding_right=$(( total_width - text_len - padding_left ))
|
|
145
|
+
|
|
146
|
+
printf "│%${padding_left}s%s%${padding_right}s│\n" "" "$text" ""
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
PRINT_DIVIDER() {
|
|
150
|
+
# either "top" or "bottom", no argument means middle divider
|
|
151
|
+
local side="$1"
|
|
152
|
+
case "$side" in
|
|
153
|
+
"top")
|
|
154
|
+
local left_symbol="├"
|
|
155
|
+
local middle_symbol="┬"
|
|
156
|
+
local right_symbol="┤"
|
|
157
|
+
;;
|
|
158
|
+
"bottom")
|
|
159
|
+
local left_symbol="└"
|
|
160
|
+
local middle_symbol="┴"
|
|
161
|
+
local right_symbol="┘"
|
|
162
|
+
;;
|
|
163
|
+
*)
|
|
164
|
+
local left_symbol="├"
|
|
165
|
+
local middle_symbol="┼"
|
|
166
|
+
local right_symbol="┤"
|
|
167
|
+
esac
|
|
168
|
+
|
|
169
|
+
local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))
|
|
170
|
+
local divider="$left_symbol"
|
|
171
|
+
for (( i = 0; i < length - 3; i++ )); do
|
|
172
|
+
divider+="─"
|
|
173
|
+
if [ "$i" -eq 14 ]; then
|
|
174
|
+
divider+="$middle_symbol"
|
|
175
|
+
fi
|
|
176
|
+
done
|
|
177
|
+
divider+="$right_symbol"
|
|
178
|
+
printf '%s\n' "$divider"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
PRINT_DATA() {
|
|
182
|
+
local name="$1"
|
|
183
|
+
local data="$2"
|
|
184
|
+
local max_data_len=$CURRENT_LEN
|
|
185
|
+
|
|
186
|
+
# Pad name
|
|
187
|
+
local name_len=${#name}
|
|
188
|
+
if (( name_len < MIN_NAME_LEN )); then
|
|
189
|
+
name=$(printf "%-${MIN_NAME_LEN}s" "$name")
|
|
190
|
+
elif (( name_len > MAX_NAME_LEN )); then
|
|
191
|
+
name=$(echo "$name" | cut -c 1-$((MAX_NAME_LEN-3)))...
|
|
192
|
+
else
|
|
193
|
+
name=$(printf "%-${MAX_NAME_LEN}s" "$name")
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Truncate or pad data
|
|
197
|
+
local data_len=${#data}
|
|
198
|
+
if (( data_len >= MAX_DATA_LEN || data_len == MAX_DATA_LEN-1 )); then
|
|
199
|
+
data=$(echo "$data" | cut -c 1-$((MAX_DATA_LEN-3-2)))...
|
|
200
|
+
else
|
|
201
|
+
data=$(printf "%-${max_data_len}s" "$data")
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
printf "│ %-${MAX_NAME_LEN}s │ %s │\n" "$name" "$data"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
PRINT_FOOTER() {
|
|
208
|
+
local length=$((CURRENT_LEN+MAX_NAME_LEN+BORDERS_AND_PADDING))
|
|
209
|
+
local footer="└"
|
|
210
|
+
for (( i = 0; i < length - 3; i++ )); do
|
|
211
|
+
footer+="─"
|
|
212
|
+
if [ "$i" -eq 14 ]; then
|
|
213
|
+
footer+="┴"
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
216
|
+
footer+="┘"
|
|
217
|
+
printf '%s\n' "$footer"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
bar_graph() {
|
|
221
|
+
local percent
|
|
222
|
+
local num_blocks
|
|
223
|
+
local width=$CURRENT_LEN
|
|
224
|
+
local graph=""
|
|
225
|
+
local used=$1
|
|
226
|
+
local total=$2
|
|
227
|
+
|
|
228
|
+
if (( total == 0 )); then
|
|
229
|
+
percent=0
|
|
230
|
+
else
|
|
231
|
+
percent=$(awk -v used="$used" -v total="$total" 'BEGIN { printf "%.2f", (used / total) * 100 }')
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
num_blocks=$(awk -v percent="$percent" -v width="$width" 'BEGIN { printf "%d", (percent / 100) * width }')
|
|
235
|
+
|
|
236
|
+
for (( i = 0; i < num_blocks; i++ )); do
|
|
237
|
+
graph+="█"
|
|
238
|
+
done
|
|
239
|
+
for (( i = num_blocks; i < width; i++ )); do
|
|
240
|
+
graph+="░"
|
|
241
|
+
done
|
|
242
|
+
printf "%s" "${graph}"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
get_ip_addr() {
|
|
246
|
+
# Initialize variables
|
|
247
|
+
ipv4_address=""
|
|
248
|
+
ipv6_address=""
|
|
249
|
+
|
|
250
|
+
# Check if ifconfig command exists
|
|
251
|
+
if command -v ifconfig &> /dev/null; then
|
|
252
|
+
# Try to get IPv4 address using ifconfig
|
|
253
|
+
ipv4_address=$(ifconfig | awk '
|
|
254
|
+
/^[a-z]/ {iface=$1}
|
|
255
|
+
iface != "lo:" && iface !~ /^docker/ && /inet / && !found_ipv4 {found_ipv4=1; print $2}')
|
|
256
|
+
|
|
257
|
+
# If IPv4 address not available, try IPv6 using ifconfig
|
|
258
|
+
if [ -z "$ipv4_address" ]; then
|
|
259
|
+
ipv6_address=$(ifconfig | awk '
|
|
260
|
+
/^[a-z]/ {iface=$1}
|
|
261
|
+
iface != "lo:" && iface !~ /^docker/ && /inet6 / && !found_ipv6 {found_ipv6=1; print $2}')
|
|
262
|
+
fi
|
|
263
|
+
elif command -v ip &> /dev/null; then
|
|
264
|
+
# Try to get IPv4 address using ip addr
|
|
265
|
+
ipv4_address=$(ip -o -4 addr show | awk '
|
|
266
|
+
$2 != "lo" && $2 !~ /^docker/ {split($4, a, "/"); if (!found_ipv4++) print a[1]}')
|
|
267
|
+
|
|
268
|
+
# If IPv4 address not available, try IPv6 using ip addr
|
|
269
|
+
if [ -z "$ipv4_address" ]; then
|
|
270
|
+
ipv6_address=$(ip -o -6 addr show | awk '
|
|
271
|
+
$2 != "lo" && $2 !~ /^docker/ {split($4, a, "/"); if (!found_ipv6++) print a[1]}')
|
|
272
|
+
fi
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# If neither IPv4 nor IPv6 address is available, assign "No IP found"
|
|
276
|
+
if [ -z "$ipv4_address" ] && [ -z "$ipv6_address" ]; then
|
|
277
|
+
ip_address="No IP found"
|
|
278
|
+
else
|
|
279
|
+
# Prioritize IPv4 if available, otherwise use IPv6
|
|
280
|
+
ip_address="${ipv4_address:-$ipv6_address}"
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
printf '%s' "$ip_address"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# ============================================================================
|
|
287
|
+
# OPERATING SYSTEM INFORMATION
|
|
288
|
+
# ============================================================================
|
|
289
|
+
|
|
290
|
+
if [ "$OS_TYPE" = "macos" ]; then
|
|
291
|
+
# macOS detection using sw_vers
|
|
292
|
+
if command_exists sw_vers; then
|
|
293
|
+
os_name="macOS $(sw_vers -productVersion 2>/dev/null || echo 'Unknown')"
|
|
294
|
+
else
|
|
295
|
+
os_name="macOS (Unknown Version)"
|
|
296
|
+
fi
|
|
297
|
+
elif file_readable /etc/os-release; then
|
|
298
|
+
# Linux detection using os-release
|
|
299
|
+
source /etc/os-release
|
|
300
|
+
if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
|
|
301
|
+
# Bash 4+: Use capitalize
|
|
302
|
+
os_name="${ID^} ${VERSION} ${VERSION_CODENAME^}"
|
|
303
|
+
else
|
|
304
|
+
# Bash 3: Plain format
|
|
305
|
+
os_name="${ID} ${VERSION} ${VERSION_CODENAME}"
|
|
306
|
+
fi
|
|
307
|
+
else
|
|
308
|
+
# Fallback for systems without os-release
|
|
309
|
+
os_name="$(uname -s) (Unknown Version)"
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
# Kernel information - portable format
|
|
313
|
+
os_kernel="$(uname -s) $(uname -r)"
|
|
314
|
+
|
|
315
|
+
# ============================================================================
|
|
316
|
+
# NETWORK INFORMATION
|
|
317
|
+
# ============================================================================
|
|
318
|
+
|
|
319
|
+
net_current_user=$(whoami)
|
|
320
|
+
|
|
321
|
+
# Hostname detection with fallbacks
|
|
322
|
+
if command_exists hostname; then
|
|
323
|
+
# Try hostname -f first (FQDN), fallback to plain hostname
|
|
324
|
+
if hostname -f &> /dev/null 2>&1; then
|
|
325
|
+
net_hostname=$(hostname -f 2>/dev/null)
|
|
326
|
+
else
|
|
327
|
+
net_hostname=$(hostname 2>/dev/null)
|
|
328
|
+
fi
|
|
329
|
+
elif file_readable /etc/hosts; then
|
|
330
|
+
# Fallback: try to find hostname in /etc/hosts
|
|
331
|
+
net_hostname=$(grep -w "$(uname -n)" /etc/hosts 2>/dev/null | awk '{print $2}' | head -n 1)
|
|
332
|
+
else
|
|
333
|
+
# Last resort: use uname
|
|
334
|
+
net_hostname=$(uname -n 2>/dev/null)
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Ensure we have something
|
|
338
|
+
[ -z "$net_hostname" ] && net_hostname="Not Defined"
|
|
339
|
+
|
|
340
|
+
# Get machine IP address (uses get_ip_addr function defined earlier)
|
|
341
|
+
net_machine_ip=$(get_ip_addr)
|
|
342
|
+
|
|
343
|
+
# Get client IP (for SSH sessions)
|
|
344
|
+
net_client_ip=$(who am i 2>/dev/null | awk '{print $5}' | tr -d '()')
|
|
345
|
+
[ -z "$net_client_ip" ] && net_client_ip="Not connected"
|
|
346
|
+
|
|
347
|
+
# DNS servers detection
|
|
348
|
+
if [ "$OS_TYPE" = "macos" ] && command_exists scutil; then
|
|
349
|
+
# macOS: use scutil to get DNS servers
|
|
350
|
+
net_dns_ip=($(scutil --dns 2>/dev/null | grep 'nameserver\[[0-9]*\]' | awk '{print $3}' | head -5))
|
|
351
|
+
elif file_readable /etc/resolv.conf; then
|
|
352
|
+
# Linux/Unix: parse resolv.conf
|
|
353
|
+
net_dns_ip=($(grep '^nameserver [0-9.]' /etc/resolv.conf 2>/dev/null | awk '{print $2}'))
|
|
354
|
+
else
|
|
355
|
+
# No DNS info available
|
|
356
|
+
net_dns_ip=()
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
# ============================================================================
|
|
360
|
+
# CPU INFORMATION
|
|
361
|
+
# ============================================================================
|
|
362
|
+
|
|
363
|
+
if [ "$OS_TYPE" = "macos" ]; then
|
|
364
|
+
# macOS CPU detection using sysctl
|
|
365
|
+
cpu_model="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo 'Unknown CPU')"
|
|
366
|
+
cpu_cores="$(sysctl -n hw.ncpu 2>/dev/null || echo '0')"
|
|
367
|
+
cpu_cores_per_socket="$(sysctl -n machdep.cpu.core_count 2>/dev/null || echo '')"
|
|
368
|
+
cpu_sockets="$(sysctl -n hw.packages 2>/dev/null || sysctl -n hw.physicalcpu 2>/dev/null || echo '-')"
|
|
369
|
+
cpu_hypervisor="Bare Metal" # macOS doesn't expose hypervisor easily
|
|
370
|
+
|
|
371
|
+
# CPU frequency on macOS (may not always be available)
|
|
372
|
+
cpu_freq_hz="$(sysctl -n hw.cpufrequency_max 2>/dev/null || sysctl -n hw.cpufrequency 2>/dev/null)"
|
|
373
|
+
if [ -n "$cpu_freq_hz" ] && [ "$cpu_freq_hz" != "0" ]; then
|
|
374
|
+
cpu_freq=$(awk -v freq="$cpu_freq_hz" 'BEGIN { printf "%.2f", freq / 1000000000 }')
|
|
375
|
+
else
|
|
376
|
+
cpu_freq="" # Will show blank like on some ARM systems
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
elif command_exists lscpu; then
|
|
380
|
+
# Linux with lscpu
|
|
381
|
+
cpu_model="$(lscpu | grep -E 'Model name:|^Model:' | grep -v 'BIOS' | cut -f 2 -d ':' | awk '{$1=$1; print $1 " " $2 " " $3 " " $4}')"
|
|
382
|
+
cpu_hypervisor="$(lscpu | grep 'Hypervisor vendor' | cut -f 2 -d ':' | awk '{$1=$1}1')"
|
|
383
|
+
[ -z "$cpu_hypervisor" ] && cpu_hypervisor="Bare Metal"
|
|
384
|
+
|
|
385
|
+
cpu_cores="$(lscpu | grep -E '^CPU\(s\):' | awk '{print $2}' | head -1)"
|
|
386
|
+
cpu_cores_per_socket="$(lscpu | grep 'Core(s) per socket' | cut -f 2 -d ':' | awk '{$1=$1}1')"
|
|
387
|
+
cpu_sockets="$(lscpu | grep 'Socket(s)' | cut -f 2 -d ':' | awk '{$1=$1}1')"
|
|
388
|
+
|
|
389
|
+
# CPU frequency - try multiple sources
|
|
390
|
+
if file_readable /proc/cpuinfo; then
|
|
391
|
+
cpu_freq="$(grep 'cpu MHz' /proc/cpuinfo | cut -f 2 -d ':' | awk 'NR==1 { printf "%.2f", $1 / 1000 }')"
|
|
392
|
+
fi
|
|
393
|
+
# Fallback for ARM: try sysfs
|
|
394
|
+
if [ -z "$cpu_freq" ] && [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then
|
|
395
|
+
cpu_freq_khz="$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq 2>/dev/null)"
|
|
396
|
+
[ -n "$cpu_freq_khz" ] && cpu_freq=$(awk -v freq="$cpu_freq_khz" 'BEGIN { printf "%.2f", freq / 1000000 }')
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
else
|
|
400
|
+
# Fallback: No lscpu available
|
|
401
|
+
cpu_model="Unknown CPU"
|
|
402
|
+
cpu_hypervisor="Unknown"
|
|
403
|
+
|
|
404
|
+
# Try nproc or fallback to getconf
|
|
405
|
+
if command_exists nproc; then
|
|
406
|
+
cpu_cores="$(nproc --all 2>/dev/null || nproc 2>/dev/null)"
|
|
407
|
+
elif command_exists getconf; then
|
|
408
|
+
cpu_cores="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo '0')"
|
|
409
|
+
else
|
|
410
|
+
cpu_cores="0"
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
cpu_cores_per_socket=""
|
|
414
|
+
cpu_sockets="-"
|
|
415
|
+
cpu_freq=""
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# Normalize cpu_cores if using nproc as fallback
|
|
419
|
+
if [ -z "$cpu_cores" ] || [ "$cpu_cores" = "0" ]; then
|
|
420
|
+
if command_exists nproc; then
|
|
421
|
+
cpu_cores="$(nproc --all 2>/dev/null || echo '0')"
|
|
422
|
+
fi
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
# Load averages - prefer /proc/loadavg on Linux, use uptime elsewhere
|
|
426
|
+
if file_readable /proc/loadavg; then
|
|
427
|
+
read load_avg_1min load_avg_5min load_avg_15min rest < /proc/loadavg
|
|
428
|
+
elif [ "$OS_TYPE" = "macos" ] && command_exists sysctl; then
|
|
429
|
+
# macOS: use sysctl
|
|
430
|
+
load_avg="$(sysctl -n vm.loadavg 2>/dev/null | tr -d '{}')"
|
|
431
|
+
load_avg_1min="$(echo "$load_avg" | awk '{print $1}')"
|
|
432
|
+
load_avg_5min="$(echo "$load_avg" | awk '{print $2}')"
|
|
433
|
+
load_avg_15min="$(echo "$load_avg" | awk '{print $3}')"
|
|
434
|
+
else
|
|
435
|
+
# Fallback: parse uptime (less reliable with different locales)
|
|
436
|
+
load_avg_1min=$(uptime | awk -F'load average: ' '{print $2}' | cut -d ',' -f1 | tr -d ' ')
|
|
437
|
+
load_avg_5min=$(uptime | awk -F'load average: ' '{print $2}' | cut -d ',' -f2 | tr -d ' ')
|
|
438
|
+
load_avg_15min=$(uptime | awk -F'load average: ' '{print $2}' | cut -d ',' -f3 | tr -d ' ')
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
# ============================================================================
|
|
442
|
+
# MEMORY INFORMATION
|
|
443
|
+
# ============================================================================
|
|
444
|
+
|
|
445
|
+
if [ "$OS_TYPE" = "macos" ]; then
|
|
446
|
+
# macOS memory detection using sysctl and vm_stat
|
|
447
|
+
mem_total_bytes="$(sysctl -n hw.memsize 2>/dev/null || echo '0')"
|
|
448
|
+
mem_total=$((mem_total_bytes / 1024)) # Convert to KB to match Linux format
|
|
449
|
+
|
|
450
|
+
if command_exists vm_stat; then
|
|
451
|
+
# Parse vm_stat for memory usage
|
|
452
|
+
vm_stat_output="$(vm_stat)"
|
|
453
|
+
page_size=$(echo "$vm_stat_output" | grep 'page size' | awk '{print $8}' | tr -d '.')
|
|
454
|
+
[ -z "$page_size" ] && page_size=4096 # Default page size
|
|
455
|
+
|
|
456
|
+
pages_free=$(echo "$vm_stat_output" | grep 'Pages free' | awk '{print $3}' | tr -d '.')
|
|
457
|
+
pages_inactive=$(echo "$vm_stat_output" | grep 'Pages inactive' | awk '{print $3}' | tr -d '.')
|
|
458
|
+
pages_speculative=$(echo "$vm_stat_output" | grep 'Pages speculative' | awk '{print $3}' | tr -d '.')
|
|
459
|
+
|
|
460
|
+
# Calculate available memory in KB
|
|
461
|
+
mem_available=$((((pages_free + pages_inactive + pages_speculative) * page_size) / 1024))
|
|
462
|
+
else
|
|
463
|
+
# Rough estimate if vm_stat unavailable
|
|
464
|
+
mem_available=$((mem_total / 4))
|
|
465
|
+
fi
|
|
466
|
+
|
|
467
|
+
mem_used=$((mem_total - mem_available))
|
|
468
|
+
|
|
469
|
+
elif file_readable /proc/meminfo; then
|
|
470
|
+
# Linux memory detection
|
|
471
|
+
mem_total=$(grep 'MemTotal' /proc/meminfo | awk '{print $2}')
|
|
472
|
+
mem_available=$(grep 'MemAvailable' /proc/meminfo | awk '{print $2}')
|
|
473
|
+
mem_used=$((mem_total - mem_available))
|
|
474
|
+
|
|
475
|
+
else
|
|
476
|
+
# Fallback for unknown systems
|
|
477
|
+
mem_total=0
|
|
478
|
+
mem_available=0
|
|
479
|
+
mem_used=0
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
# Calculate percentages and convert to GB
|
|
483
|
+
if [ "$mem_total" -gt 0 ]; then
|
|
484
|
+
mem_percent=$(awk -v used="$mem_used" -v total="$mem_total" 'BEGIN { printf "%.2f", (used / total) * 100 }')
|
|
485
|
+
mem_total_gb=$(echo "$mem_total" | awk '{ printf "%.2f", $1 / (1024 * 1024) }')
|
|
486
|
+
mem_available_gb=$(echo "$mem_available" | awk '{ printf "%.2f", $1 / (1024 * 1024) }')
|
|
487
|
+
mem_used_gb=$(echo "$mem_used" | awk '{ printf "%.2f", $1 / (1024 * 1024) }')
|
|
488
|
+
else
|
|
489
|
+
mem_percent="0.00"
|
|
490
|
+
mem_total_gb="0.00"
|
|
491
|
+
mem_available_gb="0.00"
|
|
492
|
+
mem_used_gb="0.00"
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# ============================================================================
|
|
496
|
+
# DISK INFORMATION
|
|
497
|
+
# ============================================================================
|
|
498
|
+
|
|
499
|
+
# Check for ZFS (Linux only)
|
|
500
|
+
if [ "$OS_TYPE" = "linux" ] && command_exists zfs && grep -q "zfs" /proc/mounts 2>/dev/null; then
|
|
501
|
+
zfs_present=1
|
|
502
|
+
|
|
503
|
+
# ZFS health check - handle different ZFS versions
|
|
504
|
+
zfs_health_output="$(zpool status -x zroot 2>/dev/null | head -1)"
|
|
505
|
+
if [[ "$zfs_health_output" == "all pools are healthy" ]] || [[ "$zfs_health_output" == *"is healthy"* ]]; then
|
|
506
|
+
zfs_health="HEALTH O.K."
|
|
507
|
+
elif [ -z "$zfs_health_output" ]; then
|
|
508
|
+
zfs_health="Unknown"
|
|
509
|
+
else
|
|
510
|
+
zfs_health="CHECK REQUIRED"
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
# Get ZFS usage
|
|
514
|
+
zfs_available=$(zfs get -o value -Hp available "$zfs_filesystem" 2>/dev/null || echo "0")
|
|
515
|
+
zfs_used=$(zfs get -o value -Hp used "$zfs_filesystem" 2>/dev/null || echo "0")
|
|
516
|
+
zfs_available_gb=$(echo "$zfs_available" | awk '{ printf "%.2f", $1 / (1024 * 1024 * 1024) }')
|
|
517
|
+
zfs_used_gb=$(echo "$zfs_used" | awk '{ printf "%.2f", $1 / (1024 * 1024 * 1024) }')
|
|
518
|
+
|
|
519
|
+
# FIX: Correct percentage calculation - used / (used + available)
|
|
520
|
+
disk_percent=$(awk -v used="$zfs_used" -v available="$zfs_available" \
|
|
521
|
+
'BEGIN { total = used + available; if (total > 0) printf "%.2f", (used / total) * 100; else print "0.00" }')
|
|
522
|
+
else
|
|
523
|
+
# Standard filesystem detection (Linux, macOS, BSD)
|
|
524
|
+
root_partition="/"
|
|
525
|
+
|
|
526
|
+
# Try df -m first (MB), fallback to df -k (KB) if unsupported
|
|
527
|
+
if df -m "$root_partition" &> /dev/null 2>&1; then
|
|
528
|
+
root_used=$(df -m "$root_partition" 2>/dev/null | awk 'NR==2 {print $3}')
|
|
529
|
+
root_total=$(df -m "$root_partition" 2>/dev/null | awk 'NR==2 {print $2}')
|
|
530
|
+
root_total_gb=$(awk -v total="$root_total" 'BEGIN { printf "%.2f", total / 1024 }')
|
|
531
|
+
root_used_gb=$(awk -v used="$root_used" 'BEGIN { printf "%.2f", used / 1024 }')
|
|
532
|
+
else
|
|
533
|
+
# Fallback to KB and convert
|
|
534
|
+
root_used=$(df -k "$root_partition" 2>/dev/null | awk 'NR==2 {print $3}')
|
|
535
|
+
root_total=$(df -k "$root_partition" 2>/dev/null | awk 'NR==2 {print $2}')
|
|
536
|
+
root_total_gb=$(awk -v total="$root_total" 'BEGIN { printf "%.2f", total / 1024 / 1024 }')
|
|
537
|
+
root_used_gb=$(awk -v used="$root_used" 'BEGIN { printf "%.2f", used / 1024 / 1024 }')
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
# Calculate percentage with safety check
|
|
541
|
+
if [ -n "$root_total" ] && [ "$root_total" -gt 0 ]; then
|
|
542
|
+
disk_percent=$(awk -v used="$root_used" -v total="$root_total" \
|
|
543
|
+
'BEGIN { printf "%.2f", (used / total) * 100 }')
|
|
544
|
+
else
|
|
545
|
+
disk_percent="0.00"
|
|
546
|
+
root_total_gb="0.00"
|
|
547
|
+
root_used_gb="0.00"
|
|
548
|
+
fi
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
# Last login and Uptime
|
|
552
|
+
# Try lastlog2 first (modern Debian), fall back to lastlog if available
|
|
553
|
+
if command -v lastlog2 &> /dev/null; then
|
|
554
|
+
last_login=$(lastlog2 -u "$USER" 2>/dev/null)
|
|
555
|
+
last_login_ip=$(echo "$last_login" | awk 'NR==2 {print $3}')
|
|
556
|
+
|
|
557
|
+
# Check if last_login_ip is an IP address
|
|
558
|
+
if [[ "$last_login_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
559
|
+
last_login_ip_present=1
|
|
560
|
+
last_login_time=$(echo "$last_login" | awk 'NR==2 {print $4, $5, $6, $7, $8}' | sed 's/ */ /g')
|
|
561
|
+
else
|
|
562
|
+
last_login_time=$(echo "$last_login" | awk 'NR==2 {print $4, $5, $6, $7, $8}' | sed 's/ */ /g')
|
|
563
|
+
# Check for never logged in edge case
|
|
564
|
+
if [ -z "$last_login_time" ] || [ "$last_login_time" = "in**" ]; then
|
|
565
|
+
last_login_time="Never logged in"
|
|
566
|
+
fi
|
|
567
|
+
fi
|
|
568
|
+
elif command -v lastlog &> /dev/null; then
|
|
569
|
+
last_login=$(lastlog -u "$USER" 2>/dev/null)
|
|
570
|
+
last_login_ip=$(echo "$last_login" | awk 'NR==2 {print $3}')
|
|
571
|
+
|
|
572
|
+
# Check if last_login_ip is an IP address
|
|
573
|
+
if [[ "$last_login_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
574
|
+
last_login_ip_present=1
|
|
575
|
+
last_login_time=$(echo "$last_login" | awk 'NR==2 {print $6, $7, $10, $8}')
|
|
576
|
+
else
|
|
577
|
+
last_login_time=$(echo "$last_login" | awk 'NR==2 {print $4, $5, $8, $6}')
|
|
578
|
+
# Check for **Never logged in** edge case
|
|
579
|
+
if [ "$last_login_time" = "in**" ]; then
|
|
580
|
+
last_login_time="Never logged in"
|
|
581
|
+
fi
|
|
582
|
+
fi
|
|
583
|
+
else
|
|
584
|
+
# Neither command available
|
|
585
|
+
last_login_time="Login tracking unavailable"
|
|
586
|
+
last_login_ip=""
|
|
587
|
+
last_login_ip_present=0
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# System uptime - use uptime -p if available, otherwise calculate from boot time
|
|
591
|
+
if uptime -p &> /dev/null 2>&1; then
|
|
592
|
+
# Linux with uptime -p
|
|
593
|
+
sys_uptime=$(uptime -p 2>/dev/null | sed 's/up\s*//; s/\s*day\(s*\)/d/; s/\s*hour\(s*\)/h/; s/\s*minute\(s*\)/m/')
|
|
594
|
+
elif [ "$OS_TYPE" = "macos" ] && command_exists sysctl; then
|
|
595
|
+
# macOS: calculate from boot time
|
|
596
|
+
boot_time=$(sysctl -n kern.boottime 2>/dev/null | awk '{print $4}' | tr -d ',')
|
|
597
|
+
if [ -n "$boot_time" ]; then
|
|
598
|
+
current_time=$(date +%s)
|
|
599
|
+
uptime_seconds=$((current_time - boot_time))
|
|
600
|
+
uptime_days=$((uptime_seconds / 86400))
|
|
601
|
+
uptime_hours=$(( (uptime_seconds % 86400) / 3600 ))
|
|
602
|
+
uptime_mins=$(( (uptime_seconds % 3600) / 60 ))
|
|
603
|
+
|
|
604
|
+
# Format similar to Linux uptime -p
|
|
605
|
+
sys_uptime=""
|
|
606
|
+
[ "$uptime_days" -gt 0 ] && sys_uptime="${uptime_days}d "
|
|
607
|
+
[ "$uptime_hours" -gt 0 ] && sys_uptime="${sys_uptime}${uptime_hours}h "
|
|
608
|
+
[ "$uptime_mins" -gt 0 ] && sys_uptime="${sys_uptime}${uptime_mins}m"
|
|
609
|
+
sys_uptime=$(echo "$sys_uptime" | sed 's/ */ /g; s/ $//') # Clean up spacing
|
|
610
|
+
else
|
|
611
|
+
sys_uptime="Unknown"
|
|
612
|
+
fi
|
|
613
|
+
else
|
|
614
|
+
# Fallback: parse uptime command output (less reliable)
|
|
615
|
+
sys_uptime=$(uptime 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="up") {for(j=i+1;j<=NF;j++) {if($j ~ /user/) break; printf "%s ", $j}}}' | sed 's/,//g')
|
|
616
|
+
[ -z "$sys_uptime" ] && sys_uptime="Unknown"
|
|
617
|
+
fi
|
|
618
|
+
|
|
619
|
+
# Set current length before graphs get calculated
|
|
620
|
+
set_current_len
|
|
621
|
+
|
|
622
|
+
# Create graphs
|
|
623
|
+
cpu_1min_bar_graph=$(bar_graph "$load_avg_1min" "$cpu_cores")
|
|
624
|
+
cpu_5min_bar_graph=$(bar_graph "$load_avg_5min" "$cpu_cores")
|
|
625
|
+
cpu_15min_bar_graph=$(bar_graph "$load_avg_15min" "$cpu_cores")
|
|
626
|
+
|
|
627
|
+
mem_bar_graph=$(bar_graph "$mem_used" "$mem_total")
|
|
628
|
+
|
|
629
|
+
if [ $zfs_present -eq 1 ]; then
|
|
630
|
+
disk_bar_graph=$(bar_graph "$zfs_used" "$zfs_available")
|
|
631
|
+
else
|
|
632
|
+
disk_bar_graph=$(bar_graph "$root_used" "$root_total")
|
|
633
|
+
fi
|
|
634
|
+
|
|
635
|
+
# Machine Report
|
|
636
|
+
PRINT_HEADER
|
|
637
|
+
PRINT_CENTERED_DATA "$report_title"
|
|
638
|
+
PRINT_CENTERED_DATA "TR-200 MACHINE REPORT"
|
|
639
|
+
PRINT_DIVIDER "top"
|
|
640
|
+
PRINT_DATA "OS" "$os_name"
|
|
641
|
+
PRINT_DATA "KERNEL" "$os_kernel"
|
|
642
|
+
PRINT_DIVIDER
|
|
643
|
+
PRINT_DATA "HOSTNAME" "$net_hostname"
|
|
644
|
+
PRINT_DATA "MACHINE IP" "$net_machine_ip"
|
|
645
|
+
PRINT_DATA "CLIENT IP" "$net_client_ip"
|
|
646
|
+
|
|
647
|
+
for dns_num in "${!net_dns_ip[@]}"; do
|
|
648
|
+
PRINT_DATA "DNS IP $(($dns_num + 1))" "${net_dns_ip[dns_num]}"
|
|
649
|
+
done
|
|
650
|
+
|
|
651
|
+
PRINT_DATA "USER" "$net_current_user"
|
|
652
|
+
PRINT_DIVIDER
|
|
653
|
+
PRINT_DATA "PROCESSOR" "$cpu_model"
|
|
654
|
+
PRINT_DATA "CORES" "$cpu_cores_per_socket vCPU(s) / $cpu_sockets Socket(s)"
|
|
655
|
+
PRINT_DATA "HYPERVISOR" "$cpu_hypervisor"
|
|
656
|
+
PRINT_DATA "CPU FREQ" "$cpu_freq GHz"
|
|
657
|
+
PRINT_DATA "LOAD 1m" "$cpu_1min_bar_graph"
|
|
658
|
+
PRINT_DATA "LOAD 5m" "$cpu_5min_bar_graph"
|
|
659
|
+
PRINT_DATA "LOAD 15m" "$cpu_15min_bar_graph"
|
|
660
|
+
|
|
661
|
+
if [ $zfs_present -eq 1 ]; then
|
|
662
|
+
PRINT_DIVIDER
|
|
663
|
+
PRINT_DATA "VOLUME" "$zfs_used_gb/$zfs_available_gb GB [$disk_percent%]"
|
|
664
|
+
PRINT_DATA "DISK USAGE" "$disk_bar_graph"
|
|
665
|
+
PRINT_DATA "ZFS HEALTH" "$zfs_health"
|
|
666
|
+
else
|
|
667
|
+
PRINT_DIVIDER
|
|
668
|
+
PRINT_DATA "VOLUME" "$root_used_gb/$root_total_gb GB [$disk_percent%]"
|
|
669
|
+
PRINT_DATA "DISK USAGE" "$disk_bar_graph"
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
PRINT_DIVIDER
|
|
673
|
+
PRINT_DATA "MEMORY" "${mem_used_gb}/${mem_total_gb} GiB [${mem_percent}%]"
|
|
674
|
+
PRINT_DATA "USAGE" "${mem_bar_graph}"
|
|
675
|
+
PRINT_DIVIDER
|
|
676
|
+
PRINT_DATA "LAST LOGIN" "$last_login_time"
|
|
677
|
+
|
|
678
|
+
if [ $last_login_ip_present -eq 1 ]; then
|
|
679
|
+
PRINT_DATA "" "$last_login_ip"
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
PRINT_DATA "UPTIME" "$sys_uptime"
|
|
683
|
+
PRINT_DIVIDER "bottom"
|