Last active 8 hours ago

albert's Avatar albert revised this gist 2 months ago. Go to revision

No changes

albert's Avatar albert revised this gist 2 months ago. Go to revision

No changes

albert's Avatar albert revised this gist 2 months ago. Go to revision

No changes

albert's Avatar albert revised this gist 2 months ago. Go to revision

1 file changed, 316 insertions

diskreport.sh(file created)

@@ -0,0 +1,316 @@
1 + #!/bin/bash
2 +
3 + if [ "$EUID" -ne 0 ]; then
4 + echo "Error: This script must be run as root (e.g., sudo $0)" >&2
5 + exit 1
6 + fi
7 +
8 + if ! command -v smartctl >/dev/null 2>&1; then
9 + echo "Error: smartctl is not installed." >&2
10 + exit 1
11 + fi
12 +
13 + OUTPUT="${1:-diskreport.html}"
14 +
15 + escape_html() {
16 + sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g'
17 + }
18 +
19 + cat << EOF > "$OUTPUT"
20 + <!DOCTYPE html>
21 + <html lang="en">
22 + <head>
23 + <meta charset="UTF-8">
24 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
25 + <title>DiskReport</title>
26 + <style>
27 + :root {
28 + --bg-color: #0f0f0f;
29 + --box-bg: #1a1a1a;
30 + --raw-bg: #0a0a0a;
31 + --text-color: #d3d7cf;
32 + --pass-color: #8ae234;
33 + --fail-color: #ef2929;
34 + --warn-color: #fce94f;
35 + --link-color: #729fcf;
36 + --border-color: #333333;
37 + --muted-color: #888a85;
38 + }
39 +
40 + * {
41 + scrollbar-width: thin;
42 + scrollbar-color: var(--border-color) var(--bg-color);
43 + }
44 +
45 + ::-webkit-scrollbar {
46 + width: 10px;
47 + height: 10px;
48 + }
49 + ::-webkit-scrollbar-track {
50 + background: var(--bg-color);
51 + }
52 + ::-webkit-scrollbar-thumb {
53 + background: var(--border-color);
54 + border-radius: 5px;
55 + }
56 + ::-webkit-scrollbar-thumb:hover {
57 + background: var(--muted-color);
58 + }
59 +
60 + html, body {
61 + min-height: 100vh;
62 + margin: 0;
63 + padding: 0;
64 + background-color: var(--bg-color);
65 + color: var(--text-color);
66 + font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "Courier New", monospace;
67 + font-size: 16px;
68 + line-height: 1.5;
69 + }
70 + body {
71 + display: flex;
72 + justify-content: center;
73 + align-items: flex-start;
74 + padding: 2rem;
75 + box-sizing: border-box;
76 + }
77 + .container {
78 + margin: auto;
79 + width: 100%;
80 + max-width: 800px;
81 + }
82 + .header-box {
83 + border: 2px solid var(--pass-color);
84 + text-align: center;
85 + padding: 1.5rem;
86 + border-radius: 8px;
87 + margin-bottom: 2rem;
88 + background: var(--box-bg);
89 + box-shadow: 0 4px 6px rgba(0,0,0,0.3);
90 + }
91 + .header-title {
92 + color: var(--pass-color);
93 + margin: 0 0 0.5rem 0;
94 + font-size: 2rem;
95 + letter-spacing: 2px;
96 + text-transform: uppercase;
97 + }
98 + .header-date {
99 + color: var(--muted-color);
100 + font-size: 0.9rem;
101 + }
102 + .drive-box {
103 + margin-bottom: 2rem;
104 + background: var(--box-bg);
105 + padding: 1.5rem;
106 + border-radius: 8px;
107 + border: 1px solid var(--border-color);
108 + box-shadow: 0 4px 6px rgba(0,0,0,0.3);
109 + }
110 + .pass { color: var(--pass-color); font-weight: bold; }
111 + .fail { color: var(--fail-color); font-weight: bold; }
112 + .warn { color: var(--warn-color); font-weight: bold; }
113 + pre {
114 + margin: 0;
115 + white-space: pre-wrap;
116 + word-wrap: break-word;
117 + }
118 + summary {
119 + cursor: pointer;
120 + color: var(--link-color);
121 + margin-top: 1.5rem;
122 + outline: none;
123 + font-weight: bold;
124 + user-select: none;
125 + transition: color 0.2s ease;
126 + }
127 + summary:hover { color: #ffffff; }
128 + .raw-data {
129 + color: var(--muted-color);
130 + font-size: 14px;
131 + margin-top: 1rem;
132 + padding: 1rem;
133 + background: var(--raw-bg);
134 + border-left: 3px solid var(--border-color);
135 + overflow-x: auto;
136 + white-space: pre;
137 + }
138 +
139 + .raw-data::-webkit-scrollbar-track {
140 + background: var(--raw-bg);
141 + }
142 + </style>
143 + </head>
144 + <body>
145 + <div class="container">
146 +
147 + <div class="header-box">
148 + <h1 class="header-title">DiskReport</h1>
149 + <div class="header-date">GENERATED: $(date)</div>
150 + </div>
151 + EOF
152 +
153 + for disk in $(lsblk -nd -o NAME,TYPE | awk '$2=="disk" {print $1}'); do
154 + dev="/dev/$disk"
155 +
156 + if smartctl -i "$dev" >/dev/null 2>&1; then
157 + raw_output=$(smartctl -a "$dev")
158 +
159 + model=$(echo "$raw_output" | grep -iE "^Device Model:|^Product:|^Model Number:|^Vendor:" | head -n 1 | awk -F: '{print $2}' | xargs)
160 + if [ -z "$model" ]; then
161 + model=$(lsblk -n -d -o MODEL "$dev" | xargs)
162 + fi
163 +
164 + interface=$(lsblk -n -d -o TRAN "$dev" | tr 'a-z' 'A-Z' | xargs)
165 + if [ -z "$interface" ] || [ "$interface" = "UNKNOWN" ]; then
166 + if echo "$raw_output" | grep -qi "Transport protocol:"; then
167 + interface=$(echo "$raw_output" | grep -i "Transport protocol:" | awk -F: '{print $2}' | xargs | cut -d' ' -f1)
168 + elif echo "$raw_output" | grep -qi "Protocol:.*NVM Express"; then
169 + interface="NVMe"
170 + elif echo "$raw_output" | grep -qi "SATA Version is:"; then
171 + interface=$(echo "$raw_output" | grep -i "SATA Version is:" | awk -F: '{print $2}' | xargs | cut -d, -f1)
172 + elif echo "$raw_output" | grep -qi "SCSI"; then
173 + interface="SCSI"
174 + else
175 + interface="Unknown"
176 + fi
177 + fi
178 +
179 + health=$(echo "$raw_output" | grep -iE "test result:|Health Status:" | awk -F: '{print $2}' | xargs)
180 + if [[ "$health" == *"PASSED"* || "$health" == *"OK"* ]]; then
181 + health_fmt="<span class='pass'>OK</span>"
182 + else
183 + health_fmt="<span class='fail'>${health:-FAIL/UNKNOWN}</span>"
184 + fi
185 +
186 + health_pct=""
187 + pct_used=$(echo "$raw_output" | grep -i "^Percentage Used:" | awk '{print $3}' | tr -d '%')
188 + if [ -z "$pct_used" ]; then
189 + pct_used=$(echo "$raw_output" | grep -i "Percentage used endurance indicator:" | awk '{print $5}' | tr -d '%')
190 + fi
191 +
192 + if [ -n "$pct_used" ] && [[ "$pct_used" =~ ^[0-9]+$ ]]; then
193 + health_pct=$((100 - pct_used))
194 + else
195 + wearout=$(echo "$raw_output" | grep -iE "^[ ]*(231|233|202|177|169)[ ]" | head -n 1 | awk '{print $4}')
196 + if [ -n "$wearout" ] && [[ "$wearout" =~ ^[0-9]+$ ]]; then
197 + health_pct=$((10#$wearout))
198 + fi
199 + fi
200 +
201 + if [ -n "$health_pct" ]; then
202 + if [ "$health_pct" -ge 80 ]; then health_str="<span class='pass'>${health_pct}% (Excellent)</span>"
203 + elif [ "$health_pct" -ge 50 ]; then health_str="<span class='warn'>${health_pct}% (Good)</span>"
204 + elif [ "$health_pct" -ge 20 ]; then health_str="<span class='warn'>${health_pct}% (Fair)</span>"
205 + else health_str="<span class='fail'>${health_pct}% (Poor)</span>"
206 + fi
207 + else
208 + if [[ "$health" == *"PASSED"* || "$health" == *"OK"* ]]; then
209 + health_str="<span class='pass'>100% (Excellent)</span>"
210 + else
211 + health_str="<span class='fail'>0% (Failing)</span>"
212 + fi
213 + fi
214 +
215 + perf_pct=100
216 + perf_reason=""
217 +
218 + crc_err=$(echo "$raw_output" | grep -iE "^[ ]*199[ ]+UDMA_CRC_Error_Count" | awk '{print $10}')
219 + if [[ -n "$crc_err" && "$crc_err" -gt 0 ]]; then
220 + perf_pct=80
221 + perf_reason=" (Degraded - Cable/Interface Errors)"
222 + fi
223 +
224 + nvme_err=$(echo "$raw_output" | grep -i "^Media and Data Integrity Errors:" | awk '{print $6}')
225 + if [[ -n "$nvme_err" && "$nvme_err" -gt 0 ]]; then
226 + perf_pct=70
227 + perf_reason=" (Degraded - Integrity Errors)"
228 + fi
229 +
230 + scsi_defects=$(echo "$raw_output" | grep -i "Elements in grown defect list:" | awk -F: '{print $2}' | xargs)
231 + if [[ -n "$scsi_defects" && "$scsi_defects" -gt 0 ]]; then
232 + perf_pct=70
233 + perf_reason=" (Degraded - Grown Defects)"
234 + fi
235 +
236 + realloc=$(echo "$raw_output" | grep -iE "^[ ]*5[ ]+Reallocated_Sector_Ct" | awk '{print $10}')
237 + if [[ -n "$realloc" && "$realloc" -gt 0 ]]; then
238 + if [ "$perf_pct" -gt 80 ]; then perf_pct=80; fi
239 + perf_reason=" (Degraded - Reallocated Sectors)"
240 + fi
241 +
242 + if [ "$perf_pct" -eq 100 ]; then
243 + perf_str="<span class='pass'>100% (Excellent)</span>"
244 + elif [ "$perf_pct" -ge 80 ]; then
245 + perf_str="<span class='warn'>${perf_pct}%${perf_reason}</span>"
246 + else
247 + perf_str="<span class='fail'>${perf_pct}%${perf_reason}</span>"
248 + fi
249 +
250 + temp=$(echo "$raw_output" | grep -iE "^Temperature:|^Current Drive Temperature:" | head -n 1 | awk -F: '{print $2}' | xargs)
251 +
252 + pwr_raw=$(echo "$raw_output" | grep -iE "Power On Hours|Accumulated power on time|number of hours powered up")
253 + if echo "$pwr_raw" | grep -q "minutes"; then
254 + pwr=$(echo "$pwr_raw" | awk -F'hours:minutes ' '{print $2}' | cut -d: -f1)
255 + pwr="${pwr} Hours"
256 + elif echo "$pwr_raw" | grep -qi "number of hours powered up"; then
257 + pwr=$(echo "$pwr_raw" | awk -F= '{print $2}' | awk '{print $1}')
258 + pwr="${pwr} Hours"
259 + elif [ -n "$pwr_raw" ]; then
260 + pwr=$(echo "$pwr_raw" | awk -F: '{print $2}' | xargs | sed 's/,//g')
261 + pwr="${pwr} Hours"
262 + else
263 + pwr="N/A"
264 + fi
265 +
266 + written="N/A"
267 + if echo "$raw_output" | grep -q "Data Units Written:"; then
268 + written=$(echo "$raw_output" | grep "Data Units Written:" | awk -F'[][]' '{print $2}')
269 + elif echo "$raw_output" | grep -qi "gigabytes processed \[consume\]:"; then
270 + gb_written=$(echo "$raw_output" | grep -i "gigabytes processed \[consume\]:" | awk '{print $4}')
271 + written=$(awk -v gb="$gb_written" 'BEGIN { printf "%.2f TB", gb/1000 }')
272 + elif echo "$raw_output" | grep -qiE "^[ ]*241[ ]+Total_LBAs_Written"; then
273 + lbas=$(echo "$raw_output" | grep -iE "^[ ]*241[ ]+Total_LBAs_Written" | awk '{print $10}')
274 + written=$(awk -v lbas="$lbas" 'BEGIN { printf "%.2f TB", (lbas * 512) / 1000000000000 }')
275 + fi
276 +
277 + safe_dev=$(echo "$dev" | escape_html)
278 + safe_model=$(echo "${model:-Unknown}" | escape_html)
279 + safe_interface=$(echo "${interface:-Unknown}" | escape_html)
280 + safe_temp=$(echo "${temp:-N/A}" | escape_html)
281 + safe_pwr=$(echo "${pwr:-N/A}" | escape_html)
282 + safe_written=$(echo "${written:-N/A}" | escape_html)
283 + safe_raw=$(echo "$raw_output" | grep -v "Copyright" | grep -v "smartmontools.org" | escape_html)
284 +
285 + cat << EOF >> "$OUTPUT"
286 + <div class='drive-box'>
287 + <pre>
288 + ==========================================================
289 + DRIVE : $safe_dev
290 + MODEL : $safe_model
291 + INTERFACE : $safe_interface
292 + ----------------------------------------------------------
293 + HEALTH : $health_str
294 + PERFORMANCE : $perf_str
295 + STATUS : $health_fmt
296 + TEMP : $safe_temp
297 + POWER ON : $safe_pwr
298 + WRITTEN : $safe_written
299 + ==========================================================
300 + </pre>
301 + <details>
302 + <summary>raw</summary>
303 + <pre class='raw-data'>$safe_raw</pre>
304 + </details>
305 + </div>
306 + EOF
307 + fi
308 + done
309 +
310 + cat << 'EOF' >> "$OUTPUT"
311 + </div>
312 + </body>
313 + </html>
314 + EOF
315 +
316 + echo "DiskReport generated: $(pwd)/$OUTPUT"
Newer Older