PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/tier1adv/bin
Viewing File: /opt/tier1adv/bin/server_overview
#!/opt/support/venv/bin/python3 """Displays an overview of the server configuration""" # Original py2 version written by Joe C <joe@cartonia.net> import argparse from pathlib import Path import os import re import platform import pwd from glob import glob import shlex import subprocess import sys from collections.abc import Generator import distro import psutil import pymysql import rads sys.path.insert(0, '/opt/support/lib') from output import header as header_print, warn from server import ROLE from arg_types import any_cpuser_arg, cpuser_safe_arg header = lambda x: header_print( f" {x} ".center(80, '='), color=rads.color.blue, flush=True, ) MEGACLI = '/opt/MegaRAID/MegaCli/MegaCli64' if Path('/etc/cpanel/ea4/is_ea4').exists(): HTTPD_CONF = '/etc/apache2/conf/httpd.conf' HTTPD_BIN = '/usr/sbin/httpd' else: HTTPD_CONF = '/usr/local/apache/conf/httpd.conf' HTTPD_BIN = '/usr/local/apache/bin/httpd' IGNORE_DIRS = ['mail', 'etc'] CONFIG_MATCHES = [ 'wp-config.php', 'configuration.php', 'local.xml', 'config.php', 'core.php', 'settings.php', 'settings.inc.php', 'header.tpl', 'general.php', ] def parse_args() -> list[str]: """Parse CLI arguments""" parser = argparse.ArgumentParser(description=__doc__) group = parser.add_mutually_exclusive_group() group.add_argument( "-u", "--users", default=[], nargs='*', type=cpuser_safe_arg if ROLE else any_cpuser_arg, help="list of users to analyze software for", ) if not ROLE: # v/ded group.add_argument( '-a', '--all-users', action='store_true', help="Analyze software for all users", ) args = parser.parse_args() if getattr(args, 'all_users', False): args.users = rads.all_cpusers() return args.users def show_server_ips(): try: print( 'cPanel main IP:', Path('/var/cpanel/mainip').read_text(encoding='utf-8'), ) except FileNotFoundError: pass try: print( 'cPanel shared IP(s):', Path('/var/cpanel/mainips/root').read_text(encoding='utf-8'), ) except FileNotFoundError: try: with open('/etc/wwwacct.conf', encoding='utf-8') as handle: for line in handle: if line.startswith('ADDR '): print('cPanel shared IP:', line.split()[1]) return except (FileNotFoundError, IndexError): pass if distro.major_version() == '6': cmd = ['ip', 'address', 'show', 'scope', 'global'] else: cmd = ['ip', '-c', 'address', 'show', 'scope', 'global'] print('Interfaces:') subprocess.call(cmd) def cpu_info(): print('CPU cores (logical):', psutil.cpu_count(logical=True)) print('CPU cores (physical):', psutil.cpu_count(logical=False)) cpu_model = 'UNKNOWN' with open('/proc/cpuinfo', encoding='utf-8') as cpuinfo: for line in cpuinfo: if line.startswith('model name'): cpu_model = line.split(':', maxsplit=1)[1].strip() print('CPU Model:', cpu_model) def mem_info(): mem = psutil.virtual_memory() for attr in 'used free available total'.split(): print( f"Memory {attr.title(): <9}: {int(getattr(mem, attr) / 2**20): >6} MB" ) def display_server_specs(): header("Server specs") print(f"Hostname: {platform.node()}") show_server_ips() print("kernel:", end=' ', flush=True) subprocess.call(['uname', '-r']) cpu_info() mem_info() def out_lines(cmd: list[str], stderr=subprocess.DEVNULL) -> list[str]: try: return subprocess.check_output( cmd, encoding='utf-8', stderr=stderr ).splitlines() except (FileNotFoundError, subprocess.CalledProcessError): return [] def display_disk_info(): header("Disk information") subprocess.call(['df', '-h']) try: with open('/proc/scsi/sg/device_strs', encoding='utf-8') as handle: unique = {x for x in handle if 'PERC' in x or 'Virtual Disk' in x} for line in unique: print(line, end='') except FileNotFoundError: pass for line in out_lines(['lspci', '-vvv']): if re.search('RAID', line, re.I): print(re.sub(r'^[\s]+Subsystem: ', '', line)) found_raid = False for line in out_lines(['sas2ircu', '0', 'DISPLAY']): if re.search(r'(RAID level|Volume Name)', line): print(re.sub(r'^[\s]+', '', line)) found_raid = True for line in out_lines(['sas2ircu', '0', 'STATUS']): if 'Volume state' in line: print(re.sub(r'^[\s]+', '', line)) if not found_raid: found_raid = check_megacli() if not found_raid: for line in out_lines(['/usr/bin/lsiutil', '-s']): if re.search(r'(Vendor|Disk)', line): print(line) found_raid = True if not found_raid: print("RAID not found") def check_megacli() -> bool: found_raid = False for line in out_lines([MEGACLI, '-AdpAllInfo', '-aALL', '-NoLog']): if 'Product Name' in line: print(line) found_raid = True for line in out_lines([MEGACLI, '-AdpBbuCmd', '-aALL', '-NoLog']): if re.match(r'(?:Relative State of Charge|Charger Status)', line): print(line) for line in out_lines([MEGACLI, '-PDList', '-aALL', '-NoLog']): if 'Enclosure Device ID' in line: enc_id = re.sub(r'Enclosure Device ID: ', '', line) elif 'Slot Number' in line: slot_id = re.sub(r'Slot Number: ', '', line) elif 'Device Id' in line: dev_id = re.sub(r'Device Id: ', '', line) elif 'PD Type' in line: pd_type = re.sub(r'PD Type: ', '', line) elif 'Raw Size' in line: raw_size = re.sub(r'Raw Size: ', '', line) raw_size = re.sub(r' \[.*$', '', raw_size) elif 'Inquiry Data' in line: drive = re.sub(r'Inquiry Data: ', '', line) if 'Foreign State' in line: print(f"[{enc_id}:{slot_id}:{dev_id}] {pd_type} {raw_size} {drive}") found_raid = True for line in out_lines([MEGACLI, '-LDInfo', '-Lall', '-aALL', '-NoLog']): if re.search( r'^(RAID Level:|Size:|State:|Number Of Drives:|Current Cache Policy:)', line, ): if re.search(r'^(Size:|Number Of Drives:)', line): line = re.sub(r':', ': ', line) found_raid = True print(line) return found_raid def display_apache_info(): header("Apache information") try: version = subprocess.check_output([HTTPD_BIN, '-v'], encoding='utf-8') print(version.replace('\n', ' ')) except FileNotFoundError: warn('Could not find Apache binary at', HTTPD_BIN) except subprocess.CalledProcessError: pass for line in out_lines([HTTPD_BIN, '-M'], stderr=None): if 'mpm_prefork_module' in line: print("Using MPM Prefork") elif 'mpm_worker_module' in line: print("Using MPM Worker") try: with open(HTTPD_CONF, encoding='utf-8') as conf: for line in conf: if re.search(r'MaxClients|ServerLimit|KeepAlive', line): print(line, end='') except FileNotFoundError: warn('Could not find Apache configuration at', HTTPD_CONF) def display_php_info(): header("PHP information") try: subprocess.call(['/usr/local/bin/php', '-v']) except FileNotFoundError: pass try: subprocess.call( ['/usr/local/cpanel/bin/rebuild_phpconf', '--current'], stderr=subprocess.DEVNULL, ) except FileNotFoundError: warn("Could not find /usr/local/cpanel/bin/rebuild_phpconf") def display_mysql_info(): header("MySQL information") try: subprocess.call(['mysql', '-V']) except FileNotFoundError: warn('Could not find mysql client') return try: subprocess.call(['mysqladmin', 'status']) except FileNotFoundError: warn('could not find mysqladmin client') return conn_kargs = { 'host': 'localhost', 'read_default_file': '/root/.my.cnf', 'database': 'mysql', } try: with pymysql.connect(**conn_kargs) as conn, conn.cursor() as cur: cur.execute("SHOW VARIABLES LIKE 'max_%conn%'") for row in cur.fetchall(): print(*row, sep=' = ') except pymysql.Error as exc: warn(str(exc)) def display_cpanel_info(): header("cPanel information") try: ver = Path('/usr/local/cpanel/version').read_text(encoding='utf-8') except FileNotFoundError: warn('Could not find cPanel version') else: print('cPanel version:', ver.strip()) try: user_count = len(rads.all_cpusers()) except (FileNotFoundError, rads.CpuserError) as exc: warn(str(exc)) else: print('cPanel accounts:', user_count) def check_optimizations(): header("Optimizations") for line in out_lines(['/usr/local/bin/php', '-m']): if re.match(r'apc$', line): print("Found PHP PECL: apc") if re.match(r'memcache$', line): print("Found PHP PECL: memcache") if not Path('/usr/local/bin/memcached').is_file(): return try: memcached = out_lines(['/usr/local/bin/memcached', '-i'])[0] except IndexError: warn("Found '/usr/local/bin/memcached' but could not determine version") else: print('Found', memcached) def check_mds_exists() -> bool: """Check if /mds exists and has contents""" try: return any(os.scandir('/mds')) except (NotADirectoryError, FileNotFoundError): return False def check_recent_high_loads(): header("Recent high 1-minute load averages") if not check_sa(): return data = { 'file': "File", 'time': "Time", 'runq': "Run Queue", 'plist': "Task List", 'one': "Load Avg 1", 'five': "Load Avg 5", 'fifteen': "Load Avg 15", } template = ( "%(file)20s%(time)15s%(runq)14s%(plist)14s" "%(one)14s%(five)14s%(fifteen)14s" ) print(template % data) for file_name, data in get_highest_sa_load_entries(): if file_name and data: data['file'] = file_name print(template % data) def get_highest_sa_load_entries() -> list[tuple[str, dict]]: sar_entry = re.compile( r"(?P<time>\d{2}:\d{2}:\d{2}\s(?:A|P)M)\s+" r"(?P<runq>\d+)\s+(?P<plist>\d+)\s+" r"(?P<one>[\d\.]+)\s+(?P<five>[\d\.]+)\s+(?P<fifteen>[\d\.]+)" ) highest_entries = [] # iterating over sa files, newest first for sa_file in sorted( glob('/var/log/sa/sa[0-9][0-9]'), key=lambda x: os.stat(x).st_mtime, ): highest = 0.0 high_data = None for line in iter_sar('-q', '-f', sa_file): if match := sar_entry.match(line): data = match.groupdict() if float(data['one']) > highest: highest = float(data['one']) high_data = data if high_data: highest_entries.append((sa_file, high_data)) return highest_entries def check_sa() -> bool: if not Path('/var/log/sa').is_dir(): warn("Could not find sysstat log directory /var/log/sa") return False if not any(os.scandir('/var/log/sa')): warn('/var/log/sa is empty') return False return True def iter_sar(*args) -> Generator[str, None, None]: cmd = ['sar'] cmd.extend(args) try: with subprocess.Popen( cmd, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) as sar_proc: yield from sar_proc.stdout except FileNotFoundError: warn('could not find sar') except subprocess.CalledProcessError: warn('error running', shlex.join(cmd)) def check_recent_high_mem(): header("Recent high mem usage averages") if not check_sa(): return colnames = [] for line in iter_sar('-r'): if 'mem' in line: colnames = line.split()[2::] break if not colnames: warn('could not read columns from sar -r') return for index, value in enumerate(colnames): colnames[index] = re.sub(r'%', 'p', value) data = {'file': "File", 'time': "Time"} template_code = "%(file)20s%(time)15s" for item in colnames: data[str(item)] = str(item) template_code += "%(" + str(item) + ")14s" template = template_code del template_code print(template % data) for file_name, data in get_highest_sa_mem_averages(colnames): if file_name and data: data['file'] = file_name print(template % data) def get_highest_sa_mem_averages(colnames): pattern = r'(?P<time>\d{2}:\d{2}:\d{2}\s(?:A|P)M)\s+' for item in colnames: pattern += '(?P<' + re.escape(item) + r'>[\d\.]+)\s+' sar_entry = re.compile(pattern) del pattern highest_entries = [] for sa_file in sorted( glob('/var/log/sa/sa[0-9][0-9]'), key=lambda x: os.stat(x).st_mtime, ): highest = 0.0 high_data = None for line in iter_sar('-r', '-f', sa_file): if match := sar_entry.match(line): data = match.groupdict() if float(data['pmemused']) > highest: highest = float(data['pmemused']) high_data = data if high_data: highest_entries.append((sa_file, high_data)) return highest_entries def display_listeners(): header("Listening ports (HTTP and special)") for line in out_lines(['netstat', '-plnt']): if not re.search(r'(httpd|nginx|varnish|memcached)', line): continue try: cols = line.split() print(cols[3], cols[6]) except IndexError: continue def find_configs(users: list[str]) -> Generator[Path, None, None]: homedirs = sorted(pwd.getpwnam(x).pw_dir for x in users) for homedir in homedirs: for dirname, dirnames, filenames in os.walk(homedir, topdown=True): if dirname == homedir: for ig_dir in IGNORE_DIRS: if ig_dir in dirnames: dirnames.remove(ig_dir) for filename in filenames: if filename in CONFIG_MATCHES: yield Path(dirname, filename) def display_software(users: list[str]): header("Software installations found") for conf_path in find_configs(users): if conf_path.name == 'wp-config.php': display_wordpress(conf_path.parent) elif conf_path.name == 'configuration.php': display_joomla(conf_path.parent) elif str(conf_path).endswith('/sites/default/settings.php'): display_drupal(conf_path.parent.parent.parent) elif str(conf_path).endswith('/app/etc/local.xml'): display_magento(conf_path.parent.parent.parent) elif str(conf_path).endswith('/config/settings.inc.php'): display_prestashop(conf_path.parent.parent) elif str(conf_path).endswith('/view/template/common/header.tpl'): display_opencart(conf_path.parent.parent.parent.parent) elif str(conf_path).endswith('/applications/settings/general.php'): display_socialengine(conf_path.parent.parent.parent) elif str(conf_path).endswith('/ow_includes/config.php'): display_oxwall(conf_path.parent.parent) elif str(conf_path).endswith('/app/Config/core.php'): display_cakephp(conf_path.parent.parent.parent) def display_wordpress(root: Path): version = None try: with open(root / 'wp-includes/version.php', encoding='utf-8') as handle: for line in handle: if re.search(r'\$wp_version\s*?=', line): version = line.split("'")[1] break except FileNotFoundError: pass print_software('WordPress', version, root) def display_joomla(root: Path): version = None ver_paths = [ 'includes/version.php', 'libraries/joomla/version.php', 'libraries/cms/version/version.php', ] version_file = None for path in ver_paths: if root.joinpath(path).is_file(): version_file = root / path break if version_file: version = '' with version_file.open(encoding='utf-8') as handle: for line in handle: if re.search(r'\$RELEASE\s*?=', line): version = line.split("'")[1] elif re.search(r'\$DEV_LEVEL\s*?=', line): version += '.' + line.split("'")[1] break print_software('Joomla', version, root) def display_drupal(root: Path): version = None try: with open(root / 'includes/bootstrap.inc', encoding='utf-8') as handle: for line in handle: if re.search(r'\'VERSION\',\s*?\'', line): version = line.split("'")[3] break except FileNotFoundError: pass print_software('Drupal', version, root) def display_magento(root: Path): version = None try: with open( root / 'app/code/core/Mage/Admin/etc/config.xml', encoding='utf-8' ) as handle: for line in handle: if re.search(r'<version>[0-9\.]+</version>', line): version = re.search(r'[0-9\.]+', line).group(0) break except FileNotFoundError: pass print_software('Magento', version, root) def display_prestashop(root: Path): version = None try: with open(root / 'docs/readme_en.txt', encoding='utf-8') as handle: for line in handle: if re.search(r'VERSION: [0-9\.]+', line): version = re.search(r'[0-9\.]+', line).group(0) break except FileNotFoundError: pass print_software('PrestaShop', version, root) def display_opencart(root: Path): version = None try: with open(root / 'index.php', encoding='utf-8') as handle: for line in handle: if re.search(r'\'VERSION\',\s*?\'', line): version = line.split("'")[3] break except FileNotFoundError: pass print_software('OpenCart', version, root) def display_socialengine(root: Path): version = None try: with open( root / 'application/libraries/Engine/manifest.php', encoding='utf-8' ) as handle: for line in handle: if re.search(r'\'version\'.*?\'[0-9\.]+\'', line): version = line.split("'")[3] break except FileNotFoundError: pass print_software('SocialEngine', version, root) def display_oxwall(root: Path): version = None try: with open(root / 'ow_version.xml', encoding='utf-8') as handle: for line in handle: if re.search(r'<version>[0-9\.]+</version>', line): version = re.search(r'[0-9\.]+', line).group(0) break except FileNotFoundError: pass print_software('Oxwall', version, root) def display_cakephp(root: Path): version = None try: with open(root / 'lib/Cake/VERSION.txt', encoding='utf-8') as handle: for line in handle: if re.search(r'^[0-9\.]+', line): version = re.search(r'^[0-9\.]+', line).group(0) break except FileNotFoundError: pass print_software('CakePHP', version, root) def print_software(cms: str, version: str | None, path: Path): if version: print(f"{cms} {version}:", path) else: print(cms, path) def main(): users = parse_args() header("SERVER OVERVIEW") display_server_specs() display_disk_info() display_apache_info() display_php_info() display_mysql_info() display_cpanel_info() check_optimizations() check_recent_high_loads() check_recent_high_mem() display_listeners() if users: display_software(users) header("END OF REPORT") if __name__ == "__main__": main()