├── LICENSE
├── README.md
├── install.sh
├── raid_monitoring_tools
├── arcconf32_old
├── arcconf64_old
├── arcconf_v2
├── arcconf_v3
├── megacli32
├── megacli64
├── smartctl32
└── smartctl64
└── storage_system_fastvps_monitoring.pl
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | storage-system-monitoring
2 | ==========================
3 |
4 | Welcome, dear FastVPS Eesti OU customer! :) You've got here because we really care about you and your data safety!
5 |
6 | You may find an open code of our disk subsystem diagnose system for your server.
7 |
8 | ### How to install the monitoring script?
9 | - Automated install (recommended):
10 | ```bash
11 | wget --no-check-certificate https://raw.github.com/FastVPSEestiOu/storage-system-monitoring/master/install.sh -O /tmp/storage_install.sh && bash /tmp/storage_install.sh && rm --force /tmp/storage_install.sh
12 | ```
13 |
14 | - [Manual install](#manual-installation).
15 |
16 | ### Which operating systems are supported:
17 | - Debian: 6 / 7 / 8 / 9 / 10 / 11 / 12
18 | - AlmaLinux: 8
19 | - Centos: 6 / 7 / 8
20 | - Rocky: 8
21 | - Ubuntu: 12.04 / 14.04 / 16.04 / 18.04 / 20.04 / 22.04 / 24.04
22 |
23 | ### Is this script safe?
24 | - The script works via an ecrypted channel (https, ssl)
25 | - The script doesn't open any ports in the system (which excludes a chance of intrusion from outside)
26 | - The script doesn't update itself automatically (which excludes adding vulnerabilities)
27 | - The script has an open code (which gives you a chance to read its content)
28 |
29 | ### Where does it send all data?
30 | - The data is send to https://fastcheck24.com via an ecrypted channel
31 |
32 | ### What do we do with the data?
33 | - We analyze it with a special software that uses various alogorythms to predict a disk subsystem failure
34 | - In the event of detecting a potentially destructive promlems with the disk subsystem we shall contact you in any available way
35 |
36 | ### Which types of RAID are being suppored by the monitoring?
37 | - Adaptec
38 | - LSI
39 | - DELL PERC (LSI)
40 |
41 | ### What does the script do?
42 | - Sends Linux Soft Raid (mdadm only) or hardware RAID array status hourly
43 | - Sends smartctl output regarding all disks in the system
44 | - Executes S.M.A.R.T. tests (short+long) every weekend
45 |
46 | ### What the script does NOT do?
47 | - The script does not run any additional modules
48 | - The script does not update itself automatically
49 | - The script does not send any information except what is listed above
50 |
51 | ### Which program language the script was written in?
52 | - Perl (monitoring module)
53 | - Bash (installer)
54 |
55 | ### What changes do we do in your system?
56 | - We create a cron script: /etc/cron.d/storage-system-monitoring-fastvps
57 | - We place arcconf, megaraid and storage_system_fastvps_monitoring.pl script in /usr/local/bin directory
58 | - We change smartd configuration /etc/smartd.conf (is required to autorun S.M.A.R.T. short/long tests)
59 |
60 | ### Who may use the software?
61 | - Any FastVPS Eesti OU customer
62 |
63 | ### What kind of software do we install on the server and why?
64 | - smartmontools - a package of utilities for obtaining S.M.A.R.T. information from the device
65 | - Perl modules to send data via HTTP and HTTPS protocols to our server for analysis
66 | - arcconf/megacli - Adaptec и LSI vendor utilities
67 |
68 | ### Where do we get proprietary software for LSI/Adaptec?
69 | - https://storage.microsemi.com/en-us/speed/raid/storage_manager/arcconf_v2_01_22270_zip.php
70 | - https://docs.broadcom.com/docs-and-downloads/sep/oracle/files/Linux_MegaCLI-8-07-07.zip
71 |
72 | ### May I use the program locally to check an array status?
73 | - Sure, but you loose all the features of our S.M.A.R.T. analyze system and other metrics. Only array contidion can be checked. Moreover you will not get any notifications when a disk fails
74 |
75 | ### Is XXX YYY support available?
76 | - Of course, patches are welcome!
77 |
78 | ### How does the script output looks like?
79 | ```bash
80 | storage_system_fastvps_monitoring.pl --detect
81 | Device /dev/sda with type: raid model: adaptec in state: optimal detected
82 | Device /dev/sda with type: raid model: lsi in state: optimal detected
83 | Device /dev/sda with type: hard_disk model: ATA SAMSUNG HD753LJ detected
84 | Device /dev/sdb with type: hard_disk model: ATA SAMSUNG HD753LJ detected
85 | Device /dev/sdc with type: hard_disk model: ATA ST31500341AS detected
86 | Device /dev/sdd with type: hard_disk model: ATA ST31500341AS detected
87 | Device /dev/md0 with type: raid model: md in state: clean detected
88 | Device /dev/md1 with type: raid model: md in state: clean detected
89 | Device /dev/md2 with type: raid model: md in state: clean detected
90 | Device /dev/md3 with type: raid model: md in state: clean detected
91 | ```
92 | ------------------------------------------
93 |
94 | ### Manual installation
95 |
96 | #### If you have hardware RAID controller installed, you will need to install software to work with it
97 | - For **Adaptec** controllers you need to select software version according to the controller model. Normally v2 and v3 utility versions are used for 6-series controllers and newer. It is only available for 64-bit OS. [List of supported controllers by the v2 utility](https://storage.microsemi.com/en-us/speed/raid/storage_manager/arcconf_v2_05_22932_zip.php).
98 | ```bash
99 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/arcconf_v2 --output-document=/usr/local/bin/arcconf
100 | chmod +x /usr/local/bin/arcconf
101 | ```
102 | [List of supported controllers by the v3 utility](https://storage.microsemi.com/en-us/speed/raid/storage_manager/arcconf_v3_00_23488_zip.php).
103 | ```bash
104 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/arcconf_v3 --output-document=/usr/local/bin/arcconf
105 | chmod +x /usr/local/bin/arcconf
106 | ```
107 |
108 | - For older controllers you the older version is used, and you need to select version for OS architecture. [Link with the list of controllers supported](https://storage.microsemi.com/en-us/speed/raid/storage_manager/asm_linux_x64_v7_31_18856_tgz.php).
109 | ```bash
110 | # 64-bit OS
111 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/arcconf64_old --output-document=/usr/local/bin/arcconf
112 | chmod +x /usr/local/bin/arcconf
113 |
114 | # 32-bit OS
115 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/arcconf32_old --output-document=/usr/local/bin/arcconf
116 | chmod +x /usr/local/bin/arcconf
117 | ```
118 |
119 | - If you are not sure, it is better to find the version you need [here](https://storage.microsemi.com/en-us/downloads/).
120 |
121 | - For **LSI** you nned to select version for your OS architecture.
122 | ```bash
123 | # 64-bit OS
124 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/megacli64 --output-document=/usr/local/bin/megacli
125 | chmod +x /usr/local/bin/megacli
126 |
127 | # 32-bit OS
128 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/raid_monitoring_tools/megacli32 --output-document=/usr/local/bin/megacli
129 | chmod +x /usr/local/bin/megacli
130 | ```
131 |
132 | #### You will need to create smartd.conf file according to the RAID type used
133 |
134 | - Software Raid.
135 | ```bash
136 | cp /etc/smartd.conf /etc/smartd.conf.$$
137 | echo 'DEVICESCAN -d removable -n standby -s (S/../.././02|L/../../7/03)' > /etc/smartd.conf
138 | ```
139 |
140 | - Adaptec. Detect **/dev/sgX** devices related to physical drives and add the line for each of them:
141 | ```bash
142 | /dev/sgX -n standby -s (S/../.././02|L/../../7/03)
143 | ```
144 |
145 | ```bash
146 | cp /etc/smartd.conf /etc/smartd.conf.$$
147 | echo '' > /etc/smartd.conf
148 | for sgx in /dev/sg?; do
149 | if smartctl -q silent -i "$sgx"; then
150 | echo "${sgx} -n standby -s (S/../.././02|L/../../7/03)" >> /etc/smartd.conf
151 | fi
152 | done
153 | ```
154 |
155 | - LSI. Detect physical drives using **megacli** and add the line for each of themL
156 | ```bash
157 | /dev/sda -d megaraid,2 -n standby -s (S/../.././02|L/../../7/03)
158 | ```
159 |
160 | ```bash
161 | cp /etc/smartd.conf /etc/smartd.conf.$$
162 | echo '' > /etc/smartd.conf
163 | for drive in $(megacli -pdlist -a0| awk '/Device Id/ {print $NF}'); do
164 | echo "/dev/sda -d megaraid,${drive} -n standby -s (S/../.././02|L/../../7/03)" >> /etc/smartd.conf
165 | done
166 | ```
167 |
168 | #### Debian 8/9/10/11/12, Ubuntu 16.04/18.04/20.04/22.04/24.04
169 | ```bash
170 | apt-get update -qq && apt-get install wget libstdc++5 smartmontools liblwp-useragent-determined-perl libnet-https-any-perl libcrypt-ssleay-perl libjson-perl
171 |
172 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/storage_system_fastvps_monitoring.pl --output-document=/usr/local/bin/storage_system_fastvps_monitoring.pl
173 | chmod +x /usr/local/bin/storage_system_fastvps_monitoring.pl
174 |
175 | echo "# FastVPS disk monitoring tool
176 | # https://github.com/FastVPSEestiOu/storage-system-monitoring
177 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
178 | $(((RANDOM % 59))) * * * * root /usr/local/bin/storage_system_fastvps_monitoring.pl --cron >/dev/null 2>&1" > /etc/cron.d/storage-system-monitoring-fastvps
179 | chmod 644 /etc/cron.d/storage-system-monitoring-fastvps
180 |
181 | systemctl restart smartd.service
182 | systemctl enable smartd.service
183 | ```
184 |
185 | #### Debian 6/7, Ubuntu 12.04/14.04
186 | ```bash
187 | apt-get update -qq && apt-get install wget libstdc++5 smartmontools liblwp-useragent-determined-perl libnet-https-any-perl libcrypt-ssleay-perl libjson-perl
188 |
189 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/storage_system_fastvps_monitoring.pl --output-document=/usr/local/bin/storage_system_fastvps_monitoring.pl
190 | chmod +x /usr/local/bin/storage_system_fastvps_monitoring.pl
191 |
192 | echo "# FastVPS disk monitoring tool
193 | # https://github.com/FastVPSEestiOu/storage-system-monitoring
194 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
195 | $(((RANDOM % 59))) * * * * root /usr/local/bin/storage_system_fastvps_monitoring.pl --cron >/dev/null 2>&1" > /etc/cron.d/storage-system-monitoring-fastvps
196 | chmod 644 /etc/cron.d/storage-system-monitoring-fastvps
197 |
198 | /etc/init.d/smartmontools restart
199 | update-rc.d smartmontools defaults
200 | ```
201 |
202 | #### CentOS 7/8, AlmaLinux 8, Rocky 8
203 | ```bash
204 | yum install -q -y wget libstdc++ smartmontools perl-Crypt-SSLeay perl-libwww-perl perl-JSON perl-LWP-Protocol-https
205 |
206 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/storage_system_fastvps_monitoring.pl --output-document=/usr/local/bin/storage_system_fastvps_monitoring.pl
207 | chmod +x /usr/local/bin/storage_system_fastvps_monitoring.pl
208 |
209 | echo "# FastVPS disk monitoring tool
210 | # https://github.com/FastVPSEestiOu/storage-system-monitoring
211 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
212 | $(((RANDOM % 59))) * * * * root /usr/local/bin/storage_system_fastvps_monitoring.pl --cron >/dev/null 2>&1" > /etc/cron.d/storage-system-monitoring-fastvps
213 | chmod 644 /etc/cron.d/storage-system-monitoring-fastvps
214 |
215 | systemctl restart smartd.service
216 | systemctl enable smartd.service
217 | ```
218 |
219 | #### CentOS 6
220 | ```bash
221 | yum install -q -y wget libstdc++ smartmontools perl-Crypt-SSLeay perl-libwww-perl perl-JSON
222 |
223 | wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master/storage_system_fastvps_monitoring.pl --output-document=/usr/local/bin/storage_system_fastvps_monitoring.pl
224 | chmod +x /usr/local/bin/storage_system_fastvps_monitoring.pl
225 |
226 | echo "# FastVPS disk monitoring tool
227 | # https://github.com/FastVPSEestiOu/storage-system-monitoring
228 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
229 | $(((RANDOM % 59))) * * * * root /usr/local/bin/storage_system_fastvps_monitoring.pl --cron >/dev/null 2>&1" > /etc/cron.d/storage-system-monitoring-fastvps
230 | chmod 644 /etc/cron.d/storage-system-monitoring-fastvps
231 |
232 | /etc/init.d/smartd restart
233 | chkconfig smartd on
234 | ```
235 |
236 | #### Make a local check and send test data to the remote server
237 | ```bash
238 | /usr/local/bin/storage_system_fastvps_monitoring.pl --detect
239 |
240 | /usr/local/bin/storage_system_fastvps_monitoring.pl --cron
241 | ```
242 | The installation is finished.
243 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # That is installation script for storage system monitoring by FASTVPS Eesti OU
4 | # If you have any questions about that system, please contact us:
5 | # - https://github.com/FastVPSEestiOu/storage-system-monitoring
6 | # - https://bill2fast.com (via ticket system)
7 |
8 | set -u
9 |
10 | # Disable interactive mode when configuring packages
11 | export DEBIAN_FRONTEND='noninteractive'
12 |
13 | # Setting text colors
14 | TXT_GRN='\e[0;32m'
15 | TXT_RED='\e[0;31m'
16 | TXT_YLW='\e[0;33m'
17 | TXT_RST='\e[0m'
18 |
19 | # Set variable for pid
20 | PID=$$
21 |
22 | # Path for binaries
23 | BIN_PATH='/usr/local/bin'
24 |
25 | # Path of our repo, used for downloads
26 | REPO_PATH='https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/master'
27 |
28 | # Name of our script
29 | SCRIPT_NAME='storage_system_fastvps_monitoring.pl'
30 |
31 | # Path of our cron task
32 | CRON_FILE='/etc/cron.d/storage-system-monitoring-fastvps'
33 |
34 | # Static header for our cron task
35 | CRON_HEADER='# FastVPS disk monitoring tool
36 | # https://github.com/FastVPSEestiOu/storage-system-monitoring
37 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
38 |
39 | # Suffix we add to moved smartd.conf
40 | SMARTD_SUFFIX="fastvps_backup.${PID}"
41 |
42 | # Static header for our smartd.conf
43 | SMARTD_HEADER="# smartd.conf by FastVPS
44 | # backup version of distrib file saved to /etc/smartd.conf.$SMARTD_SUFFIX
45 | # Discover disks and run short tests every day at 02:00 and long tests every sunday at 03:00"
46 |
47 | # Stable smartctl version (SVN revision)
48 | SMARTCTL_STABLE_VERSION='7.2'
49 | SMARTCTL_STABLE_REVISION='5155'
50 |
51 | # Smartd config path
52 | declare -A SMARTD_CONF_FILE
53 | SMARTD_CONF_FILE["deb"]='/etc/smartd.conf'
54 | SMARTD_CONF_FILE["deb_old"]='/etc/smartd.conf'
55 | SMARTD_CONF_FILE["rpm_old"]='/etc/smartd.conf'
56 | SMARTD_CONF_FILE["rpm_new"]='/etc/smartmontools/smartd.conf'
57 |
58 | OS=''
59 | ARCH=''
60 | OS_TYPE=''
61 | CRON_MINUTES=''
62 | RAID_TYPE=''
63 |
64 | # Dependencies
65 | declare -A PKG_DEPS
66 | PKG_DEPS["deb"]='wget libstdc++5 smartmontools liblwp-useragent-determined-perl libnet-https-any-perl libcrypt-ssleay-perl libjson-perl'
67 | PKG_DEPS["deb_old"]='wget libstdc++5 smartmontools liblwp-useragent-determined-perl libnet-https-any-perl libcrypt-ssleay-perl libjson-perl'
68 | PKG_DEPS["rpm_old"]='wget libstdc++ smartmontools perl-Crypt-SSLeay perl-libwww-perl perl-JSON'
69 | PKG_DEPS["rpm_new"]='wget libstdc++ smartmontools perl-libwww-perl perl-JSON perl-LWP-Protocol-https'
70 |
71 | declare -A PKG_INSTALL
72 | PKG_INSTALL["deb"]='apt-get update -qq && apt-get install -qq'
73 | PKG_INSTALL["deb_old"]='apt-get update -o Acquire::Check-Valid-Until=false -qq && apt-get install -qq --allow-unauthenticated'
74 | PKG_INSTALL["rpm_old"]='yum install -q -y'
75 | PKG_INSTALL["rpm_new"]='yum install -q -y'
76 |
77 | # List of packages which we do NOT want to update, in form of regex
78 | declare -A PKG_UNSAFE
79 | PKG_UNSAFE["deb"]='Inst libc6|Inst apache2|Inst php'
80 | PKG_UNSAFE["deb_old"]='Inst libc6|Inst apache2|Inst php'
81 | PKG_UNSAFE["rpm_old"]='^ glibc|^ httpd|^ php'
82 | PKG_UNSAFE["rpm_new"]='^ glibc|^ httpd|^ php'
83 |
84 | # And command for it
85 | declare -A PKG_INSTALL_TEST
86 | PKG_INSTALL_TEST["deb"]='apt-get update && apt-get install -s'
87 | PKG_INSTALL_TEST["deb_old"]='apt-get update -o Acquire::Check-Valid-Until=false -qq && apt-get install -s'
88 | PKG_INSTALL_TEST["rpm_old"]='yum install --assumeno'
89 | PKG_INSTALL_TEST["rpm_new"]='yum install --assumeno'
90 |
91 |
92 | # Some fancy echoing
93 | _echo_OK()
94 | {
95 | echo -e " -> ${TXT_GRN}OK${TXT_RST}"
96 | }
97 |
98 | _echo_FAIL()
99 | {
100 | echo -e " -> ${TXT_RED}FAIL${TXT_RST}"
101 | }
102 |
103 | _echo_tabbed()
104 | {
105 | local message=$1
106 |
107 | echo -e " -> $message"
108 | }
109 |
110 | _echo_result()
111 | {
112 | local result=$*
113 | if [[ "$result" -eq 0 ]]; then
114 | _echo_OK
115 | else
116 | _echo_FAIL
117 | exit 1
118 | fi
119 | }
120 |
121 | # Detect OS
122 | _detect_os()
123 | {
124 | local issue_file='/etc/issue'
125 | local os_release_file='/etc/os-release'
126 | local redhat_release_file='/etc/redhat-release'
127 | local os=''
128 | local name=''
129 | local version=''
130 | # First of all, trying os-relese file
131 | if [ -f $os_release_file ]; then
132 | name=$(grep '^NAME=' $os_release_file | awk -F'[" ]' '{print $2}')
133 | version=$(grep '^VERSION_ID=' $os_release_file | awk -F'[". ]' '{print $2}')
134 | os="${name}${version}"
135 | else
136 | # If not, trying redhat-release file (mainly because of bitrix-env)
137 | if [ -f $redhat_release_file ]; then
138 | os=$(head -1 /etc/redhat-release | sed -re 's/([A-Za-z]+)[^0-9]*([0-9]+).*$/\1\2/')
139 | else
140 | # Else, trying issue file
141 | if [ -f $issue_file ]; then
142 | os=$(head -1 $issue_file | sed -re 's/([A-Za-z]+)[^0-9]*([0-9]+).*$/\1\2/')
143 | else
144 | # If none of that files worked, exit
145 | echo -e "${TXT_RED}Cannot detect OS. Exiting now"'!'"${TXT_RST}"
146 | exit 1
147 | fi
148 | fi
149 | fi
150 | OS=$os
151 | }
152 |
153 | # Detect architecture
154 | _detect_arch()
155 | {
156 | local arch=''
157 | local uname=''
158 |
159 | uname=$(uname -m)
160 | if [[ $uname == 'x86_64' ]]; then
161 | arch=64
162 | else
163 | arch=32
164 | fi
165 |
166 | ARCH=$arch
167 | }
168 |
169 | # Select OS type based on OS
170 | _select_os_type()
171 | {
172 | local os=$1
173 | local os_type=''
174 |
175 | case $os in
176 | Debian[6-7] )
177 | os_type='deb_old'
178 | ;;
179 | Debian[8-9]|Debian1[0-2]|Ubuntu* )
180 | os_type='deb'
181 | ;;
182 | CentOS6 )
183 | os_type='rpm_old'
184 | ;;
185 | CentOS[7-8]|AlmaLinux[8-9]|Rocky[8-9] )
186 | os_type='rpm_new'
187 | ;;
188 | * )
189 | echo "We can do nothing on $os. Exiting."
190 | _echo_FAIL
191 | exit 1
192 | ;;
193 | esac
194 |
195 | OS_TYPE=$os_type
196 | }
197 |
198 | # Check and install needed software
199 | _install_deps()
200 | {
201 | local os_type=$1
202 | local pkgs_to_install=()
203 | local unsafe_pkgs=()
204 |
205 | # Check if we have packages needed
206 | local pkg=''
207 | local result=''
208 |
209 | for pkg in ${PKG_DEPS[$os_type]}; do
210 | if ! _check_pkg "$os_type" "$pkg" ; then
211 | pkgs_to_install+=("$pkg")
212 | fi
213 | done
214 |
215 | if [[ ${#pkgs_to_install[@]} -eq 0 ]]; then
216 | _echo_tabbed "We have everything we need."
217 | return 0
218 | else
219 | _echo_tabbed "Installing: ${TXT_YLW}${pkgs_to_install[*]}${TXT_RST} ..."
220 |
221 | # Check if we are going to break something
222 | mapfile -t < <( eval "${PKG_INSTALL_TEST[$os_type]}" "${pkgs_to_install[@]}" ) result
223 | for (( i=0; i<${#result[@]}; i++ )); do
224 | if [[ "${result[i]}" =~ ${PKG_UNSAFE[$os_type]} ]]; then
225 | unsafe_pkgs+=("${result[i]}")
226 | fi
227 | done
228 |
229 | if [[ ${#unsafe_pkgs[@]} -gt 0 ]]; then
230 | echo "We are going to update something we do not want to:"
231 | for (( i=0; i<${#unsafe_pkgs[@]}; i++ )); do
232 | echo "${unsafe_pkgs[i]}";
233 | done
234 | echo -e "\nYou can check it yourself with command:\n${PKG_INSTALL_TEST[$os_type]}" "${pkgs_to_install[@]}"
235 | return 1
236 | fi
237 |
238 | # Catch error in variable
239 | if IFS=$'\n' result=( $(eval "${PKG_INSTALL[$os_type]}" "${pkgs_to_install[@]}" 2>&1) ); then
240 | return 0
241 |
242 | # And output it, if we had nonzero exit code
243 | else
244 | echo
245 | for (( i=0; i<${#result[@]}; i++ )); do
246 | echo "${result[i]}";
247 | done
248 | return 1
249 | fi
250 | fi
251 | }
252 |
253 | # Check package
254 | _check_pkg()
255 | {
256 | local os_type=$1
257 | local pkg=$2
258 |
259 | case $os_type in
260 | deb* )
261 | if dpkg-query -W -f='\${Status}' "$pkg" 2>&1 | grep -qE '^(\$install ok installed)+$'; then
262 | return 0
263 | else
264 | return 1
265 | fi
266 | ;;
267 | rpm* )
268 | if rpm --quiet -q "$pkg"; then
269 | return 0
270 | else
271 | return 1
272 | fi
273 | ;;
274 | * )
275 | _echo_tabbed "We can do nothing on $os_type. Exiting."
276 | exit 1
277 | ;;
278 | esac
279 | }
280 |
281 | # Function to download with check
282 | _dl_and_check()
283 | {
284 | local remote_path=$1
285 | local local_path=$2
286 | local os=$OS
287 | local result=()
288 | local wget_param=()
289 |
290 | # Adding --no-check-certificate on old OS
291 | case $os in
292 | Debian6 )
293 | wget_param=(--no-check-certificate --verbose)
294 | ;;
295 | * )
296 | wget_param=(--verbose)
297 | ;;
298 | esac
299 |
300 | # Clean target path before download
301 | if [[ -x "$local_path" ]]; then
302 | rm -f "$local_path";
303 | fi
304 |
305 | # Catch error in variable
306 | if IFS=$'\n' result=( $(wget "${wget_param[@]}" "$remote_path" --output-document="$local_path" 2>&1) ); then
307 | return 0
308 |
309 | # And output it, if we had nonzero exit code
310 | else
311 | echo
312 | for (( i=0; i<${#result[@]}; i++ )); do
313 | echo "${result[i]}";
314 | done
315 | return 1
316 | fi
317 | }
318 |
319 | # Install RAID tools if needed
320 | _install_raid_tools()
321 | {
322 | local bin_path=$1
323 | local repo_path=$2
324 | local arch=$3
325 |
326 | local util_path=''
327 | local dl_path=''
328 | local raid_type=''
329 |
330 | # Detect RAID
331 | local sys_block_check=''
332 | sys_block_check=$(cat /sys/block/*/device/vendor /sys/block/*/device/model 2>/dev/null | grep -oEm1 'Adaptec|LSI|PERC|ASR8405')
333 |
334 | # Select utility to install
335 | case $sys_block_check in
336 | # arcconf for Adaptec
337 | ASR8405 )
338 | raid_type='adaptec'
339 | util_path="${bin_path}/arcconf"
340 |
341 | _echo_tabbed "Found RAID: ${TXT_YLW}${sys_block_check}${TXT_RST}"
342 |
343 | dl_path="${repo_path}/raid_monitoring_tools/arcconf_v2"
344 | ;;
345 | Adaptec )
346 | raid_type='adaptec'
347 | util_path="${bin_path}/arcconf"
348 |
349 | local adaptec_version=''
350 | adaptec_version=$(lspci -m | awk -F\" '/Adaptec/ {print $(NF-1)}')
351 |
352 | _echo_tabbed "Found RAID: ${TXT_YLW}${sys_block_check} ${adaptec_version}${TXT_RST}"
353 |
354 | # Select arcconf version dpending on controller version
355 | case $adaptec_version in
356 | # Old Adaptec controller (2xxx-5xxx) - Adaptec Storage Manager v7
357 | *[2-5][0-9][0-9][0-9] )
358 | dl_path="${repo_path}/raid_monitoring_tools/arcconf${arch}_old"
359 | ;;
360 | # Newer Adaptec controller (6xxx-8xxx) - arcconf v2
361 | *[6-8][0-9][0-9][0-9] )
362 | dl_path="${repo_path}/raid_monitoring_tools/arcconf_v2"
363 | ;;
364 | # Even newer Adaptec controller (SmartRAID 31xx) - arcconf v3
365 | *SmartRAID*31[0-9][0-9]* )
366 | dl_path="${repo_path}/raid_monitoring_tools/arcconf_v3"
367 | ;;
368 | # Otherwise exit
369 | * )
370 | echo "We don't know, what arcconf version is needed."
371 | return 1
372 | ;;
373 | esac
374 |
375 | ;;
376 |
377 | # megacli for LSI (PERC is LSI controller on DELL)
378 | LSI|PERC )
379 | raid_type='lsi'
380 | _echo_tabbed "Found RAID: ${TXT_YLW}${sys_block_check}${TXT_RST}"
381 | util_path="${bin_path}/megacli"
382 | dl_path="${repo_path}/raid_monitoring_tools/megacli${arch}"
383 | ;;
384 |
385 | # Nothing if none RAID found
386 | '' )
387 | raid_type='soft'
388 | _echo_tabbed "No HW RAID."
389 | ;;
390 |
391 | # Fallback that should never be reached
392 | * )
393 | _echo_tabbed "Unknown RAID type: ${TXT_YLW}${sys_block_check}${TXT_RST}. Exiting."
394 | return 1
395 | ;;
396 | esac
397 |
398 |
399 | # Set raid type for smartd
400 | RAID_TYPE="$raid_type"
401 |
402 | # Download selected utility
403 | case $raid_type in
404 | soft )
405 | return 0
406 | ;;
407 | adaptec|lsi )
408 | if _dl_and_check "$dl_path" "$util_path"; then
409 | chmod +x "$util_path"
410 | _echo_tabbed "Installed ${TXT_YLW}${util_path}${TXT_RST}"
411 | RAID_TYPE="$raid_type"
412 | return 0
413 | else
414 | return 1
415 | fi
416 | ;;
417 | # Fallback that should never be reached
418 | * )
419 | _echo_tabbed "Unknown RAID type: ${TXT_YLW}${raid_type}${TXT_RST}. Exiting."
420 | return 1
421 | ;;
422 | esac
423 | }
424 |
425 | # Install new smartctl binary, if we have too old one
426 | _install_smartctl()
427 | {
428 | local bin_path=$1
429 | local repo_path=$2
430 | local arch=$3
431 | local smartctl_stable_version=$4
432 | local smartctl_stable_revision=$5
433 |
434 | local smartctl_current_version=''
435 | local smartctl_current_revision=''
436 | local version_comp_result=''
437 |
438 | local util_path="${bin_path}/smartctl"
439 | local dl_path="${repo_path}/raid_monitoring_tools/smartctl${arch}"
440 |
441 | smartctl_current_version=$(smartctl --version | awk '/^smartmontools release/ {print $3}')
442 | smartctl_current_revision=$(smartctl --version | awk '/^smartmontools SVN rev/ {print $4}')
443 |
444 |
445 | # If current version is lower then stable, download a new one
446 |
447 | # We'll get exit code 2 if current version is lower than stable version
448 | _version_copmare "$smartctl_current_version" "$smartctl_stable_version"
449 | version_comp_result=$?
450 |
451 | if [[ "$version_comp_result" -eq "2" ]] || [[ "$smartctl_current_revision" -lt "$smartctl_stable_revision" ]]; then
452 | if _dl_and_check "$dl_path" "$util_path"; then
453 | chmod +x "$util_path"
454 | _echo_tabbed "Installed ${TXT_YLW}${util_path}${TXT_RST}"
455 | return 0
456 | else
457 | return 1
458 | fi
459 | else
460 | _echo_tabbed "We have smartctl version ${TXT_YLW}${smartctl_current_version}${TXT_RST} (rev. ${TXT_YLW}${smartctl_current_revision}${TXT_RST}) here."
461 | return 0
462 | fi
463 | }
464 |
465 | # Function to compare dotted versions
466 | _version_copmare()
467 | {
468 | # Compares two versions
469 | # Returns:
470 | # 0 -> first = second
471 | # 1 -> first > second
472 | # 2 -> first < second
473 |
474 | local first_string=$1
475 | local second_string=$2
476 |
477 | local first_array=()
478 | local second_array=()
479 |
480 | # Split versions into arrays
481 | IFS='.' read -ra first_array <<< "$first_string"
482 | IFS='.' read -ra second_array <<< "$second_string"
483 |
484 | # Fill empty fields in first array with zeros
485 | for ((i=${#first_array[@]}; i<${#second_array[@]}; i++)); do
486 | first_array[$i]=0
487 | done
488 |
489 | for ((i=0; i<${#first_array[@]}; i++)); do
490 | # Fill empty fields in second array with zeros
491 | if [[ -z ${second_array[$i]} ]]; then
492 | second_array[$i]=0
493 | fi
494 |
495 | # "10#" forces decimal numbers interpretation
496 | if ((10#${first_array[$i]} > 10#${second_array[$i]})); then
497 | return 1
498 | fi
499 | if ((10#${first_array[$i]} < 10#${second_array[$i]})); then
500 | return 2
501 | fi
502 | done
503 |
504 | return 0
505 | }
506 |
507 | # Install monitoring script
508 | _install_script()
509 | {
510 | local bin_path=$1
511 | local repo_path=$2
512 | local script_name=$3
513 | local cron_file=$4
514 | local cron_minutes=$5
515 | local cron_header=$6
516 |
517 | local script_local="${bin_path}/${script_name}"
518 | local script_remote="${repo_path}/${script_name}"
519 |
520 | if _dl_and_check "$script_remote" "$script_local"; then
521 | chmod +x -- "$script_local"
522 | _echo_tabbed "Installed ${TXT_YLW}${script_local}${TXT_RST}"
523 | else
524 | return 1
525 | fi
526 |
527 | if _set_cron "$cron_file" "$script_local" "$cron_minutes" "$cron_header"; then
528 | _echo_tabbed "Installed ${TXT_YLW}${cron_file}${TXT_RST}"
529 | return 0
530 | else
531 | return 1
532 | fi
533 | }
534 |
535 |
536 | # Add cron task for script
537 | _set_cron()
538 | {
539 | local cron_file=$1
540 | local script_local=$2
541 | local cron_minutes=$3
542 | local cron_header=$4
543 |
544 | local cron_text=''
545 |
546 | local cron_line="$cron_minutes * * * * root $script_local --cron >/dev/null 2>&1"
547 |
548 | read -r -d '' cron_text < "$cron_file"
554 | chmod 644 -- "$cron_file"
555 | }
556 |
557 | # Configure SMARTD
558 | _set_smartd()
559 | {
560 | local raid_type=$1
561 | local smartd_header=$2
562 | local os_type=$3
563 | local smartd_suffix=$4
564 |
565 | local smartd_conf_file=${SMARTD_CONF_FILE[$os_type]}
566 | local smartd_conf_backup=${smartd_conf_file}.${smartd_suffix}
567 |
568 | local smartd_conf=''
569 | local drive=''
570 | local drives=()
571 | local lines=()
572 |
573 | # Select smartd.conf for our RAID type
574 | case $raid_type in
575 | soft )
576 | lines+=('DEVICESCAN -d removable -n standby -s (S/../.././02|L/../../7/03)')
577 | ;;
578 | adaptec )
579 | # For older controllers (aacraid)
580 | if [[ -d '/sys/bus/pci/drivers/aacraid' ]]; then
581 | # Try to load sg module if it is not loaded for some reason
582 | if [[ ! -c /dev/sg0 ]]; then
583 | # Catch error in variable
584 | if IFS=$'\n' result=( $(modprobe sg 2>&1) ); then
585 | _echo_tabbed "Loaded ${TXT_YLW}sg${TXT_RST} module."
586 |
587 | # And output it, if we had nonzero exit code
588 | else
589 | echo
590 | for (( i=0; i<${#result[@]}; i++ )); do
591 | echo "${result[i]}";
592 | done
593 | _echo_tabbed "Failed to load ${TXT_YLW}sg${TXT_RST} module. We need it to work with Adaptec controller."
594 | return 1
595 | fi
596 | fi
597 |
598 | # Get drives to check
599 | local sgx=''
600 | for sgx in /dev/sg?; do
601 | if smartctl -q silent -i "$sgx"; then
602 | drives+=("$sgx")
603 | fi
604 | done
605 |
606 | if [[ ${#drives[@]} -eq 0 ]]; then
607 | _echo_tabbed "Failed to get ${TXT_YLW}/dev/sg?${TXT_RST} drives for Adaptec controller. We have tried ${TXT_YLW}modprobe sg${TXT_RST} but without success. Check it and proceed manually."
608 | return 1
609 | fi
610 |
611 | # Form smartd rules
612 | for drive in "${drives[@]}"; do
613 | lines+=("$drive -n standby -s (S/../.././02|L/../../7/03)")
614 | done
615 | # For newer controllers (smartpqi)
616 | elif [[ -d '/sys/bus/pci/drivers/smartpqi' ]]; then
617 | # Get drives to check
618 | mapfile -t < <( arcconf getconfig 1 pd | grep 'Reported Location' | sed -e 's/^.*Slot \([0-9]\).*/\1/g' -e 's/^.*Device \([0-9]\).*/\1/g' ) drives
619 |
620 | if [[ ${#drives[@]} -eq 0 ]]; then
621 | _echo_tabbed "Failed to get drives for Adaptec controller. Try to call ${TXT_YLW}arcconf getconfig 1 pd | grep 'Reported Location'${TXT_RST} and check the output."
622 | return 1
623 | fi
624 |
625 | # Form smartd rules
626 | for drive in "${drives[@]}"; do
627 | lines+=("/dev/sda -d cciss,$drive -n standby -s (S/../.././02|L/../../7/03)")
628 | done
629 | fi
630 | ;;
631 | lsi )
632 | # Get drives to check
633 | mapfile -t < <( megacli -pdlist -a0| awk '/Device Id/ {print $NF}' ) drives
634 |
635 | if [[ ${#drives[@]} -eq 0 ]]; then
636 | _echo_tabbed "Failed to get drives for LSI controller. Try to call ${TXT_YLW}megacli -pdlist -a0${TXT_RST} and check the output."
637 | return 1
638 | fi
639 |
640 | # Form smartd rules
641 | for drive in "${drives[@]}"; do
642 | lines+=("/dev/sda -d megaraid,${drive} -n standby -s (S/../.././02|L/../../7/03)")
643 | done
644 | ;;
645 | * )
646 | _echo_tabbed "Unknown RAID type: ${TXT_YLW}${raid_type}${TXT_RST}. Exiting."
647 | return 1
648 | ;;
649 | esac
650 |
651 | IFS=$'\n' read -r -d '' smartd_conf < "$smartd_conf_file"; then
663 | _echo_tabbed "Filled ${TXT_YLW}${smartd_conf_file}${TXT_RST}"
664 | return 0
665 | else
666 | return 1
667 | fi
668 | }
669 |
670 |
671 | # Restart smartd to enable config
672 | _restart_smartd()
673 | {
674 | local os=$1
675 | local restart_cmd=''
676 |
677 | case $os in
678 | # systemctl on new OS
679 | Debian[8-9]|Debian1[0-2]|CentOS[7-8]|AlmaLinux[8-9]|Rocky[8-9]|Ubuntu1[6789]|Ubuntu2[0-4] )
680 | restart_cmd='systemctl restart smartd.service'
681 | ;;
682 | # /etc/init.d/ on sysv|upstart OS
683 | CentOS6 )
684 | restart_cmd='/etc/init.d/smartd restart'
685 | ## On Debian 7 we should always have /etc/init.d/smartmontools while /etc/init.d/smartd can be removed when using backports
686 | ;;
687 | Debian[6-7]|Ubuntu12|Ubuntu14 )
688 | # Hack for Debain 6-7
689 | sed -i -e 's/^#start_smartd/start_smartd/' /etc/default/smartmontools
690 | restart_cmd='/etc/init.d/smartmontools restart'
691 | ;;
692 | * )
693 | _echo_tabbed "Don't know how to restart smartd on that OS: ${TXT_YLW}${os}${TXT_RST}"
694 | return 1
695 | ;;
696 | esac
697 |
698 | # Catch error in variable
699 | if IFS=$'\n' result=( $(eval "$restart_cmd" 2>&1) ); then
700 | _echo_tabbed "Smartd started."
701 | return 0
702 |
703 | # And output it, if we had nonzero exit code
704 | else
705 | echo
706 | for (( i=0; i<${#result[@]}; i++ )); do
707 | echo "${result[i]}";
708 | done
709 | return 1
710 | fi
711 |
712 | }
713 |
714 | # Enable autostart of smartd
715 | _enable_smartd_autostart()
716 | {
717 | local os=$1
718 | local enable_cmd=''
719 |
720 | case $os in
721 | # systemctl on new OS
722 | Debian[8-9]|Debian1[0-2]|CentOS[7-8]|AlmaLinux[8-9]|Rocky[8-9]|Ubuntu1[6789]|Ubuntu2[0-2] )
723 | enable_cmd='find /usr/lib/systemd/system/ /lib/systemd/system/ /etc/systemd/system/ \
724 | -type f \
725 | \( -name "smartd.service" -or -name "smartmontools.service" \) \
726 | -exec basename \{\} \; |\
727 | uniq |\
728 | xargs systemctl enable'
729 | ;;
730 | # systemctl on Ubuntu 24.04
731 | Ubuntu24 )
732 | enable_cmd='systemctl enable smartmontools.service'
733 | ;;
734 | # chkconfig on CentOS 6
735 | CentOS6 )
736 | enable_cmd='chkconfig smartd on'
737 | ;;
738 | # update-rc.d on sysv/upstart deb-based OS
739 | Debian[6-7]|Ubuntu12|Ubuntu14 )
740 | enable_cmd='update-rc.d smartmontools defaults'
741 | ;;
742 | * )
743 | _echo_tabbed "Don't know how to enable smartd autostart on that OS: ${TXT_YLW}${os}${TXT_RST}"
744 | return 1
745 | ;;
746 | esac
747 |
748 | # Catch error in variable
749 | if IFS=$'\n' result=( $(eval "$enable_cmd" 2>&1) ); then
750 | _echo_tabbed "Smartd autostart enabled."
751 | return 0
752 |
753 | # And output it, if we had nonzero exit code
754 | else
755 | echo
756 | for (( i=0; i<${#result[@]}; i++ )); do
757 | echo "${result[i]}";
758 | done
759 | return 1
760 | fi
761 | }
762 |
763 | # Run monitoring script
764 | _run_script()
765 | {
766 | local bin_path=$1
767 | local script_name=$2
768 | local mode=$3
769 |
770 | if "${bin_path}/${script_name}" --"$mode"; then
771 | return 0
772 | else
773 | _echo_tabbed "Cannot run script in --$mode mode"
774 | return 1
775 | fi
776 | }
777 |
778 |
779 | # Actual installation
780 |
781 | # Detect OS and arch
782 | _detect_os
783 | _detect_arch
784 | echo -e "OS: ${TXT_YLW}${OS} x${ARCH}${TXT_RST}"
785 |
786 | # Set OS type
787 | _select_os_type "$OS"
788 |
789 | # We should randomize run time to prevent ddos attacks to our gates
790 | # Limit random numbers by 59 minutes
791 | ((CRON_MINUTES = RANDOM % 59))
792 |
793 | echo -e "Checking dependencies..."
794 | _install_deps "$OS_TYPE"
795 | _echo_result $?
796 |
797 | echo -e "Checking for hardware RAID..."
798 | _install_raid_tools "$BIN_PATH" "$REPO_PATH" "$ARCH"
799 | _echo_result $?
800 |
801 | echo -e "Installing new smartctl if needed..."
802 | _install_smartctl "$BIN_PATH" "$REPO_PATH" "$ARCH" "$SMARTCTL_STABLE_VERSION" "$SMARTCTL_STABLE_REVISION"
803 | _echo_result $?
804 |
805 | echo -e "Installing monitoring script..."
806 | _install_script "$BIN_PATH" "$REPO_PATH" "$SCRIPT_NAME" "$CRON_FILE" "$CRON_MINUTES" "$CRON_HEADER"
807 | _echo_result $?
808 |
809 | echo -e "Setting smartd..."
810 | _set_smartd "$RAID_TYPE" "$SMARTD_HEADER" "$OS_TYPE" "$SMARTD_SUFFIX"
811 | _echo_result $?
812 |
813 | echo -e "Enabling smartd autostart..."
814 | _enable_smartd_autostart "$OS"
815 | _echo_result $?
816 |
817 | echo -e "Starting smartd..."
818 | _restart_smartd "$OS"
819 | _echo_result $?
820 |
821 | echo -e "Sending data to FASTVPS monitoring server..."
822 | _run_script "$BIN_PATH" "$SCRIPT_NAME" "cron"
823 | _echo_result $?
824 |
825 | echo -e "Current storage status:"
826 | _run_script "$BIN_PATH" "$SCRIPT_NAME" "detect"
827 | echo
828 |
--------------------------------------------------------------------------------
/raid_monitoring_tools/arcconf32_old:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/arcconf32_old
--------------------------------------------------------------------------------
/raid_monitoring_tools/arcconf64_old:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/arcconf64_old
--------------------------------------------------------------------------------
/raid_monitoring_tools/arcconf_v2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/arcconf_v2
--------------------------------------------------------------------------------
/raid_monitoring_tools/arcconf_v3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/arcconf_v3
--------------------------------------------------------------------------------
/raid_monitoring_tools/megacli32:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/megacli32
--------------------------------------------------------------------------------
/raid_monitoring_tools/megacli64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/megacli64
--------------------------------------------------------------------------------
/raid_monitoring_tools/smartctl32:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/smartctl32
--------------------------------------------------------------------------------
/raid_monitoring_tools/smartctl64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FastVPSEestiOu/storage-system-monitoring/a56608b4407a0e2451296bab5dd93251ffec50a7/raid_monitoring_tools/smartctl64
--------------------------------------------------------------------------------
/storage_system_fastvps_monitoring.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | =description
3 | Authors:
4 | Alexander Kaidalov
5 | Pavel Odintsov
6 | License: GPLv2
7 | =cut
8 |
9 | # List of modules.
10 | use strict;
11 | use warnings;
12 |
13 | use POSIX qw(locale_h);
14 |
15 | use JSON;
16 | use LWP::UserAgent;
17 | use Data::Dumper;
18 |
19 | use Getopt::Long;
20 |
21 | # Configuration.
22 | my $VERSION = '1.6';
23 | my $PATH = $ENV{'PATH'};
24 | my $LANG = 'en_US';
25 | my $API_URL = 'https://fastcheck24.com/api/server-state/storage';
26 |
27 | # Diagnostic utilities
28 | my $smartctl;
29 | my $mdadm;
30 | my $megacli;
31 | my $arcconf;
32 |
33 | # List of possible raid controllers.
34 | my $adaptec_needed = 0;
35 | my $lsi_needed = 0;
36 | my $mdadm_needed = 0;
37 |
38 | # Centos && Debian uses same path
39 | my $sysfs_block_path = '/sys/block';
40 |
41 | # Options
42 | my $only_detect_drives;
43 | my $cron_run;
44 | my $json;
45 | my $help;
46 |
47 | # Set locale
48 | $ENV{LC_ALL} = "$LANG";
49 | setlocale(LC_ALL, "$LANG");
50 |
51 | # List of device's major_id for ignore.
52 | my @major_blacklist = (
53 | 1, # это ram устройства
54 | 2, # Floppy disks
55 | 7, # это loop устройства
56 | 11, # CD-ROM
57 | 43, # nbd http://en.wikipedia.org/wiki/Network_block_device
58 | 182, # это openvz ploop диск
59 | 230, # zvol
60 | 253, # device-mapper, но на Citrix XenServer это tapdev
61 | 252, # device-mapper на Citrix XenServer
62 | );
63 |
64 | my $options = "Use options: [ --detect ] [ --cron ] [ --help ]
65 | \t-d|--detect - Print list of drives. The result will not be sent to API.
66 | \t-j|--json - Print smart data for found drives in json. The result will not be sent to API.
67 | \t-c|--cron - Print smart data for found drives. The result will be sent to API. Must be set in crontab task.
68 | \t-h|--help - show this message.\n";
69 |
70 | GetOptions (
71 | "d|detect" => \$only_detect_drives,
72 | "j|json" => \$json,
73 | "c|cron" => \$cron_run,
74 | "h|help" => \$help,
75 | ) or die "Error in command line arguments!\n$options\n";
76 |
77 | if ( $help ) {
78 | print $options,"\n";
79 | exit 0;
80 | }
81 |
82 | my $user_id = $<;
83 | if ( $user_id != 0 ) {
84 | die "This program can only be run under root.\n";
85 | }
86 |
87 | my $os = get_os();
88 | my $ipaddress = `ip r get 8.8.8.8 | awk 'NR==1 {print \$7}' 2>&1`;
89 |
90 | # Get list of devices.
91 | my @disks = find_disks_without_parted();
92 |
93 | # Сheck the availability of utilities.
94 | check_disk_utilities(@disks);
95 |
96 | # Get smart result for found devices.
97 | @disks = diag_disks(@disks);
98 |
99 | # If a "--detect" option is specified .
100 | if ( $only_detect_drives ) {
101 | for my $storage ( @disks ) {
102 | # The "hard_disk" does not have a "$storage->{status}"
103 | if ( $storage->{'type'} eq 'hard_disk' ) {
104 | print "Device $storage->{device_name} with type: $storage->{type} model: $storage->{model} detected\n";
105 | } else {
106 | print "Device $storage->{device_name} with type: $storage->{type} model: $storage->{model} in state: $storage->{status} detected\n";
107 | }
108 | }
109 | if ( scalar @disks == 0 ) {
110 | print "I can't find any disk devices. I suppose bug on this platform :( ";
111 | }
112 | exit (0);
113 | }
114 |
115 | # If a "--cron" option is specified .
116 | if ( $cron_run ) {
117 | if ( !send_disks_results(@disks) ) {
118 | print "Failed to send storage monitoring data to FastVPS";
119 | exit(1);
120 | }
121 | exit 0;
122 | }
123 |
124 | if ( $json ) {
125 | print_disks_results(@disks);
126 | exit 0;
127 | }
128 |
129 | if ( !$only_detect_drives && !$cron_run ) {
130 | print "This information was gathered and will be sent to FastVPS:\n";
131 | print "Disks found: " . (scalar @disks) . "\n\n";
132 |
133 | for my $storage ( @disks ) {
134 | print $storage->{device_name} . " is " . $storage->{'type'} . " Diagnostic data:\n";
135 | print $storage->{'diag'} . "\n\n";
136 | }
137 | }
138 |
139 | # Functions
140 | sub which {
141 | my $prog_name = shift;
142 | my $path_string = shift;
143 |
144 | my @path_array = split /:/, $PATH;
145 |
146 | for my $path ( @path_array ) {
147 | if ( -x "$path/$prog_name" ) {
148 | return "$path/$prog_name";
149 | }
150 | }
151 | return 0;
152 | }
153 |
154 | # Deleting space symbols.
155 | sub rtrim {
156 | my $string = shift;
157 |
158 | $string =~ s/\s+$//g;
159 | return $string;
160 | }
161 |
162 | # Get full path to device.
163 | sub get_device_path {
164 | my $name = shift;
165 |
166 | return "/dev/$name";
167 | }
168 |
169 | # Get major_id for block devices in Linux.
170 | sub get_major {
171 | my $device = shift;
172 |
173 | my $device_path = get_device_path($device);
174 | my $major = '';
175 |
176 | if ( -e $device_path ) {
177 | my $rdev = (stat $device_path)[6];
178 | # https://github.com/quattor/LC/blob/master/src/main/perl/Stat.pm
179 | $major = ($rdev >> 8) & 0xFF;
180 | return $major;
181 | } else {
182 | # Для dm устройств и /dev/t у нас нету псевдо-устройст в /dev, поэтому мы можем попробовать получить major из sysfs
183 | my $dev_info = file_get_contents("/sys/block/$device/dev");
184 | if ( $dev_info =~ /(\d+):\d+/ ) {
185 | $major = $1;
186 | return $major;
187 | } else {
188 | return '';
189 | }
190 | }
191 | }
192 |
193 | sub in_array {
194 | my ($elem, @array) = @_;
195 |
196 | return scalar grep { $elem eq $_ } @array;
197 | }
198 |
199 | sub file_get_contents {
200 | my $path = shift;
201 |
202 | open my $fl, "<", $path or die "Can't open file";
203 | my $data = join '', <$fl>;
204 | chomp $data;
205 | close $fl;
206 | return $data;
207 | }
208 |
209 | # Get name to device vendor
210 | sub get_device_vendor {
211 | my $device_name = shift;
212 | my $vendor_path = "$sysfs_block_path/$device_name/device/vendor";
213 |
214 | if ( -e $vendor_path ) {
215 | my $vendor_raw = file_get_contents($vendor_path);
216 | $vendor_raw = lc($vendor_raw);
217 | # remove trailing spaces
218 | $vendor_raw =~ s/\s+$//g;
219 | return $vendor_raw;
220 | } else {
221 | return "unknown";
222 | }
223 | }
224 |
225 | # Получаем модуль устройства
226 | sub get_device_model {
227 | my $device_name = shift;
228 | my $model_path = "$sysfs_block_path/$device_name/device/model";
229 |
230 | if ( -e $model_path ) {
231 | my $model_raw = file_get_contents($model_path);
232 | $model_raw = lc($model_raw);
233 | # remove trailing spaces
234 | $model_raw =~ s/\s+$//g;
235 | return $model_raw;
236 | } else {
237 | return "unknown";
238 | }
239 | }
240 |
241 | sub get_device_size {
242 | my $device_name = shift;
243 | my $size_path = "$sysfs_block_path/$device_name/size";
244 |
245 | if ( -e $size_path ) {
246 | my $size_in_blocks = file_get_contents($size_path);
247 | # Convert to bytes.
248 | my $size_in_bytes = $size_in_blocks << 9;
249 | my $size_in_gbytes = int($size_in_bytes/1024**3);
250 |
251 | return "${size_in_gbytes}GB";
252 | } else {
253 | return "unknown";
254 | }
255 | }
256 |
257 | # Find block devices. Pareted is not used.
258 | sub find_disks_without_parted {
259 | opendir my $block_devices, $sysfs_block_path or die "Can't open path $sysfs_block_path";
260 |
261 | my @disks = ();
262 | while ( my $block_device = readdir($block_devices) ) {
263 | # skip . and ..
264 | if ( $block_device =~ m/^\.+$/ ) {
265 | next;
266 | }
267 | # skip device mapper fake devices
268 | if ( $block_device =~ m/^dm-\d+/ ) {
269 | next;
270 | }
271 |
272 | # Skip devices if they have major_id from @major_blacklist.
273 | my $major = get_major($block_device);
274 | if ( in_array($major, @major_blacklist) ) {
275 | next;
276 | }
277 |
278 | my $vendor = get_device_vendor($block_device);
279 | my $model = get_device_model($block_device);
280 |
281 | $model = "$vendor $model";
282 |
283 | # Skip idrac devices.
284 | if ( $model =~ m/^idrac\s+virtual\s+\w+$/ ) {
285 | next;
286 | }
287 |
288 | # Skip ipmi devices.
289 | if ( $model =~ m/^ipmi\s+virtual\s+\w+$/ ) {
290 | next;
291 | }
292 |
293 | # Skip ami virtual devices.
294 | if ( $model =~ m/^ami\s+virtual\s+\w+$/ ) {
295 | next;
296 | }
297 |
298 | # Skip qemu devices.
299 | if ( $model =~ m/.+qemu.+/ ) {
300 | next;
301 | }
302 |
303 | # Skip JetFlash devices
304 | if ( $model =~ m/^jetflash\s+transcend\s+\w+$/ ) {
305 | next;
306 | }
307 |
308 | my $device_size = get_device_size($block_device);
309 | my $device_name = get_device_path($block_device);
310 |
311 | # detect type ( raid or disk )
312 | my $type = 'disk';
313 | my $is_raid = '';
314 | my $raid_level = '';
315 |
316 | # adaptec
317 | if ( $model =~ m/adaptec/i or $model =~ m/ASR8405/i ) {
318 | $model = 'adaptec';
319 | $is_raid = 1;
320 | }
321 |
322 | # Linux MD raid ( Soft RAID )
323 | if ( $device_name =~ m/\/md\d+/ ) {
324 | $model = 'md';
325 | $is_raid = 1;
326 |
327 | $raid_level = file_get_contents("/sys/block/$block_device/md/level");
328 | }
329 |
330 | # LSI ( 3ware ) / DELL PERC ( LSI chips also )
331 | if ( $model =~ m/lsi/i or $model =~ m/PERC/i or $model =~ m/RS3DC080/i ) {
332 | $model = 'lsi';
333 | $is_raid = 1;
334 | }
335 |
336 | # add to list
337 | my $tmp_disk = {
338 | "device_name" => $device_name,
339 | "size" => $device_size,
340 | "model" => $model,
341 | "type" => ($is_raid ? 'raid' : 'hard_disk'),
342 | };
343 |
344 | push @disks, $tmp_disk;
345 | }
346 |
347 | return @disks;
348 | }
349 |
350 | # Check diagnostic utilities availability
351 | sub check_disk_utilities {
352 | my (@disks) = @_;
353 |
354 | for my $storage ( @disks ) {
355 | # Adaptec
356 | if ( $storage->{model} eq 'adaptec' ) {
357 | $adaptec_needed = 1;
358 |
359 | $arcconf = which('arcconf', $PATH);
360 | unless ( $arcconf ) {
361 | die "Adaptec utility not found. Please, install Adaptech raid management utility.\n";
362 | }
363 | }
364 | # LSI
365 | if ( $storage->{model} eq "lsi" ) {
366 | $lsi_needed = 1;
367 |
368 | $megacli = which('megacli', $PATH);
369 | unless ( $megacli ) {
370 | die "Megacli not found. Please, install LSI MegaCli raid management utility into.\n";
371 | }
372 | }
373 | # Mdadm
374 | if ( $storage->{model} eq "md" ) {
375 | $mdadm_needed = 1;
376 |
377 | $mdadm = which('mdadm', $PATH);
378 | unless ( $mdadm ) {
379 | die "mdadm not found. Please, install mdadm.\n";
380 | }
381 | }
382 | }
383 |
384 | $smartctl = which('smartctl', $PATH);
385 | unless ( $smartctl ) {
386 | die "smartctl not found. Please, install smartctl.\n"
387 | }
388 | }
389 |
390 | # Get status from adaptec logical devices.
391 | sub extract_adaptec_status {
392 | my $data = shift;
393 |
394 | my @data_as_array = split "\n", $data;
395 | my $status = 'unknown';
396 |
397 | for my $line ( @data_as_array ) {
398 | chomp $line;
399 | if ( $line =~ /^\s+Status of logical device\s+:\s+(\w+)$/i ) {
400 | $status = lc(rtrim($1));
401 | }
402 | }
403 | return $status;
404 | }
405 |
406 | sub extract_lsi_status {
407 | my $data = shift;
408 |
409 | my @data_as_array = split "\n", $data;
410 | my $status = 'unknown';
411 |
412 | for my $line ( @data_as_array ) {
413 | chomp $line;
414 | if ( $line =~ /^State\s+:\s+(\w+)/i ) {
415 | $status = lc(rtrim($1));
416 | }
417 | }
418 | return $status;
419 | }
420 |
421 | # Get status from mdadm.
422 | sub extract_mdadm_raid_status {
423 | my $data = shift;
424 |
425 | my @data_as_array = split "\n", $data;
426 | my $status = 'unknown';
427 |
428 | for my $line ( @data_as_array ) {
429 | chomp $line;
430 | if ( $line =~ /^\s+State\s+:\s+(.+)$/ ) {
431 | $status = lc(rtrim($1));
432 | }
433 | }
434 | return $status;
435 | }
436 |
437 | # Run disgnostic utility for each disk
438 | sub diag_disks {
439 | my (@disks) = @_;
440 |
441 | my @result_disks = ();
442 | my @lsi_ld_all;
443 | my @adaptec_ld_all;
444 | my @hwraid_disk_smart;
445 | my $adapctec_device_quantity = 0;
446 |
447 | if ( $lsi_needed ) {
448 | my $lsi_ld_all_res = `$megacli -LDInfo -Lall -Aall 2>&1`;
449 | $lsi_ld_all_res =~ s/^\n\n//;
450 | @lsi_ld_all = split /\n\n\n/, $lsi_ld_all_res;
451 | }
452 |
453 | if ( $adaptec_needed ) {
454 | my $adaptec_ld_all_res = `$arcconf getconfig 1 ld 2>&1`;
455 | @adaptec_ld_all = split /\n\n/, $adaptec_ld_all_res;
456 | $adapctec_device_quantity = @{[$adaptec_ld_all_res =~ /Logical device number/gi]};
457 | }
458 |
459 | foreach my $storage ( sort { $a->{'device_name'} cmp $b->{'device_name'} } @disks ) {
460 | my $device_name = $storage->{device_name};
461 | my $type = $storage->{type};
462 | my $model = $storage->{model};
463 | my $res = '';
464 | my $cmd = '';
465 | # Get the status of a raid array.
466 | my $storage_status = 'undefined';
467 |
468 | if ( $type eq 'raid' ) {
469 | # adaptec
470 | if ( $model eq "adaptec" ) {
471 | if ( scalar @adaptec_ld_all > 0 ) {
472 | $res = shift @adaptec_ld_all;
473 | } else {
474 | $res = "";
475 | }
476 | $storage_status = extract_adaptec_status($res);
477 | }
478 | # md
479 | if ( $model eq "md" ) {
480 | $cmd = "$mdadm --detail $device_name";
481 | $res = `$cmd 2>&1`;
482 | # Get status from mdadm.
483 | $storage_status = extract_mdadm_raid_status($res);
484 | }
485 |
486 | # lsi ( 3ware )
487 | if ( $model eq "lsi" ) {
488 | if ( scalar @lsi_ld_all > 0 ) {
489 | $res = shift @lsi_ld_all;
490 | } else {
491 | $res = "";
492 | }
493 | $storage_status = extract_lsi_status($res);
494 | }
495 | } elsif ( $type eq 'hard_disk' ) {
496 | if ( $device_name =~ /nvme/i ) {
497 | $cmd = "$smartctl -d nvme,0xffffffff --all $device_name";
498 | } else {
499 | $cmd = "$smartctl --all $device_name";
500 | }
501 | $res = `$cmd 2>&1`;
502 | } else {
503 | warn "Unexpected type";
504 | $cmd = '';
505 | }
506 |
507 | $storage->{'status'} = $storage_status;
508 | $storage->{'diag'} = $res;
509 |
510 | push @result_disks, $storage;
511 | }
512 |
513 | # Get smart data for drives from a hardware raid.
514 | # If raid model is adaptec, additionally specify quantity of virtual arrays.
515 | if ( $lsi_needed ) {
516 | @hwraid_disk_smart = get_smart_disk('lsi');
517 | push @result_disks, @hwraid_disk_smart;
518 | }
519 | if ( $adaptec_needed ) {
520 | @hwraid_disk_smart = get_smart_disk('adaptec', $adapctec_device_quantity);
521 | push @result_disks, @hwraid_disk_smart;
522 | }
523 |
524 | return @result_disks;
525 | }
526 |
527 | sub get_smart_disk{
528 | my $raid_control = shift;
529 | my $adapctec_device_quantity = shift;
530 |
531 | my %pd_type;
532 | my %pd_smart;
533 | my @disk_hwraid_type_number;
534 | my @disk_raid_list;
535 | my $smart_all_result;
536 | my ($device_name, $device_size, $model, $diag);
537 |
538 | # Получаем номера дисков в raid, и определяем SAS он или нет.
539 | if ( $raid_control =~ /lsi/ ) {
540 | my $res = `$megacli -LdpdInfo -a0 -NoLog|grep -E 'Device Id:|Inquiry Data:|PD Type:' `;
541 | $res =~ s/\nPD Type/ PD Type/g;
542 | $res =~ s/\nInquiry/ Inquiry/g;
543 |
544 | my $smart_result;
545 |
546 | for ( split(/\n/,$res) ) {
547 | if (/ SSD /) {
548 | s/ SATA / SSD /;
549 | }
550 | chomp($_);
551 | /PD Type: ([A-Z]{3,4}) /;
552 | my $pd = $1;
553 | s/Device Id: //;
554 | s/ PD Type.*//;
555 |
556 | if ( $pd =~ /SAS/ ) {
557 | $smart_result = `$smartctl -a -d megaraid,$_ /dev/sda`;
558 | } else {
559 | $smart_result = `$smartctl -a -d sat+megaraid,$_ /dev/sda`;
560 | }
561 |
562 | push @disk_hwraid_type_number,$_;
563 | $pd_type{$_} = $pd;
564 | $pd_smart{$_} = $smart_result;
565 | }
566 |
567 | } elsif ( $raid_control =~ /adaptec/ ) {
568 | my $disk_number;
569 | my $disk_channel;
570 | my $disk_connector;
571 | my $smart_result;
572 |
573 | my $res=`arcconf getconfig 1 pd | grep -E "Device #|Transfer Speed|SSD|SES2|Channel|Location" | sed 's/ //g'`;
574 | $res =~ s/\n ?(Transfer|SSD|Type|Reported)/ $1/g;
575 | for( split(/\n/,$res) ) {
576 | # Skip SES2 devices
577 | if ( / SES2/ ) {
578 | next;
579 | }
580 | if ( / Model|Channel #\d:/ ) {
581 | next;
582 | }
583 | if ( / Yes / ) {
584 | s/ SATA / SSD /;
585 | }
586 | chomp($_);
587 |
588 | /Device #(\d+)/;
589 | $disk_number = $1;
590 |
591 | /Reported Channel,Device\(T:L\) : (\d+)/;
592 | $disk_channel = $1;
593 |
594 | /Reported Location: Connector (\d)/;
595 | $disk_connector = $1;
596 |
597 | if ( /Device #(\d+) Transfer Speed : (\w+) / ) {
598 | $pd_type{$disk_number} = $2;
599 | } else {
600 | $pd_type{$disk_number} = "SAS";
601 | }
602 |
603 | push @disk_hwraid_type_number,$disk_number;
604 | $pd_smart{$disk_number} = get_adaptec_disk_smart_info($disk_number, $disk_channel, $disk_connector, $pd_type{$disk_number});
605 | }
606 | } else {
607 | warn("It is not LSI or Adaptec - we didnt know to do!\n");
608 | }
609 | # Пытаемся получить smart-ы для каждого найденного в raid-е диска.
610 | for my $disk_number ( @disk_hwraid_type_number ) {
611 | unless ( $pd_smart{$disk_number} ) {
612 | next;
613 | }
614 | $device_name = $disk_number;
615 | $model = $raid_control;
616 | $diag = $pd_smart{$disk_number};
617 | # Данный хэш нужен, чтобы данные о каждом диске, добавлялись в общий массив.
618 | my $tmp_disk = {
619 | "device_name" => $device_name,
620 | "size" => 'undefined',
621 | "model" => $model,
622 | "type" => 'hard_disk',
623 | "status" => 'undefined',
624 | "diag" => $diag,
625 | };
626 | push @disk_raid_list, $tmp_disk;
627 | }
628 | return @disk_raid_list;
629 | }
630 |
631 | sub get_adaptec_disk_smart_info {
632 | my $disk_number = shift;
633 | my $disk_channel = shift;
634 | my $disk_connector = shift;
635 | my $disk_type = shift;
636 |
637 | my $smart_result;
638 |
639 | my $adapctec_device_quantity = `$arcconf getconfig 1 ld 2>&1 | grep -ci 'Logical Device number'`;
640 | my $sg_number=$disk_number+$adapctec_device_quantity;
641 |
642 | # Using cciss mode for new Adaptec with smartpqi driver
643 | if ( -d '/sys/bus/pci/drivers/smartpqi' ) {
644 | my $smart_info = `$smartctl -i -d cciss,$disk_number /dev/sda`;
645 | if ( $smart_info =~ /Product:\s+LogicalDrv/ ) {
646 | return 0;
647 | } else {
648 | $smart_result = `$smartctl -a -d cciss,$disk_number /dev/sda`;
649 | }
650 | # And using sg mode for Adaptec with aacraid driver
651 | } elsif ( -d '/sys/bus/pci/drivers/aacraid' ) {
652 | if ( -c "/dev/sg$sg_number" ) {
653 | my $smart_info = `$smartctl -i /dev/sg$sg_number`;
654 | if ( $smart_info =~ /Product:\s+LogicalDrv/ ) {
655 | return 0;
656 | } else {
657 | if ( $disk_type =~ /SAS/ ) {
658 | $smart_result = `$smartctl -a /dev/sg$sg_number`;
659 | } else {
660 | $smart_result = `$smartctl -a -d sat /dev/sg$sg_number`;
661 | }
662 | }
663 | } else {
664 | $smart_result = `$smartctl -d aacraid,$disk_channel,$disk_connector,$disk_number -a /dev/sda`;
665 | }
666 | }
667 | return "$smart_result\n";
668 | }
669 |
670 | # Send disks diag results
671 | sub send_disks_results {
672 | my (@disks) = @_;
673 |
674 | my $request_data = {
675 | 'storage_devices' => \@disks,
676 | 'version' => $VERSION,
677 | };
678 |
679 | # Set request params
680 | my $ua = LWP::UserAgent->new();
681 |
682 | # Add ip in headers
683 | $ua->default_header('FASTVPS-IP' => "$ipaddress");
684 |
685 | # Add script version and OS name to User-Agent
686 | $ua->agent("storage-system-monitoring-v${VERSION}-${os}");
687 |
688 | # Allow redirects for POST requests
689 | push @{ $ua->requests_redirectable }, 'POST';
690 |
691 | my $res = $ua->post($API_URL, Content => encode_json($request_data));
692 |
693 | if ( $res->is_success ) {
694 | #print "Data sent successfully\n";
695 | exit 0;
696 | } else {
697 | warn "Can't sent data to collector: " . $res->status_line . "\n";
698 | exit 1;
699 | }
700 | }
701 |
702 | sub get_os {
703 | my @supported_os_list = (
704 | 'almalinux',
705 | 'rocky',
706 | 'centos',
707 | 'ubuntu',
708 | 'debian',
709 | );
710 |
711 | my $redhat_release = '/etc/redhat-release';
712 | my $os_release = '/etc/os-release';
713 | my $issue_file = '/etc/issue';
714 | my $release_file;
715 |
716 | if ( -e $redhat_release ) {
717 | $release_file = $redhat_release;
718 | } elsif ( -e $os_release ) {
719 | $release_file = $os_release;
720 | } elsif ( -e $issue_file ) {
721 | $release_file = $issue_file;
722 | } else {
723 | return 'unknown';
724 | }
725 |
726 | my @release_file_content = read_file($release_file);
727 | unless ( @release_file_content ) {
728 | return 0;
729 | }
730 |
731 | for my $os ( @supported_os_list ) {
732 | if ( find_string("$os", \@release_file_content) ) {
733 | return "$os";
734 | } else {
735 | next;
736 | }
737 | }
738 |
739 | return 'unknown';
740 | }
741 |
742 | sub find_string {
743 | my $regexp = shift;
744 | my $file = shift;
745 |
746 | for my $string ( @$file ) {
747 | if ( $string =~ /$regexp/i ) {
748 | return $string;
749 | } else {
750 | next;
751 | }
752 | }
753 |
754 | return 0;
755 | }
756 |
757 | sub read_file {
758 | my $file_path = shift;
759 |
760 | my @file_content;
761 |
762 | if ( open my $fh,"<",$file_path ) {
763 | @file_content = <$fh>;
764 | close $fh;
765 | chomp @file_content;
766 | return @file_content;
767 | } else {
768 | return 0;
769 | }
770 | }
771 |
772 | sub print_disks_results {
773 | my (@disks) = @_;
774 |
775 | my $request_data = {
776 | 'storage_devices' => \@disks,
777 | 'version' => $VERSION,
778 | };
779 | print encode_json($request_data), "\n";
780 | }
781 |
--------------------------------------------------------------------------------