1
0

raid.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. #!/bin/bash
  2. #########################################################################
  3. # Script for check HDD & RAID state #
  4. # Created by Pavol Macko - DoIT #
  5. # ver 0.7 #
  6. #########################################################################
  7. #---------------------------- VARIABLES --------------------------------#
  8. REP=/tmp/hdd.rep
  9. WEB=/var/www/hdd
  10. TMP=/tmp/hdd.tmp
  11. LOG=/tmp/hdd.log
  12. STATE=/tmp/hdd.state
  13. MAIL_TO="info@doit.sk"
  14. EMAIL_ALERT=0
  15. EMAIL_BODY=""
  16. mkdir -p $WEB
  17. rm -f $TMP $LOG $REP
  18. #----------------------------- WEB-START -------------------------------#
  19. webstart(){
  20. cat > $WEB/hdd.html <<\EOF
  21. <!DOCTYPE html>
  22. <html lang="en">
  23. <head>
  24. <meta charset="UTF-8">
  25. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  26. <title>SITE - Storage</title>
  27. <link rel="icon" href="logo.ico">
  28. <style>
  29. :root {
  30. --bg-main: #0f172a;
  31. --card-bg: #1e293b;
  32. --border-color: #334155;
  33. --text-muted: #94a3b8;
  34. --accent-blue: #38bdf8;
  35. }
  36. body {
  37. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  38. background-color: var(--bg-main);
  39. color: #f8fafc;
  40. margin: 0;
  41. padding: 40px 20px;
  42. line-height: 1.5;
  43. }
  44. .container {
  45. max-width: 1150px;
  46. margin: 0 auto;
  47. background: var(--card-bg);
  48. border-radius: 12px;
  49. box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);
  50. padding: 32px;
  51. border: 1px solid var(--border-color);
  52. }
  53. h2 {
  54. text-align: center;
  55. color: #f8fafc;
  56. margin: 0 0 6px 0;
  57. font-weight: 700;
  58. font-size: 1.6em;
  59. letter-spacing: -0.02em;
  60. }
  61. .subtitle {
  62. text-align: center;
  63. color: var(--text-muted);
  64. font-size: 0.88em;
  65. margin-bottom: 28px;
  66. }
  67. .cards-grid {
  68. display: grid;
  69. grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  70. gap: 16px;
  71. margin-bottom: 30px;
  72. }
  73. .card {
  74. background: #0f172a;
  75. border: 1px solid var(--border-color);
  76. border-radius: 8px;
  77. padding: 16px;
  78. transition: transform 0.2s ease, border-color 0.2s ease;
  79. }
  80. .card:hover {
  81. border-color: #475569;
  82. transform: translateY(-2px);
  83. }
  84. .card-title {
  85. font-size: 0.72em;
  86. text-transform: uppercase;
  87. letter-spacing: 0.08em;
  88. color: var(--text-muted);
  89. font-weight: 700;
  90. margin-bottom: 8px;
  91. }
  92. .card-content {
  93. font-size: 0.98em;
  94. font-weight: 600;
  95. color: var(--accent-blue);
  96. }
  97. .raid-item {
  98. display: flex;
  99. justify-content: space-between;
  100. align-items: center;
  101. font-size: 0.85em;
  102. padding: 4px 0;
  103. border-bottom: 1px dashed var(--border-color);
  104. }
  105. .raid-item:last-child { border-bottom: none; }
  106. table {
  107. width: 100%;
  108. border-collapse: separate;
  109. border-spacing: 0;
  110. border-radius: 8px;
  111. overflow: hidden;
  112. border: 1px solid var(--border-color);
  113. }
  114. th {
  115. background-color: #0f172a;
  116. color: #cbd5e1;
  117. font-size: 0.75em;
  118. text-transform: uppercase;
  119. letter-spacing: 0.08em;
  120. font-weight: 700;
  121. padding: 14px;
  122. border-bottom: 1px solid var(--border-color);
  123. text-align: center;
  124. }
  125. td {
  126. padding: 12px 14px;
  127. text-align: center;
  128. border-bottom: 1px solid var(--border-color);
  129. font-size: 0.88em;
  130. color: #e2e8f0;
  131. }
  132. tr:last-child td {
  133. border-bottom: none;
  134. }
  135. tr:nth-child(even) {
  136. background-color: rgba(255, 255, 255, 0.015);
  137. }
  138. tr:hover {
  139. background-color: rgba(51, 65, 85, 0.5);
  140. }
  141. .status-ok {
  142. background-color: rgba(16, 185, 129, 0.12);
  143. color: #34d399;
  144. border: 1px solid rgba(52, 211, 153, 0.25);
  145. padding: 3px 10px;
  146. border-radius: 20px;
  147. font-size: 0.78em;
  148. font-weight: 600;
  149. display: inline-block;
  150. }
  151. .status-fail {
  152. background-color: rgba(239, 68, 68, 0.15);
  153. color: #fca5a5;
  154. border: 1px solid rgba(252, 165, 165, 0.3);
  155. padding: 3px 10px;
  156. border-radius: 20px;
  157. font-size: 0.78em;
  158. font-weight: 600;
  159. display: inline-block;
  160. }
  161. .status-warn {
  162. background-color: rgba(245, 158, 11, 0.15);
  163. color: #fbbf24;
  164. border: 1px solid rgba(251, 191, 36, 0.3);
  165. padding: 3px 10px;
  166. border-radius: 20px;
  167. font-size: 0.78em;
  168. font-weight: 600;
  169. display: inline-block;
  170. }
  171. .type-tag {
  172. background-color: rgba(56, 189, 248, 0.08);
  173. color: #38bdf8;
  174. border: 1px solid rgba(56, 189, 248, 0.25);
  175. padding: 2px 8px;
  176. border-radius: 4px;
  177. font-size: 0.78em;
  178. font-weight: 600;
  179. display: inline-block;
  180. }
  181. </style>
  182. </head>
  183. <body>
  184. <div class="container">
  185. <h2>RAID Storage Status on [SITE]</h2>
  186. <div class="subtitle">Number of disks: <b style="color: #f8fafc;">[NUM]</b> | Last update: DATE</div>
  187. <!-- Info Cards -->
  188. <div class="cards-grid">
  189. <div class="card">
  190. <div class="card-title">HW RAID Controller</div>
  191. <div class="card-content">CTRL_INFO</div>
  192. </div>
  193. <div class="card">
  194. <div class="card-title">BBU / CacheVault</div>
  195. <div class="card-content">BBU_INFO</div>
  196. </div>
  197. <div class="card">
  198. <div class="card-title">Background Tasks</div>
  199. <div class="card-content">TASK_INFO</div>
  200. </div>
  201. <div class="card">
  202. <div class="card-title">Logical Arrays (RAID)</div>
  203. RAID_INFO
  204. </div>
  205. </div>
  206. <table>
  207. <thead>
  208. <tr>
  209. <th>Slot</th>
  210. <th>HDD-Model</th>
  211. <th>Serial</th>
  212. <th>Size</th>
  213. <th>Type</th>
  214. <th>Temp</th>
  215. <th>Media Err</th>
  216. <th>Pred Fail</th>
  217. </tr>
  218. </thead>
  219. <tbody>
  220. EOF
  221. }
  222. #--------------------------- CHECK HDD INFO ----------------------------#
  223. checkhdd(){
  224. echo -e "- \e[1;32mExtracting\e[0m HDD info from PVE hypervisor"
  225. CON=$(megacli -ShowSummary -aALL 2>/dev/null | grep "ProductName" | head -n1 | awk -F':' '{print $2}' | xargs)
  226. BBU_RAW=$(megacli -AdpBbuCmd -aALL 2>/dev/null)
  227. if echo "$BBU_RAW" | grep -qi "Get BBU Status Failed\|Adapter.*Has No BBU"; then
  228. BBU_HTML="<span style=\"color:#94a3b8; font-size:0.9em;\">N/A (No BBU)</span>"
  229. else
  230. BBU_STATE=$(echo "$BBU_RAW" | grep "Battery State" | head -n1 | awk -F':' '{print $2}' | xargs)
  231. BBU_TEMP=$(echo "$BBU_RAW" | grep "Temperature" | head -n1 | awk -F':' '{print $2}' | xargs)
  232. [ -z "$BBU_STATE" ] && BBU_STATE="Optimal"
  233. if [[ "$BBU_STATE" == "Optimal" || "$BBU_STATE" == "Operational" ]]; then
  234. BBU_HTML="<span class=\"status-ok\">$BBU_STATE</span> <span style=\"font-size:0.85em; color:#94a3b8;\">(${BBU_TEMP:-N/A})</span>"
  235. else
  236. BBU_HTML="<span class=\"status-fail\">${BBU_STATE}</span> <span style=\"font-size:0.85em; color:#94a3b8;\">(${BBU_TEMP:-N/A})</span>"
  237. EMAIL_ALERT=1
  238. EMAIL_BODY="${EMAIL_BODY}\n- BBU/CacheVault error: State=$BBU_STATE, Temp=$BBU_TEMP"
  239. fi
  240. fi
  241. REBUILD_INFO=""
  242. megacli -PDList -aALL 2>/dev/null > /tmp/pd_task.tmp
  243. csplit -s -f /tmp/pd_tblock_ /tmp/pd_task.tmp '/Device Id:/' '{*}' 2>/dev/null
  244. for block in /tmp/pd_tblock_*; do
  245. [ -f "$block" ] || continue
  246. if grep -q "Firmware state" "$block"; then
  247. F_STATE=$(grep "Firmware state" "$block" | awk -F':' '{print $2}' | xargs)
  248. if echo "$F_STATE" | grep -qi "Rebuild"; then
  249. DEV_ID=$(grep "Device Id" "$block" | awk -F':' '{print $2}' | xargs)
  250. ENC_ID=$(grep "Enclosure Device ID" "$block" | awk -F':' '{print $2}' | xargs)
  251. PROG=$(megacli -PDRbld -ShowProg -PhysDrv "[$ENC_ID:$DEV_ID]" -aALL 2>/dev/null | grep -i "Completed" | awk -F'=' '{print $2}' | xargs)
  252. REBUILD_INFO="Rebuild Dev $DEV_ID: ${PROG:-In Progress}"
  253. break
  254. fi
  255. fi
  256. rm -f "$block"
  257. done
  258. rm -f /tmp/pd_task.tmp /tmp/pd_tblock_*
  259. if [ -n "$REBUILD_INFO" ]; then
  260. TASK_HTML="<span class=\"status-warn\">$REBUILD_INFO</span>"
  261. else
  262. TASK_HTML="<span class=\"status-ok\">Idle</span>"
  263. fi
  264. echo "--------------------------------------" > /tmp/hdd.rac
  265. echo -e "\e[1;37mHW-RAID-CONTROLLER\e[0m" >> /tmp/hdd.rac
  266. echo "--------------------------------------" >> /tmp/hdd.rac
  267. echo -e "\e[1;36m${CON:-N/A}\e[0m" >> /tmp/hdd.rac
  268. echo "------ ---------- --------" > /tmp/hdd.rai
  269. echo -e "\e[1;37mTYPE SIZE STATUS\e[0m" >> /tmp/hdd.rai
  270. echo "------ ---------- --------" >> /tmp/hdd.rai
  271. RAID_HTML_ACC=""
  272. megacli -LDInfo -Lall -aALL 2>/dev/null > /tmp/ld.tmp
  273. csplit -s -f /tmp/ld_block_ /tmp/ld.tmp '/Virtual Drive:/' '{*}' 2>/dev/null
  274. for block in /tmp/ld_block_*; do
  275. [ -f "$block" ] || continue
  276. if grep -q "RAID Level" "$block"; then
  277. RAI=$(grep -E "Name|RAID Level" "$block" | grep -v "User Name" | head -n1 | awk -F':' '{print $2}' | xargs)
  278. SIZ=$(grep "Size" "$block" | grep -vE "Sector|Strip" | awk -F':' '{print $2}' | xargs | tr -d ' ')
  279. STA=$(grep "State" "$block" | awk -F':' '{print $2}' | xargs)
  280. echo -e "\e[1;36m${RAI:-LD} ${SIZ:-N/A} ${STA:-N/A}\e[0m" >> /tmp/hdd.rai
  281. if [[ "$STA" == "Optimal" ]]; then
  282. STA_HTML="<span class=\"status-ok\">$STA</span>"
  283. else
  284. STA_HTML="<span class=\"status-fail\">$STA</span>"
  285. EMAIL_ALERT=1
  286. EMAIL_BODY="${EMAIL_BODY}\n- RAID array ${RAI:-LD} bad state: $STA"
  287. fi
  288. RAID_HTML_ACC="${RAID_HTML_ACC}<div class=\"raid-item\"><span><b>${RAI:-LD}</b> (${SIZ:-N/A})</span> ${STA_HTML}</div>"
  289. fi
  290. rm -f "$block"
  291. done
  292. rm -f /tmp/ld.tmp
  293. echo "------ ---------- --------" >> /tmp/hdd.rai
  294. echo "---- -------------------- ---------------- ---------- ---------- --- ------- ----" > $REP
  295. echo -e "\e[1;37mSLOT HDD-MODEL SERIAL SIZE TYPE TEMP MEDIA-ERR FAIL\e[0m" >> $REP
  296. echo "---- -------------------- ---------------- ---------- ---------- --- ------- ----" >> $REP
  297. megacli -PDList -aALL 2>/dev/null > /tmp/hdd.tmp
  298. csplit -s -f /tmp/pd_block_ /tmp/hdd.tmp '/Device Id:/' '{*}' 2>/dev/null
  299. for block in /tmp/pd_block_*; do
  300. [ -f "$block" ] || continue
  301. SN=$(grep "Inquiry Data" "$block" | awk -F':' '{print $2}' | awk '{print $NF}')
  302. if [ -n "$SN" ]; then
  303. MODL=$(grep "Inquiry Data" "$block" | awk -F':' '{print $2}' | awk '{print $1"-"$2}')
  304. SIZE=$(grep "Raw Size" "$block" | awk -F':' '{print $2}' | awk '{print $1$2}')
  305. SLOT=$(grep "Slot Number" "$block" | awk -F':' '{print $2}' | xargs)
  306. FAIL=$(grep "Predictive Failure Count" "$block" | awk -F':' '{print $2}' | xargs)
  307. MEDIA_ERR=$(grep "Media Error Count" "$block" | awk -F':' '{print $2}' | xargs)
  308. TEMP=$(grep "Drive Temperature" "$block" | awk -F':' '{print $2}' | awk '{print $1}')
  309. M_TYPE_RAW=$(grep "Media Type" "$block" | awk -F':' '{print $2}' | xargs)
  310. I_TYPE_RAW=$(grep "PD Type" "$block" | awk -F':' '{print $2}' | xargs)
  311. if echo "$M_TYPE_RAW" | grep -qi "Solid State"; then
  312. M_TYPE="SSD"
  313. else
  314. M_TYPE="HDD"
  315. fi
  316. if echo "$I_TYPE_RAW" | grep -qi "SATA"; then
  317. I_TYPE="SATA"
  318. else
  319. I_TYPE="SAS"
  320. fi
  321. BUS_TYPE="${I_TYPE}/${M_TYPE}"
  322. [ -z "$SLOT" ] && SLOT="N/A"
  323. [ -z "$FAIL" ] && FAIL="0"
  324. [ -z "$MEDIA_ERR" ] && MEDIA_ERR="0"
  325. [ -z "$TEMP" ] && TEMP="N/A"
  326. if [[ "$FAIL" == "0" ]]; then
  327. FAIL_HTML="<span class=\"status-ok\">0</span>"
  328. else
  329. FAIL_HTML="<span class=\"status-fail\">$FAIL</span>"
  330. fi
  331. if [[ "$MEDIA_ERR" == "0" ]]; then
  332. MEDIA_HTML="<span class=\"status-ok\">0</span>"
  333. else
  334. MEDIA_HTML="<span class=\"status-fail\">$MEDIA_ERR</span>"
  335. fi
  336. # Zachytenie chyby disku pre e-mail
  337. if [[ "$FAIL" != "0" || "$MEDIA_ERR" != "0" ]]; then
  338. EMAIL_ALERT=1
  339. EMAIL_BODY="${EMAIL_BODY}\n- Disk Slot $SLOT ($MODL, SN: $SN): Media Errors = $MEDIA_ERR, Predictive Failures = $FAIL"
  340. fi
  341. cat >> $WEB/hdd.html <<EOF
  342. <tr>
  343. <td><b>$SLOT</b></td>
  344. <td>$MODL</td>
  345. <td>$SN</td>
  346. <td>$SIZE</td>
  347. <td><span class="type-tag">$BUS_TYPE</span></td>
  348. <td>$TEMP</td>
  349. <td>$MEDIA_HTML</td>
  350. <td>$FAIL_HTML</td>
  351. </tr>
  352. EOF
  353. echo "$SLOT $MODL $SN $SIZE $BUS_TYPE $TEMP $MEDIA_ERR $FAIL" >> $REP
  354. fi
  355. rm -f "$block"
  356. done
  357. echo "---- -------------------- ---------------- ---------- ---------- --- ------- ----" >> $REP
  358. rm -f /tmp/hdd.tmp
  359. }
  360. #------------------------------ WEB-STOP -------------------------------#
  361. webend(){
  362. cat >> $WEB/hdd.html <<EOF
  363. </tbody>
  364. </table>
  365. </div>
  366. </body>
  367. </html>
  368. EOF
  369. NUM=$(grep -vE "SERIAL|---" $REP | wc -l)
  370. SITE=$(hostname -s | sed 's/-/ /g' | awk '{print $1}')
  371. sed -i "s/\[NUM\]/$NUM/g" $WEB/hdd.html
  372. sed -i "s/DATE/$(date '+%Y-%m-%d %H:%M:%S')/g" $WEB/hdd.html
  373. sed -i "s/SITE/$SITE/g" $WEB/hdd.html
  374. sed -i "s|CTRL_INFO|${CON:-N/A}|g" $WEB/hdd.html
  375. SAFE_BBU=$(echo "$BBU_HTML" | sed -e 's/[]\/$*.^[]/\\&/g')
  376. SAFE_TASK=$(echo "$TASK_HTML" | sed -e 's/[]\/$*.^[]/\\&/g')
  377. SAFE_RAID=$(echo "$RAID_HTML_ACC" | sed -e 's/[]\/$*.^[]/\\&/g')
  378. sed -i "s|BBU_INFO|$SAFE_BBU|g" $WEB/hdd.html
  379. sed -i "s|TASK_INFO|$SAFE_TASK|g" $WEB/hdd.html
  380. sed -i "s|RAID_INFO|$SAFE_RAID|g" $WEB/hdd.html
  381. }
  382. #------------------------------- CORE ----------------------------------#
  383. webstart
  384. checkhdd
  385. webend
  386. IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7}')
  387. echo -e "- Final HTML report (\e[1;35mhttp://${IP:-localhost}/hdd/hdd.html\e[0m)"
  388. cat /tmp/hdd.rac
  389. cat /tmp/hdd.rai | column -t
  390. cat $REP | column -t
  391. #------------------------------ NOTIFICATION ---------------------------#
  392. if [ "$EMAIL_ALERT" -eq 1 ]; then
  393. if [ ! -f "$STATE" ]; then
  394. SUBJECT="[CRITICAL] Storage Error on $SITE ($IP)"
  395. echo -e "On $SITE was detected errors:\n$EMAIL_BODY\n\nDetail report: http://${IP:-localhost}/hdd/hdd.html" | mail -s "$SUBJECT" "$MAIL_TO"
  396. touch "$STATE"
  397. fi
  398. else
  399. if [ -f "$STATE" ]; then
  400. SUBJECT="[RECOVERY] Storage OK on $SITE ($IP)"
  401. echo -e "All drives and RAID array on $SITE are optimal state.\n\nDetail report: http://${IP:-localhost}/hdd/hdd.html" | mail -s "$SUBJECT" "$MAIL_TO"
  402. rm -f "$STATE"
  403. fi
  404. fi
  405. EOGTR