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/dedrads
Viewing File: /opt/dedrads/remote_dump
#!/usr/lib/rads/venv/bin/python3 __author__ = 'chases' import os import shlex import socket import argparse import sys import base64 import subprocess import threading from typing import IO from time import sleep from urllib.parse import urlencode import requests from requests.adapters import TimeoutSauce from prettytable import PrettyTable from rads import prompt_y_n, is_cpuser, SECURE_USER, SYS_USERS, get_owner from rads.color import red, yellow, green from cpapis import whmapi1, CpAPIError def check_user(username): """Checks whether the user is valid for switching. Taken from /opt/tier1adv/bin/switch""" restricted_users = SYS_USERS sec_user = SECURE_USER if sec_user is not None: # will be None on VPS/Ded restricted_users.append(sec_user) if username in restricted_users: print(red(f"You may not remove {username}.")) return False if not is_cpuser(username): print(f"The user {username} is not a cpanel user.") return False return True def input_parse(): parser = argparse.ArgumentParser() # fmt: off parser.add_argument( '-u', '--user', action='store', dest='user', help='Define your remote cpanel user or reseller', ) parser.add_argument( '-d', '--domain', action='store', dest='domain', default=False, help='set domain if cannot be pulled from listaccount (no reseller)', ) parser.add_argument( '-p', '--pass', action='store', dest='res_pass', default=None, help='Set the password for the remote reseller or cpuser. Not ' 'recommended to pass as an argument, but an option nonetheless.', ) parser.add_argument( '--replace', action='store', dest='replace', default=False, help='Replace a cpanel user with the backup restored.', ) parser.add_argument( '-r', '--host', action='store', dest='remote_host', default=None, help='Remote Hostname.', ) parser.add_argument( '-n', '--nosize', action='store_true', default=False, dest='nosize', help='Ignore size limitations', ) parser.add_argument( '--norestore', action='store_true', default=False, dest='norestore', help='Will not restore the package', ) parser.add_argument( '--skip', action='store', default=False, nargs='+', dest='skip', help='Skip specific accounts. Useful with --all', ) parser.add_argument( '-i', '--ip', action='store', dest='IP', default=False, help='Set the IP address on Restore.', ) parser.add_argument( '-o', '--owner', action='store', dest='owner', default=False, help='Set the Owner on restore', ) parser.add_argument( '-P', '--package', action='store', dest='package', default=False, help='Set the package on restore', ) parser.add_argument( '--all', action='store_true', default=False, dest='all', help='Dump all backups for accounts within size specification', ) parser.add_argument('--version', action='version', version='%(prog)s 2.0') # fmt: on raw = parser.parse_known_args() parsed = raw[0] try: if not parsed.user: parsed.user = input( "Enter the reseller user from the remote host: " ) if not parsed.res_pass: parsed.res_pass = input( "Enter the reseller password for the remote host: " ) if not parsed.remote_host: parsed.remote_host = input("Enter remote hostname or IP address: ") if parsed.replace: if os.path.isfile(f'/var/cpanel/users/{parsed.replace}'): choice = prompt_y_n( f'Are you sure you would like to replace {parsed.replace} ' f'which is owned by: {get_owner(parsed.replace)}' ) if not choice: parsed.replace = False else: parsed.replace = False except KeyboardInterrupt: sys.exit('\nQuitting') return parsed class MyTimeout(TimeoutSauce): def __init__(self, *_args, **kwargs): connect = kwargs.get('connect', 5) read = kwargs.get('read', connect) super().__init__(connect=connect, read=read) def validate_whm_connection(parsed): print( 'Trying to make a socket connection to WHM. If you know this will ', 'not work you can Ctrl ^C to move on', ) sock = socket.socket() address = parsed.remote_host port = 2086 # port number is a number, not string try: sock.connect((address, port)) except KeyboardInterrupt: print('Moving on') return False except Exception as exc: print(exc, file=sys.stderr) print(red(f"Unable to connect to {address} on port {port}.")) return False return True def remote_whm_post(parsed, jsoncall, url_args, **kwargs): user = kwargs.get('user', parsed.user) port = kwargs.get('port', '2087') remote_host = parsed.remote_host if url_args: jsoncall += '?' + urlencode(url_args) url = 'https://' + remote_host + ':' + port + '/' + jsoncall passholder = parsed.res_pass encoded_pass = base64.b64encode(user + ':' + passholder) auth = "Basic " + encoded_pass header = {'Authorization': auth} requests.adapters.TimeoutSauce = MyTimeout try: response = requests.post(url, headers=header, verify=False, timeout=5) except requests.exceptions.ConnectTimeout: return False try: response = response.json() except ValueError: print('Unable to get valid response from', remote_host) print('Response received from ', remote_host, ':', response) return False return response def _write_stdin(stdin: IO, data: str): stdin.write(data) stdin.close() def get_cpanel_backup( parsed, restoreuser=False, restoredomain=False, attempt=0 ): if restoreuser: parsed.user = restoreuser if restoredomain: parsed.domain = restoredomain restorelog = f'/tmp/parsed.{parsed.user}.backuplog' print( "Backup will start in the background. You can monitor it by tail -f", restorelog, ) cmd = [ '/scripts/getremotecpmove', parsed.remote_host, parsed.user, parsed.domain, ] with open(restorelog, 'w', encoding='utf-8') as writelog: with subprocess.Popen( cmd, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, ) as proc: # write to stdin in a thread so this won't deadlock if the proc # writes > 1024 bytes to stdout/err before consuming stdin thread = threading.Thread( target=_write_stdin, args=(proc.stdin, parsed.res_pass), daemon=True, ) for line in proc.stdout: print(line.strip(), flush=True) writelog.write(line) thread.join() if proc.wait(): # non-zero exit code print(red('Failed to get backup from remote host.')) if attempt < 2: print( red('Waiting 10 minutes and attempting again.'), red('Press ctrl ^c to Manually Fail this user.'), flush=True, ) try: sleep(600) except KeyboardInterrupt: # This user Failed, but continuing on return False attempt += 1 get_cpanel_backup(parsed, attempt=attempt) else: print(red('Too many attempts failed. Moving on to next user.')) return False with open(restorelog, encoding='utf-8') as resultfile: for line in resultfile: if line.startswith('pkgacctfile is'): print('Backup Found: {}'.format(line.split(' ')[2].strip())) return line.split(' ')[2].strip() if 'Failed to fetch cpmove file via cPanel XML-API' in line: return False return False def set_backup_to_standard_naming(backup, user): if os.path.isfile(backup): os.rename(backup, f'/home/cpmove-{user}.tar.gz') return f'/home/cpmove-{user}.tar.gz' return False def restore_backup(backup, user, parsed): # TODO: this script is python. restorepkg is python. Import and inspect the # result directly rather than executing and reading its output str. # Do this after merging the rpms. cmd = ['/opt/tier2c/safe_restorepkg.py'] if parsed.IP: cmd.extend(['--ip', parsed.IP.strip()]) cmd.extend('--quiet', backup) status = 'Success' with subprocess.Popen( cmd, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) as proc: for line in proc.stdout: print(line.strip()) if 'Failed' in line: status = 'Failed' if line.startswith("{'%s'" % user): try: status = line.split("'")[3] except KeyError: pass if proc.wait(): status = 'Non-Zero Exit' if status == 'Success' and parsed.owner: try: whmapi1.set_owner(user, parsed.owner) except CpAPIError as exc: status = str(exc) return status def main(): parsed = input_parse() if parsed.domain or parsed.replace or not validate_whm_connection(parsed): if not parsed.domain: print('Additional Information Required') parsed.domain = input( "Enter the primary domain for the cPanel you are transferring: " ) print('Running:') cmd = [ '/scripts/getremotecpmove', parsed.remote_host, parsed.user, parsed.domain, ] print('echo', shlex.quote(parsed.res_pass), '|', shlex.join(cmd)) backup = get_cpanel_backup(parsed) if backup: backup = set_backup_to_standard_naming(backup, parsed.user) if ( parsed.replace and check_user(parsed.replace) and backup is not False ): print( yellow(f'Removing Account {parsed.replace} so it can '), yellow('be restored from the backup'), ) subprocess.check_call( ['/scripts/removeacct', '--force', parsed.user] ) if not parsed.norestore: restore_backup(backup, parsed.user, parsed) else: parsed.userinfo = remote_whm_post( parsed, 'json-api/listaccts', { 'api.version': 1, 'want': 'user,disklimit,diskused,domain,suspended', }, ) # Check to see if the WHM connection failed and if so switch # to single transfer mode if parsed.userinfo: try: print(parsed.userinfo['cpanelresult']['data']['reason']) # Check for a read-only filesystem, and exit if found if "Read-only" in parsed.userinfo['metadata']['reason']: sys.exit('Read-only file system found') if prompt_y_n( 'Connection to remote WHM Failed. Would you like to try ' 'connecting via cPanel? Select Yes if not a reseller.' ): if not parsed.domain: print('Additional Information Required') parsed.domain = input( "Enter the primary domain for the cPanel " "you are transferring: " ) backup = get_cpanel_backup(parsed) if backup: backup = set_backup_to_standard_naming( backup, parsed.user ) if not parsed.norestore: restore_backup(backup, parsed.user, parsed) except Exception as exc: print(exc, file=sys.stderr) # This handles the case of remote_whm_post returning False, and was # otherwise not a caught exception prior else: sys.exit( 'There was an issue connecting to the remote host. Exiting.' ) userdata = {} pickerlist = [] toobig = [] try: for userinfo in parsed.userinfo['data']['acct']: userdata[userinfo['user']] = {} userdata[userinfo['user']]['domain'] = userinfo['domain'] userdata[userinfo['user']]['diskused'] = userinfo['diskused'] userdata[userinfo['user']]['disklimit'] = userinfo['disklimit'] userdata[userinfo['user']]['disklimit'] = userinfo['suspended'] if ( str(userinfo['diskused'].split('M')).lower() == 'none' or parsed.nosize or int(userinfo['diskused'].split('M')[0]) <= 6000 ): pickerlist.append( '{} - {} - {}'.format( userinfo['user'], userinfo['domain'], userinfo['diskused'], )[:50] ) if ( not parsed.nosize and int(userinfo['diskused'].split('M')[0]) >= 6000 ): toobig.append(userinfo['user']) if userinfo['suspended'] == 1: try: print( red(f'Unable to transfer {userinfo["user"]}'), red('because it is suspended.'), ) pickerlist.remove( '{} - {} - {}'.format( userinfo['user'], userinfo['domain'], userinfo['diskused'], )[:50] ) except Exception: pass except KeyError: print(userdata) sys.exit('Unable to gather required information') if toobig: print( 'The Following Accounts are too big to move by default.', 'You can use --nosize if you need to transfer them', ) print(*toobig) if not prompt_y_n('Would you like to continue?'): sys.exit('Try again with --nosize to ignore 6GB limit') if parsed.all: opts = [i.split(' ')[0] for i in pickerlist] else: useroptions = [i.split(' ')[0] for i in opts] print("Select users to transfer") print(opts.join("\n")) print("send q to finish") opts = [] while True: user_input = input().lower() if user_input in useroptions and user_input not in opts: opts.append(user_input) elif user_input == "q": break split = user_input.split(" ") if len(split) > 1: for name in split: if name in useroptions and name not in opts: opts.append(name) os.system('clear') print(opts) sys.stdout.flush() status = {} for user in opts: status[user] = 'Pending' for user in opts: print_status_info(status) try: if ( userdata[user]['disklimit'].lower() != 'unlimited' and str(userdata[user]['diskused'].split('M')).lower() != 'none' ): if ( int(userdata[user]['diskused'].split('M')[0]) * 2 ) < userdata[user]['disklimit'].split('M')[0]: print( red(f'Warning: User {user}: Does not have double'), red('the quota of the disk space used which can '), red('cause problems'), ) print( green('Setting Quota to unlimited to ensure no '), green(f'problem with backup on {user}'), ) newquota = remote_whm_post( parsed, 'json-api/modifyacct', {'api.version': 1, 'user': user, 'QUOTA': 0}, ) if newquota['metadata']['result'] == 1: print(green('Result: Success')) except Exception as exc: print(exc) print(red(f'Failed to set new Quota on {user}')) backup = get_cpanel_backup( parsed, restoreuser=user, restoredomain=userdata[user]['domain'] ) if backup and not parsed.norestore: backup = set_backup_to_standard_naming(backup, user) status[user] = restore_backup(backup, user, parsed) elif parsed.norestore: status[user] = 'Restore Skipped' else: status[user] = 'Failed' print_status_info(status) print('Done') def print_status_info(status): tbl = PrettyTable() tbl.field_names = ["User", "Status", "Conflict Info"] for user, itemstatus in status.items(): if '-' in status: conflict = status.split('-')[1:] itemstatus = status.split('-')[0] else: conflict = None tbl.add_row([user, itemstatus, conflict]) print(tbl) if __name__ == "__main__": main()