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/saltstack/salt/lib/python3.10/site-packages/salt/modules
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/runit.py
""" runit service module (http://smarden.org/runit) This module is compatible with the :mod:`service <salt.states.service>` states, so it can be used to maintain services using the ``provider`` argument: .. code-block:: yaml myservice: service: - running - provider: runit Provides virtual `service` module on systems using runit as init. Service management rules (`sv` command): service $n is ENABLED if file SERVICE_DIR/$n/run exists service $n is AVAILABLE if ENABLED or if file AVAIL_SVR_DIR/$n/run exists service $n is DISABLED if AVAILABLE but not ENABLED SERVICE_DIR/$n is normally a symlink to a AVAIL_SVR_DIR/$n folder Service auto-start/stop mechanism: `sv` (auto)starts/stops service as soon as SERVICE_DIR/<service> is created/deleted, both on service creation or a boot time. autostart feature is disabled if file SERVICE_DIR/<n>/down exists. This does not affect the current's service status (if already running) nor manual service management. Service's alias: Service `sva` is an alias of service `svc` when `AVAIL_SVR_DIR/sva` symlinks to folder `AVAIL_SVR_DIR/svc`. `svc` can't be enabled if it is already enabled through an alias already enabled, since `sv` files are stored in folder `SERVICE_DIR/svc/`. XBPS package management uses a service's alias to provides service alternative(s), such as chrony and openntpd both aliased to ntpd. """ import glob import logging import os import time import salt.utils.files import salt.utils.path from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) # Function alias to not shadow built-ins. __func_alias__ = {"reload_": "reload"} # which dir sv works with VALID_SERVICE_DIRS = [ "/service", "/var/service", "/etc/service", ] SERVICE_DIR = None for service_dir in VALID_SERVICE_DIRS: if os.path.exists(service_dir): SERVICE_DIR = service_dir break # available service directory(ies) AVAIL_SVR_DIRS = [] # Define the module's virtual name __virtualname__ = "runit" __virtual_aliases__ = ("runit",) def __virtual__(): """ Virtual service only on systems using runit as init process (PID 1). Otherwise, use this module with the provider mechanism. """ if __grains__.get("init") == "runit": if __grains__["os"] == "Void": add_svc_avail_path("/etc/sv") global __virtualname__ __virtualname__ = "service" return __virtualname__ if salt.utils.path.which("sv"): return __virtualname__ return (False, "Runit not available. Please install sv") def _service_path(name): """ Return SERVICE_DIR+name if possible name the service's name to work on """ if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") return os.path.join(SERVICE_DIR, name) # -- states.service compatible args def start(name): """ Start service name the service's name CLI Example: .. code-block:: bash salt '*' runit.start <service name> """ cmd = f"sv start {_service_path(name)}" return not __salt__["cmd.retcode"](cmd) # -- states.service compatible args def stop(name): """ Stop service name the service's name CLI Example: .. code-block:: bash salt '*' runit.stop <service name> """ cmd = f"sv stop {_service_path(name)}" return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def reload_(name): """ Reload service name the service's name CLI Example: .. code-block:: bash salt '*' runit.reload <service name> """ cmd = f"sv reload {_service_path(name)}" return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def restart(name): """ Restart service name the service's name CLI Example: .. code-block:: bash salt '*' runit.restart <service name> """ cmd = f"sv restart {_service_path(name)}" return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def full_restart(name): """ Calls runit.restart() name the service's name CLI Example: .. code-block:: bash salt '*' runit.full_restart <service name> """ restart(name) # -- states.service compatible def status(name, sig=None): """ Return ``True`` if service is running name the service's name sig signature to identify with ps CLI Example: .. code-block:: bash salt '*' runit.status <service name> """ if sig: # usual way to do by others (debian_service, netbsdservice). # XXX probably does not work here (check 'runsv sshd' instead of 'sshd' ?) return bool(__salt__["status.pid"](sig)) svc_path = _service_path(name) if not os.path.exists(svc_path): # service does not exist return False # sv return code is not relevant to get a service status. # Check its output instead. cmd = f"sv status {svc_path}" try: out = __salt__["cmd.run_stdout"](cmd) return out.startswith("run: ") except Exception: # pylint: disable=broad-except # sv (as a command) returned an error return False def _is_svc(svc_path): """ Return ``True`` if directory <svc_path> is really a service: file <svc_path>/run exists and is executable svc_path the (absolute) directory to check for compatibility """ run_file = os.path.join(svc_path, "run") if ( os.path.exists(svc_path) and os.path.exists(run_file) and os.access(run_file, os.X_OK) ): return True return False def status_autostart(name): """ Return ``True`` if service <name> is autostarted by sv (file $service_folder/down does not exist) NB: return ``False`` if the service is not enabled. name the service's name CLI Example: .. code-block:: bash salt '*' runit.status_autostart <service name> """ return not os.path.exists(os.path.join(_service_path(name), "down")) def get_svc_broken_path(name="*"): """ Return list of broken path(s) in SERVICE_DIR that match ``name`` A path is broken if it is a broken symlink or can not be a runit service name a glob for service name. default is '*' CLI Example: .. code-block:: bash salt '*' runit.get_svc_broken_path <service name> """ if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") ret = set() for el in glob.glob(os.path.join(SERVICE_DIR, name)): if not _is_svc(el): ret.add(el) return sorted(ret) def get_svc_avail_path(): """ Return list of paths that may contain available services """ return AVAIL_SVR_DIRS def add_svc_avail_path(path): """ Add a path that may contain available services. Return ``True`` if added (or already present), ``False`` on error. path directory to add to AVAIL_SVR_DIRS """ if os.path.exists(path): if path not in AVAIL_SVR_DIRS: AVAIL_SVR_DIRS.append(path) return True return False def _get_svc_path(name="*", status=None): """ Return a list of paths to services with ``name`` that have the specified ``status`` name a glob for service name. default is '*' status None : all services (no filter, default choice) 'DISABLED' : available service(s) that is not enabled 'ENABLED' : enabled service (whether started on boot or not) """ # This is the core routine to work with services, called by many # other functions of this module. # # The name of a service is the "apparent" folder's name that contains its # "run" script. If its "folder" is a symlink, the service is an "alias" of # the targeted service. if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") # path list of enabled services as /AVAIL_SVR_DIRS/$service, # taking care of any service aliases (do not use os.path.realpath()). ena = set() for el in glob.glob(os.path.join(SERVICE_DIR, name)): if _is_svc(el): if os.path.islink(el): ena.add(os.readlink(el)) else: ena.add(el) log.trace("found enabled service path: %s", el) if status == "ENABLED": return sorted(ena) # path list of available services as /AVAIL_SVR_DIRS/$service ava = set() for d in AVAIL_SVR_DIRS: for el in glob.glob(os.path.join(d, name)): if _is_svc(el): ava.add(el) log.trace("found available service path: %s", el) if status == "DISABLED": # service available but not enabled ret = ava.difference(ena) else: # default: return available services ret = ava.union(ena) return sorted(ret) def _get_svc_list(name="*", status=None): """ Return list of services that have the specified service ``status`` name a glob for service name. default is '*' status None : all services (no filter, default choice) 'DISABLED' : available service that is not enabled 'ENABLED' : enabled service (whether started on boot or not) """ return sorted(os.path.basename(el) for el in _get_svc_path(name, status)) def get_svc_alias(): """ Returns the list of service's name that are aliased and their alias path(s) """ ret = {} for d in AVAIL_SVR_DIRS: for el in glob.glob(os.path.join(d, "*")): if not os.path.islink(el): continue psvc = os.readlink(el) if not os.path.isabs(psvc): psvc = os.path.join(d, psvc) nsvc = os.path.basename(psvc) if nsvc not in ret: ret[nsvc] = [] ret[nsvc].append(el) return ret def available(name): """ Returns ``True`` if the specified service is available, otherwise returns ``False``. name the service's name CLI Example: .. code-block:: bash salt '*' runit.available <service name> """ return name in _get_svc_list(name) def missing(name): """ The inverse of runit.available. Returns ``True`` if the specified service is not available, otherwise returns ``False``. name the service's name CLI Example: .. code-block:: bash salt '*' runit.missing <service name> """ return name not in _get_svc_list(name) def get_all(): """ Return a list of all available services CLI Example: .. code-block:: bash salt '*' runit.get_all """ return _get_svc_list() def get_enabled(): """ Return a list of all enabled services CLI Example: .. code-block:: bash salt '*' service.get_enabled """ return _get_svc_list(status="ENABLED") def get_disabled(): """ Return a list of all disabled services CLI Example: .. code-block:: bash salt '*' service.get_disabled """ return _get_svc_list(status="DISABLED") def enabled(name): """ Return ``True`` if the named service is enabled, ``False`` otherwise name the service's name CLI Example: .. code-block:: bash salt '*' service.enabled <service name> """ # exhaustive check instead of (only) os.path.exists(_service_path(name)) return name in _get_svc_list(name, "ENABLED") def disabled(name): """ Return ``True`` if the named service is disabled, ``False`` otherwise name the service's name CLI Example: .. code-block:: bash salt '*' service.disabled <service name> """ # return True for a non-existent service return name not in _get_svc_list(name, "ENABLED") def show(name): """ Show properties of one or more units/jobs or the manager name the service's name CLI Example: .. code-block:: bash salt '*' service.show <service name> """ ret = {} ret["enabled"] = False ret["disabled"] = True ret["running"] = False ret["service_path"] = None ret["autostart"] = False ret["command_path"] = None ret["available"] = available(name) if not ret["available"]: return ret ret["enabled"] = enabled(name) ret["disabled"] = not ret["enabled"] ret["running"] = status(name) ret["autostart"] = status_autostart(name) ret["service_path"] = _get_svc_path(name)[0] if ret["service_path"]: ret["command_path"] = os.path.join(ret["service_path"], "run") # XXX provide info about alias ? return ret def enable(name, start=False, **kwargs): """ Start service ``name`` at boot. Returns ``True`` if operation is successful name the service's name start : False If ``True``, start the service once enabled. CLI Example: .. code-block:: bash salt '*' service.enable <name> [start=True] """ # non-existent service if not available(name): return False # if service is aliased, refuse to enable it alias = get_svc_alias() if name in alias: log.error("This service is aliased, enable its alias instead") return False # down_file: file that disables sv autostart svc_realpath = _get_svc_path(name)[0] down_file = os.path.join(svc_realpath, "down") # if service already enabled, remove down_file to # let service starts on boot (as requested) if enabled(name): if os.path.exists(down_file): try: os.unlink(down_file) except OSError: log.error("Unable to remove file %s", down_file) return False return True # let's enable the service if not start: # create a temp 'down' file BEFORE enabling service. # will prevent sv from starting this service automatically. log.trace("need a temporary file %s", down_file) if not os.path.exists(down_file): try: # pylint: disable=resource-leakage salt.utils.files.fopen(down_file, "w").close() # pylint: enable=resource-leakage except OSError: log.error("Unable to create file %s", down_file) return False # enable the service try: os.symlink(svc_realpath, _service_path(name)) except OSError: # (attempt to) remove temp down_file anyway log.error("Unable to create symlink %s", down_file) if not start: os.unlink(down_file) return False # ensure sv is aware of this new service before continuing. # if not, down_file might be removed too quickly, # before 'sv' have time to take care about it. # Documentation indicates that a change is handled within 5 seconds. cmd = f"sv status {_service_path(name)}" retcode_sv = 1 count_sv = 0 while retcode_sv != 0 and count_sv < 10: time.sleep(0.5) count_sv += 1 call = __salt__["cmd.run_all"](cmd) retcode_sv = call["retcode"] # remove the temp down_file in any case. if (not start) and os.path.exists(down_file): try: os.unlink(down_file) except OSError: log.error("Unable to remove temp file %s", down_file) retcode_sv = 1 # if an error happened, revert our changes if retcode_sv != 0: os.unlink(os.path.join([_service_path(name), name])) return False return True def disable(name, stop=False, **kwargs): """ Don't start service ``name`` at boot Returns ``True`` if operation is successful name the service's name stop if True, also stops the service CLI Example: .. code-block:: bash salt '*' service.disable <name> [stop=True] """ # non-existent as registrered service if not enabled(name): return False # down_file: file that prevent sv autostart svc_realpath = _get_svc_path(name)[0] down_file = os.path.join(svc_realpath, "down") if stop: stop(name) if not os.path.exists(down_file): try: # pylint: disable=resource-leakage salt.utils.files.fopen(down_file, "w").close() # pylint: enable=resource-leakage except OSError: log.error("Unable to create file %s", down_file) return False return True def remove(name): """ Remove the service <name> from system. Returns ``True`` if operation is successful. The service will be also stopped. name the service's name CLI Example: .. code-block:: bash salt '*' service.remove <name> """ if not enabled(name): return False svc_path = _service_path(name) if not os.path.islink(svc_path): log.error("%s is not a symlink: not removed", svc_path) return False if not stop(name): log.error("Failed to stop service %s", name) return False try: os.remove(svc_path) except OSError: log.error("Unable to remove symlink %s", svc_path) return False return True