Compare commits
11
Commits
c396c89efb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01e6ad90f0 | ||
|
|
008fd5bf7b | ||
|
|
fc2ad85e19 | ||
|
|
2d039ae32f | ||
|
|
94c77daf13 | ||
|
|
61dba61025 | ||
|
|
b73107be79 | ||
|
|
15aeb8e222 | ||
|
|
04a0937910 | ||
|
|
01cd7b8d1e | ||
|
|
5559cd425d |
No files matched your search
+426
-417
@@ -1,418 +1,427 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
39633233363564393465633065323730343130653362373436356334323930393036656638303033
|
31356235343830393937633039386366623238373864353438353332653038616330623365343135
|
||||||
6333636536643835303936633039633138336434663565320a643961663732373630333238633466
|
3961393463613335663138383233323964393832626466620a393763616364363030323833386565
|
||||||
62633236666331326165653637353038313163643162323763313635306230333338613164316337
|
30666333366332313262313836613962363537353065646361316230386434366534666338336636
|
||||||
6133396638336465350a306163643138653639646133346561323333313332336639393764343364
|
3930363231393333610a613039646430663233613163376131333637383866656564333765633565
|
||||||
30666535323264363661653236333839643631376337303563383739656238343732396364366437
|
32333338316539363034316132313631643632316337356164373765373736366639623362343738
|
||||||
33333437643930656564376236333736336465356134653239393036313830333462363633653366
|
63326164623535326461323234333933656132313836643139316339333831323331393030323233
|
||||||
62623863613261323135633262633066383363313731646361326232366661356261316633323164
|
34643932626464353861616534326130373362646437613131326464313737623965366361616266
|
||||||
34653661383366643531353939303333363832323731623830393262373538613631613631616463
|
37393934333761303163346465366464393964313831393266303434313061356333373430346339
|
||||||
66396163613965343833356432643534393539643961373263323533316664386665383330326665
|
33393331393461303163306639386239656139613833386235363335333837316666323637346462
|
||||||
35393537363734333961393931386161386234323264316466303361663139323431303034393035
|
35346634383936333631303338356433323865623030663336323663373962623531313562626666
|
||||||
33353432363162353261646231313136643533363563343537666565306638353564646235623835
|
33393063613137646631666363386161636462343262313935386535386361346464396133613736
|
||||||
33653663393838303364363762613663646139326135313438663838323064326265303733346164
|
31383464613130353034396264643766646161343664363435383639656234623035343833653832
|
||||||
62386130613134653739353930623330663135633632346531306535643332303939356263636232
|
63356130646136396463636533616334643638333239636437663030393064326362393736373233
|
||||||
34313939333263313133373635396138643838396235626662343664316632663930313038643863
|
34396166653932326632376535373762663633303261303536396334303937316431346133353830
|
||||||
65653630373437373631633032333663353866636630306235363035623837656661323735356361
|
37626337313861306164366130643131383133323337643637666535316132373238373066306339
|
||||||
33343961373235613262613363636565396234613865303937323432656433343836373831343162
|
38343865613937663366376630333365633039333430653762313864303737633661356232373133
|
||||||
37356234363662333764656530333139653434336137326631386366306162646433333239316133
|
64303934623937313863323431396638383932383836613830336235656530613234316238353364
|
||||||
35303136633664336433386331316662313330643734383139633938636462346539623835653738
|
32306561343362643431623239333638316239323164386634623736313261313936616239396431
|
||||||
63373439623866363863633963633265373339613130343966656637376632396139646266306666
|
64373934313663313139316331623436663832393131373562393436326265656562363137636461
|
||||||
62316430343338626331326433366266396131666264633234323233646639643236323937396431
|
33613864356464366239306639653264363565306331636137396466336163336631353163383430
|
||||||
62323738376664303237653563323164396166313266613937383431653766616234653762383163
|
30303034383136356465643238663034623036363562353037343832616136363539656432346639
|
||||||
65666138353262663536623036623439363463306136363930343633383436333565363232376162
|
34636536383533333538346237653731633331643137303966316138333633373564383034383438
|
||||||
35343035306230666432666131643634383263663838626466633133653830623330656164313465
|
34666465323032356333613935363161306431666533353061626536633839396234613662623062
|
||||||
32363031313266396532313563343963613133396232353832636464663462636138623637646135
|
63313338346138653765393537356639373035643935373735323166376336626166396562316535
|
||||||
64356364363862653465303837663261333933623266333736343938643065393031343535633762
|
39343236356665363038326132313437303236646161333638393939653033623664306563613730
|
||||||
62363436323931373563323863643066653237393339653363656133316365346262306463336364
|
34383538656139646436333765623861303739613939633962316139386463643666366464303935
|
||||||
66303732366437633461373065363730336235313732623131633366633834376563663734653063
|
32366239656366393832376436623534653539376661363737343865663633316138663061643866
|
||||||
31656638623933376666393232613238323239623836313430356639613131303761346535336338
|
65393930386162393663303135396237623633656230303036646431633236376465633835336364
|
||||||
65613363366331613865343163623863636666303336646636663465373065393731306634663239
|
63643332616433303766396561386433363836343836663430636530616135653966336131383533
|
||||||
32653666343631666235383863336338326438646137656562646431623436323037313438343839
|
64613933306165303564303835616132613839636432653137356131343732343736313237356436
|
||||||
34663062396564306138356465623032396239396562643933306365613033333065323630393665
|
35663033353163346363366635333231366432316434306362616265653832313963303338343538
|
||||||
66346262303965353539663463626261633332373833346430346235376636323633623864353133
|
61363966306162323932393037626335613133363936393166626433653463363661343732306465
|
||||||
34666265373936376339646363383834393166376434653233306632373966336232316339336465
|
32373839653865326333613165353461613531383666323938653034623936303562363331643363
|
||||||
63313866653732386234666531663030313364626534343235333463376334663061316461613331
|
37326164323935626239396239646465616464613332636662353539363233343032616636613237
|
||||||
64303962393930373036643561393762666334666538626233333937653038633734626430623837
|
62316663643134663038626535663262336266646565336236643234663561313264626238313136
|
||||||
33303463386363386562333061353963303538366362313131656433383263666631666566633765
|
65383364643537393436666465653437653334663934333765626233303835643732373334663639
|
||||||
65613238393736646637346333323933326664663862326563363566613361343539646264626561
|
62643064383535366630306666383365353963306563323533653664643132383832346435323465
|
||||||
37323034323136376433343433316266386135333765643036373565326539643066336533343431
|
30326466623265326437383063353833383131393166313231633032333137326563633734636263
|
||||||
34316264393261373130333930383932343864386534376633336239303066356666363438356632
|
61623466613436393237656435623335623133343032333535373430633836346566353039646238
|
||||||
63313637616465386363346162653064613236383166353432636530336437376665643830356134
|
34363338343065323765313466306130643462303061336265623833343139353336383234366362
|
||||||
35366239326637373366383962376165643932336632373330326533366535666434343161373264
|
30383465376635356462313533643961613437333930396134626463383464323661396431643234
|
||||||
30323130626236356666663230613837373666666334646366616339313661613238666138353832
|
30353737396262653336376137646137353362353066366634613034313938613234323931333365
|
||||||
37633066303139313961643935383632646662613766303366663335306263646439383534373639
|
39323733613864346132633461323563396161643738333634316564353934376537353663386636
|
||||||
33643733626531376565623431666637323736393361373566626666633232383832656333333566
|
36373335343439643432396266386665396266643063616331666438363066643162356265373732
|
||||||
65616662383561626161363061646334633033386534633533393032386532643939373736356532
|
31616537656266363866656133316663383634343735616266653161613139636164653566303666
|
||||||
31633430613338343762656630303139316634323635653763386366633366366336383064613333
|
35396566653136343038636239626561303966346636333635363836366538616233386164316633
|
||||||
32373164633066643064316535333364366461666332663165346462386563373864663565613736
|
30626135303434636335623366383566366532313937313133313664313462316438323964376130
|
||||||
64636131346133623166656235306230353733373263313166383761303364366434656666303732
|
61363166306232363737616432316666623166313962366462656464626537323538316337626635
|
||||||
34313234663139616466633562383663316630666261336463653236393832303363316438326635
|
64366533383264356430343066373664646534396538306335333861356237346164373235643239
|
||||||
32343135396333613936383239633938393261393435653239643763373065623531336633313130
|
31633264396338303061376633376564356231336338376135386639393031656437363662613861
|
||||||
64323761646666636464623734656236653439646433303934633536333862653930303133336264
|
33373934313862396238353863663530633337383633623565623937623734616664626534383061
|
||||||
66333361323735386338643933336636303263613739666432626137393061343833636537396539
|
34353862393537633932393338653265353633643166383564363766636539636334643534333736
|
||||||
62626534663636323764376437393237663839356534306438306431663937323364316431323264
|
62666332313730666661303462656162303338326433393934313536326365663235346263313330
|
||||||
38353234303364653833393733383736636139336237386137313937656137636562303036666135
|
34623539383031653538333762396361336235643936386366323235346162326635393062646263
|
||||||
62626530383037613439646261346130353430303131303936393230333662386533356631326533
|
36343638613134353966363261613632313963363236326364366133353838616636323138316561
|
||||||
65643931313036623766316137353130363535383834323363623334376234393265326362653838
|
63356237663864333334343461396364656231333939636264343966343266346430363965633764
|
||||||
34353665666537343232626137383166383761643232313539346461393937353737373935323462
|
38636562626366636438343538666632643964393463353836323536333961613961613166373963
|
||||||
38303631383465623865323230333330383730333536346366616332343539313237333538333431
|
62356336396536613238353531616237616334616231363836616561333866643033666336336439
|
||||||
65636235343938316635633732393430323932626339323236323663663932306463373039343363
|
65646362393933306534326137396561346338336263383134383263373836323034653932646463
|
||||||
66363235366539386430333365613338646166313133346239346537313666396435343064643537
|
62326364336438623137623862386438396164643432333135346462363761333437653532373361
|
||||||
30363236396563346165353032373463386630646134356634646465353136643635373164643263
|
32306161313238363365633131663334376135613336363937313263663537313733313762656134
|
||||||
31396333353232323937346530303334623163316538353937396337636165356163396639373731
|
33613838326137343061306431363237373661333163373739326339303732623131393633646565
|
||||||
64363036346434623366376261316632343238306564646234383433343137396462303466383635
|
38663963663338623362653364343337383636323730306431313830316261666264326464303235
|
||||||
38353161383933336264393332386233643666656132633038323936353630313635313365353663
|
64313662326434646366386166396464653666323735323931343436653763363535396636613938
|
||||||
30353734363435373265616333653737383838376463646466633732333537346565666433656137
|
37386435303139663965623963383861353030626530653061636132326233373165613231316232
|
||||||
62333234653264626532323064386639643136336264346232663537393065386666623463336433
|
38616539303630316565366161383664316337333665323631663430363764313736643934623638
|
||||||
38613966376535303961343361363930313664633030353538306633656335326430653534346161
|
35346264666461623533623963306238613530633034333662653561353030646437346537623065
|
||||||
31613166366231386239366631623839316361303630663063336637303832373463613735356364
|
37376263366630666430383833373966353730396239626133333330306232643037643231336433
|
||||||
39323238656232383632643630386261323764643861353063616130323939383732343863356633
|
62653332623461653565376531386464353237383665323538363061633831353534656366333933
|
||||||
33303965363565313535386463613030373634653763313430643335393032663839636164303630
|
37373235376564613665303462393634623836663835383738356138323136316432663637353434
|
||||||
38653930383661386331613763316365653538386266396430633638633638653134633365343937
|
36336338623364383334363565643334613631386330633466396336336539383762383162363631
|
||||||
30313565663139366139623065623730356363313731663034663362313335346561323130396333
|
32393230623938623433333632663365313165306165643433643131366237366131616263333465
|
||||||
37343165333434613235363964323564306330393738333635303763633466366664616138613862
|
38333739636234326139393361386131383232373362343636653862356535613230343232333365
|
||||||
61623464656630343361353433313836316230653435356661326366366131386637363334326336
|
30316261623036373531383238373164313961386532393663643736363131623533373366613565
|
||||||
34313334386464363261343831356163613834353639323562333930363563343963633833623832
|
37653133383930663037363232353032616133653763306564383434366233613066373331363162
|
||||||
37323964336565383062373739346261306139376637306461336265316336666439353536313063
|
61366530303731333235313537366436363237303561336232663363396562313735613461363331
|
||||||
31653432373235666235396239363261316333373332333234376635323261306234383836383161
|
30336561643035336430643666643565653538616536646263303265663739653132353233343834
|
||||||
33303136363335353539663833306166643737333238643066313633323031613237666264666365
|
36613332313430313138636464393139316466373738313330383762623963376363663839386366
|
||||||
63333766616631366639396235633466383062623462363638363866346462306535623839626231
|
36636265383436643661653465383561306663303461633335373331323733663936373334353164
|
||||||
63666633393137316637616634653139316439626432366236663738316166643335333534626164
|
35353138306233626363616636633236643130373432636161646563333937623334636163653637
|
||||||
62623861356134663637663665313934313833636165643732376438316563383539663037373432
|
31613431363963396266303637363833303635376239356337613935636165326265303166383062
|
||||||
30326233373936346433323934663633363166376463623135386238393334396466316265613965
|
32326165636331356466363766643766363266666530363739383535623863393135663331623737
|
||||||
33623362653436613835386535393038363064373233623266643466633732366632346330343438
|
61303261313563643665626435343362656632363263656235316230646135616163346630363335
|
||||||
65383938363063666263616330356661343732313435383137313435656337353063613230656462
|
36633564383761346163383166326562313464316362613531633565646365326538356565386630
|
||||||
63383465343932303832313335346230313164333362303935343836363764313933623831306130
|
35396334353834343733643931376261336435373362386339353138386435663961623064343438
|
||||||
36333339373230386162323337386661303663616530383863333332396439313833303461643662
|
34633639383037653437653461346563373733663536313862386262393933623434313163313136
|
||||||
37336231343065623635376564393934316333626566623765353265613737646131653934343631
|
64333531656462353966353362646636353634646433333361633166653338373938326639343732
|
||||||
32613463636130333561653066343839303332396239656534303733643639343337363437656662
|
30313163613532633163663965393434393735383730653430663762336262393564303263643935
|
||||||
63303732326235643830303038663131613330303863353231313561376231666135353464303864
|
32313564393835353762363662373631643430393938643265393462643864633465643636306664
|
||||||
66633332613139386136303264366464356434633434656536393664313130646234393634326432
|
32363936303737363934373536383136613636323538363565306563653438343961373566363463
|
||||||
63663938373932386564363834373134356464633662383130386337633564643930613734383631
|
34323338636161316537383036643638366430303964376435356164333666313833303264666464
|
||||||
38643239663733393336643834643438303737636635396630333565656264653866366166623332
|
64383766356630313130626235663364653730666638363336353938323735356434336539383132
|
||||||
35653430323365313834643862393065323531633230613630343738333934383161613264356265
|
61633334383661613039373136613735306662633734346538663838653132343066313166333834
|
||||||
66626561386138396639353934653465373162323838303233636565306534633236343066623638
|
33386533306466613037383636636432363435616266373664636631623264396538626366616164
|
||||||
38653132376663666136383235663337343762646137643935626365343162363439303061393563
|
63363831616562653034666231386365666639343330303964313165616462623865663266326531
|
||||||
32633366623834613739303139333038303762316663663136656636663663386362613264663238
|
38306337376133613932656631393532613431323964643532386234303335643731373265643536
|
||||||
35323038326239643062666336333932383065383461373933626339383434363736323536313533
|
39336233363530313533663163616462333466656364666262313132636633613863343766656465
|
||||||
65363565306239363935666234653438393039303663623636376262666164393334363839393665
|
38393339386335623234646437393061366230643432636163306432633365356330306234396530
|
||||||
62326533613639643938613838396565376334333035333962626135356265353639653633373438
|
37346466333961383538356236303630353131376330613437656536393766383062323235643465
|
||||||
37316463303838633335306333346465386461373766356533313863333534326231353864303531
|
38663034393633383661363632386431343364333664396530323066306365366539366336333265
|
||||||
32663461623731653138646336663266303065306562643961393936373937653664323032626339
|
34343865633834333564343237336661613437363333373032393661633034643161623461306362
|
||||||
39373539363664316432333931633866646333346363336466393963656637366562336162613465
|
33613663613966636235333664313731333064613033613639656232626365366461373535376532
|
||||||
32663230613335393839633633633063636635373363653234663564663264353036393434663963
|
37613365393237333037653730323432386162616165663335653565666565656438373337376232
|
||||||
66653631663062666338326133636236303862356662666533326236613965323462303839623331
|
35663933646636336535653431613337313562386137363137616561346435636562656337653265
|
||||||
35626238323837343738356264313266383135626239363731353639633837323132623635633435
|
62396430663033373234366535633838306335306263616561323438343736326664653436663432
|
||||||
61386663366533326130626332613562633965633837346237633334303564363136633733323361
|
61396237383030316436306130663039623963343362326465333933373061643165353465383761
|
||||||
32353361396166396334386630353738643438643537303034623830303939326436363239343664
|
66336433356538313761616663363438316165653763306431353139656361346631336538343936
|
||||||
35633731396161623934633864313863323336326535313637653935376436363162363565666634
|
37643363363662626362643333613431363032323938396365373337643235353333393232373862
|
||||||
33613435326539343865653862613263383236313130383234623766333637646464613365346532
|
38316664383838623836343938663231623237306566353036633265316361636565366134646562
|
||||||
34653035393862643039623763323138333334623866316437613435666530396134306665316334
|
61383161313664633736613463393035363637633836636634363231326230613437663438343362
|
||||||
31373364653865323765646535303166373638636134326538343563363732636337653931373234
|
64313362653131316433303561366536373436383865333138376663353035343731363934633537
|
||||||
35303433656137333635383334376237616261393334623835353866323333393630373765613835
|
37663832316265356535643561623435336563663362663766306662326564383865373565333134
|
||||||
33363636323664383136353863666635646563393764633531616135313935333539333664343664
|
37343035363763373236656239376665663536366131306439643331663731656234386432653665
|
||||||
62633465356563353433633564303531633236656631326331323638643666643636396162316532
|
30393632653939393564363634373334623865626231653639356633653136363630306539356361
|
||||||
36313839363164663336326639313066316331303365386130666662366638353834306662303336
|
34396238393339616333393734633835626463353932616262383263336439366262303966333666
|
||||||
34393538346338376330333332383231333630353334646635313037313736613139383832663566
|
39666536363537633030393730343037623265643438313166333064303635663430323435383438
|
||||||
30653137386339623136356663613431303564633966383633643830393636383734306238333831
|
31313364336662363365303262653566303830633930666639656234373037366238643431626562
|
||||||
64363933386532346264666638316161393435653661356162306262323131643535636134396439
|
31373137663434366564373833656233636165636636653738383035393936343063303761353265
|
||||||
64656538313938623938323638393734343464653362636532386663303762326234373930363866
|
66393938643238393338343930363233326463643237633137393566316438393266613731346162
|
||||||
32306235363837396339383461396430373964396533616434373937386266396439303964356534
|
39393630316164613139393436316533333632366638663437366266323837633937613539643434
|
||||||
65663262343461343864633934636536366335326162393830633232633832383166366430313934
|
38316132306333633666633433623565323463636664336536616266306262653064363931316165
|
||||||
64643262313163323531326163323431386532386530326332326130353230653263616139303566
|
34383233343964326363363235343165653339653162326161323932373631353665353164653731
|
||||||
64386535326262326463663734303061613435653964303764386537636132646430626530313331
|
34333665313065306439393431336632356536646334376562646232353938313836313735616238
|
||||||
38323563303738353630653631333535376239393730633535353934316131333039353931616439
|
63623765313564353463653133333536343937663165396463303531616365616363383938313939
|
||||||
35343531353332303932353636643763643035376161643536333365666466616234326237633430
|
37373530363830386338393661306261616432363737303236613232616232643666653464613937
|
||||||
62666464613334633537306431396534306237313032306632323266613134363231313331313139
|
35313631616166386330653730653062646234333432336135316235356166343139316538353337
|
||||||
63316661303830653035376261633539386533373235653839333562656135636433333263303030
|
38663265633166383563623561656632386364663365326662323036316436386639663761323861
|
||||||
65333136653939616231363835616134373038633634656366363266386534343233393730386436
|
33313937343632626561663233323862636335373930366233326163623831363765643236666463
|
||||||
61366536333730646562343333313234663037616461643864613334313464326661366133613966
|
65386234646363313039646266636435366462383266396337383439623437306166663536363966
|
||||||
32343831376266376636646439323566373033643939663261356432623865313833316238363363
|
34333762366161373531633036356139646338363031366561363863393830353336343337363664
|
||||||
34353765396233336263373161323433396437363463626464386463346330653861656162346639
|
32366263343464663338306562636664333035616165363538333764616139663063326232326664
|
||||||
32653738353738396630383431346535616165306638666137616664336235623834616239613131
|
63623264303965353632303230653636313162373061396436643064353632323731633435623232
|
||||||
33643866396530643062633537376565306538313437623462363963363364303738376662323237
|
63313263616339613065313138613032626635646236353462306263376638643966313332393939
|
||||||
37313438633138666434646633333262643333633930396439333565663930613263363638366265
|
36643637653261663830313564316364643832383330353165613666333733646138633432633763
|
||||||
61376165353939633165646330656631323765313430323630303335643362393763663134333333
|
61633431396432313863343466306266666133646266336536646635666631613633346430633539
|
||||||
64383338343939616362363930626235303532666538653561656162396566383865363463373233
|
63313964353937386636626532303532353133366237383566393061326265316163613665326131
|
||||||
62363363623938383234343639656631343831616564633136363332393131356531343137396534
|
38613232366132313762393436646265326361323736643338623035323336326539616432386630
|
||||||
65626639613663323139393061353636313036396163646434613431323864303130663531343964
|
65346333643936343835376364626539303265313239376338653835343232646362656366646264
|
||||||
35363339336436336532303763333564363962313637653438626235353339333438376433396565
|
61626463363665363737323331356361616364626565333561303836313966323461663438333933
|
||||||
32646536383036353864356134623564653436326232643330613736386162656161336165363463
|
65643533346362626137363762363663383761353364363164393062383830383931653262383564
|
||||||
37646138323937343839373736656332643430386630383463393637643463396231303730656530
|
35333532646661623731663938656339323739663964666238653963323232666664313237363137
|
||||||
61633563633338663635353235663931303964333839663333323062646266646466356434343432
|
38626438313265336263626365643763393532613831343763393130343766663335343431613038
|
||||||
38303762336132333132373532623637353731306161316564373935303366666530376639613166
|
31393938303162346236303635306239613335656338643362376539346335373130323935393133
|
||||||
66383533663634333162373436313031643533336436373563356364323236333632303663393761
|
62356533393363626139323632663865373837306337393433353963396231643937373762653837
|
||||||
32343631643566626433306532343764373263666537346538303561323465646237396639363065
|
38306331656262343832333136333536383035323532383766383538653831386531353936363936
|
||||||
64646334633033653962303437396430646332306562623763323733303930623763393537646532
|
66623762663266663135373165303233646262653465633930373832383364386263383831366461
|
||||||
38336439636134666264663339346230326435383365616433363535613235626439623037666234
|
32643533386438613533326566336538633632313331396535326562336263646334336362346634
|
||||||
33393434306662333966316364303261643137356136326637346539363538353465313662393534
|
30366562623663313638356233633433306564303065316337323862663533366366353230373434
|
||||||
34306132613636633936616339636632623733616432323564393565336633366464306661306130
|
64656437626135633932616230333339373934613666383565626535663939313930373230313130
|
||||||
32373465656131346138343537373463663937636337303765366337376666346136643139356234
|
39333434643834356634383133383533396264663831383736326162323431393464613736616539
|
||||||
37376436313834366538636136303965383330663437343363633861396634373764373863613734
|
38326533623734353937373135386664613635636264383130346263326462303531316232386131
|
||||||
39363837613363663337323137616139633935643437353838383735383561346365333565316336
|
30663833633738363533333662376461313331636230346538313764633135303331613039316363
|
||||||
37373231343665343838663235656132613331663638323331363763373539323239306232656338
|
34623937386336646339373366663563626430343866303239363661313634636333616232336438
|
||||||
33643632333863333064666332663437613961393530616330643139663736313733306339346535
|
66616132363337393334646333623737336633303331393030326561613835613164303965393932
|
||||||
31386639363338656462663566313836313066623661653139306662636331373038396636386362
|
65313665303932373331656434353634343561353236643932623464393566333037386334363066
|
||||||
63623431336139316233396432396166633762363662616432616634363639616530663931366230
|
39373163313838663163323666636337663161343664646661616438393333363365363065353732
|
||||||
37396434343039303232363633626532383235376538626438356639386661373330663838663134
|
61666666666563626432383639393038633233663561303561353238613832373633653436316366
|
||||||
65666437363139663932383035303036343633303766343836363435313739363839373938643562
|
65333030623565336266646563333336373339616231366334316336666463633964323630373634
|
||||||
33393733303165373364643961326634396265323962313433653634326436336564313338323631
|
34346131626466316163366362326265613363333933336463623961306637353639346537316336
|
||||||
63666636646638626639323631323335653961363964616461393632316363623162653665333532
|
63383561353339393963396339663738333765663934383038313631363733356562373935333765
|
||||||
34346532313436356235343539343330656235376261326262646364393866343562323663363232
|
38663735636562336564636235306135666530303762623331663931366163363734316539333734
|
||||||
32636539343063323734663732303430306261366361383461363963346633316632623531363530
|
33303638643166303165653062396231613862313165373564626432313961643835383038316437
|
||||||
34343633666136323238336530653335643730326439666637663266363563636466306531393431
|
35653639656362633631336635363035633635623035653439353561343739626637656566333065
|
||||||
33306662653537643835656637636466383965356663373530386431383837373438373436383461
|
61626536366535376237366664646534383534323331633035326632656132646364363133636133
|
||||||
66653735376535656637626231613437336663333132386135666432656638636138383939386665
|
65643065383337356136633534383033396563353034383462386230623261636337303537646463
|
||||||
35346636313439613335653635653861303231663239613563333438303738356439343933326332
|
38356538356434393864303563336563343566306638353138656561663930316230353433386362
|
||||||
35653764613061356465666361323134323331326137353863666364333332326133343039353164
|
61643561383739366362666633616365343235393363623035316263313738633662323961613931
|
||||||
35663264326432366330353230643762623933346363623039656137356135613063366265626334
|
35393234373730616133316462363661663866323466333839656462636133613936643137653833
|
||||||
64666539373230376335623365373866613761373637393066343536386134633131393339343439
|
62333030343861356365616339363134353463656162616636303430356136363638313861313637
|
||||||
39353938643233636562353131336262616439623461363531383064616564616462383833363338
|
37613731663639633865353332653835316631663364646139623631613138633162366432333532
|
||||||
31396436303330336532333661333263346233626532653437633766306230643032633035636132
|
30306432653133333637623631626165633537623265313237323532303566383334633337306564
|
||||||
65343938643630303063336664393532363131333532323566306338343837323431373062393265
|
31643163633934633161316663336334316237353735313063376436393166313033313665366265
|
||||||
31653964376339626632313734366665613563383266376533353664363764366639393563313164
|
35363864386265386139353735663238366663396366653164613833383336653230383763316134
|
||||||
63646661373461323039636338313165306435323530633535353331343166636565373630333530
|
37386633376538366633343963303339323338303263343862396631613733336261356536396264
|
||||||
63343262656538616232313633616166623534643834313138326634356334636635363935343030
|
37623236383565323331306531316563356238316237636631613736316437643965613634666437
|
||||||
61373533373863633630616135626335376136336261646465666135383130633032656465623938
|
32366661346466353261336130363233613036633863303161303931346462633763353038363136
|
||||||
32306433303937363133623537313535633164346536626465366663623430383639386662396665
|
36363762353231373933616265643039666237333334326134396531643463336238303862313465
|
||||||
65393038383136306331323035373432666565373934653763336336323131353732356164366262
|
62346431336663633661303034376165643539323061653363613635343761326363616139313933
|
||||||
64396137353838303934363033393363666632323137663762626163303963386461373166323734
|
64613430353539376131653439346337353733663830306333373966393162366235393364336532
|
||||||
30336135396262323736313037653133623237336532663765323034383766363932666562343335
|
38613936303965646231323138643464636133393037363538373562396230363434386230363934
|
||||||
36623731326232653363663937626136386139663630376564333230313632646334353664303137
|
30656361323137326335316536663231376536646565643363323462363165633139346535313733
|
||||||
62363261316161356164313433666562303965646362626331323538663865386530393864373932
|
61666434643537363135613862656462653732306665623037363638613832323530653836643137
|
||||||
63326161656561393665396461313938316430353131643035353933326430653439643634316435
|
37323233316131343039643137333532653165316137303032373635346539656635363265396565
|
||||||
61626437376565363365303061346536386134356335646137626162663739656631343334653866
|
34663263396265383532306264376430313237616438383639313863663664393066643565623636
|
||||||
31323564663165376266656233316266363232333336356137353665613961303065373735353735
|
30376136373833653462646561396238356638336432376630306465333133623538636333623961
|
||||||
30663339343631633931376562316262356462656132666661383064333838323061616361383264
|
33316263373862643166623030316239383433313365313439386534336532653436633034643938
|
||||||
64666664616231363661636337323839613830363366373530313963613132643836353062646561
|
34613737333933303934653635636239363332376436303063643566663536353266303363626539
|
||||||
32396565313631623639666433663037336338313033373938653336366539653166376237326536
|
64393737383037383336373663383163303538343438306361656164356563653236383563633735
|
||||||
35323134313962613962663630376663333334373431383731383038323965333736356462303566
|
61376362303866646130666234616362323830643238353433613831353635373636306363623262
|
||||||
32633164663734366262383264376539313438343530303364623566383564376361343231386564
|
65393266313430653133316639326330383936303566613534316366613637623465643739323234
|
||||||
39316366373964333837656536393961613466353436333839396331326463356636623738613563
|
36646138353135666265646137653138656262303864653839333839633336643466343066653865
|
||||||
66333030376364383535633637383439383237636539623930623966356461366436323933646266
|
31323538353566663638316136653438353835613137653364393032383935396134333030623831
|
||||||
63363264643238333432346432363339323461646533333266666630663161653031626361366364
|
66333431326362303038343032316436356237373932316531313437613762636238663364356535
|
||||||
62613433333235356438326439306132376234326437316437373537346363303537626632373537
|
30393931613231303936366436316438396535303561393538633465623064623039386430393230
|
||||||
33346534306363323133353163663932633632626561313566383865393461316163616532666463
|
31336630663839336364336332623165613763303331383830663161613966623530323266396531
|
||||||
36333966626638336437666338356437643862303332353663303537613933643338396432663336
|
39393931363365366334643231363732646631626533653636373364393463333661653833653964
|
||||||
65613565353536396132373663663730386664323732616533303037363362663335656236383838
|
35656361623463396664306134313365333664616436313761313837363038343632373032356163
|
||||||
32666463323463363736316534393538373938303062396564333435383834646664346261383535
|
66653030393665626264663761313266383362393565633864636436326238343836383066363438
|
||||||
65653931623934363330366361303030306132616637636366313333303664656363623061346638
|
66656164373936663732333565313466366631336464316335663361663563363765333938653437
|
||||||
38626261313765303832333562383763363935656632343735613935356333346237313436326562
|
63346563356234333936666437396462613262633834333938343635613761396632633462633837
|
||||||
65353230353537373737326239636465656566346635303134363739333139666362333964393465
|
64303563376431643930343366626336656238616162366566613166623637656533653263383832
|
||||||
35663837633132663461383036353065656334303730636661363236646462353238343737633635
|
66643465626636303136383235383739393265313964373238393563613438346132383566353061
|
||||||
38663762656234376233623264343762303434353366363262353232386131303231373832336431
|
39393334643839613538343966383736646664373363373234376639636637326639383734313562
|
||||||
35636434346531376461393330363837633564613363373235623837373130336465383933353039
|
32306163326563366534303862386638643534636162353235626438373237343135376339663164
|
||||||
31363732656164333037316438393532336435663035346139373439326565653363343336306638
|
37313836313863646539396231353864623733396163636136313864613030326534663430633062
|
||||||
30383635656336643934623132383433333962323438303733396131333761643730343464633539
|
62373238313435346637623334623631343339323131643363383863323530333236313632653364
|
||||||
32306664393738353334333465363966613261333532393130366437353433643439316135613861
|
38346165396336623838623630336264313565396436646536393237646163333338346338653438
|
||||||
63303863313133333339323665343539343930356637356262316138613733613465343031376538
|
34396537373535616339626164356635333837623234316630373333646333343034376336383739
|
||||||
30633537396262636262663033656430313831616635363239646334356334323731626530303034
|
62346366383134626162376134643738623965653963303334353936613665313061653661616438
|
||||||
61343362383935343835316561333231323261643838616435313032313636613933363837343336
|
37643032393033313265396536353433323735613766326164323461623238316335623131343932
|
||||||
34613131316239653861626434366338343232326531396530373165663931396136373536366339
|
31313763303235366436613939366634646434326233303637653235323539626264306238353733
|
||||||
64306363393061333333663435666130366630633838333533306163313237653462306461303030
|
63383666636464313064623736316466323539376365376134663763343530333839313165643432
|
||||||
61353830326337333563356165663062316165373939333938373637636164646536616639366634
|
36663238333231306134313866303332623134323631646330373563656431363537323861353632
|
||||||
65353035353836356264346234653431383565373637383035323234316637356530346332643635
|
30306334366131633865633366353666306238326537633461323965303437656538643136386262
|
||||||
37326161333838643166313664616331623532356235323564356261663734613464666133313230
|
61386162626633343266666338363563663965643761636462663333373539396634313766323139
|
||||||
35393763353639323666313430666164323836633138373961666162316433396364393563626265
|
65653564333531373561393239343036616238363162633462353962393336396530383430353265
|
||||||
64373565393965313134613365653062323764346432653630643662663930353738353038643661
|
38373539613339313964356166666630376130313830356137326465653166633135346262613839
|
||||||
34376363633338353132363061363964346162663438626638373032326536363733396634303461
|
34623038396635373936333535343330623338663235653137393335346536343331383034666139
|
||||||
39306432646362666336393437396330643864353437376235313966393837393435373437313065
|
61333965386132666439616639623165663034383633303136393730396232336139306664663233
|
||||||
33303439343736626436613663353739656264313639626633323265643966643566623765653739
|
61613731646538633265323439666535346636333531326230366238653331303830646131343430
|
||||||
31336564393138356265623636306266363861323961636131336331653632626439613461383735
|
61383735386361356262326632663666383162316232393338363633323463613732623064623232
|
||||||
61616265646365636133393031333733363537366262326632363962333433656366313965303164
|
39623231643734363937623134336632623562356531333635633031636536383362343634373461
|
||||||
66663335636363343739313838633235323561343833346332353532663065363439303936303266
|
35633665383130636336363364616466346462663962376164336331663165316661393934396136
|
||||||
34356331633961383264393765316236376130323233346163306536386436366636636139363035
|
36623836636438373761376338303534353832653433396266376638636231616631356262323736
|
||||||
34623938366266343164633532646161633264316334313930383565623766396165663664393537
|
36383466643661363062373062663039333566633763316334626334373663633062376335663461
|
||||||
32373266656134333136393965366538316366313735653535626635663462623036323832373964
|
61316564303432333730333638303566313132646636643639626131343165663063333838333635
|
||||||
61343236616264373563386265656364333230396462653833373338356233393962663965346636
|
36663139643730333630633766383132396636666632366164346537363732353830613939663262
|
||||||
39313261353263373364373034653338383632643430323335306632353331633939383137396164
|
66623433633434646432313138643639366265396333626564623562323865616261663064363630
|
||||||
65313166633632366465663466633733663939303039316265313539303564613038313136623861
|
66313134356536646633366262356331336134366265316166633831653434646232333730356661
|
||||||
37383837653262633365333063646331663562356162333937383538623662633134613364653033
|
66663431653363666639333632366261613764393665393535373934616433653834646361373438
|
||||||
32613537633639656361336236323039383533303661366432376665323432386464393534633066
|
36363164363932663463623161373736353233393934633537613932333732376436623031303962
|
||||||
32616666613938373066313866306330393061396263386563306661643831303266633465396364
|
30643830353665616636663066306536336236373064376530306135646234326131613130306433
|
||||||
30613961636333373462386531373264386332303063663264393861363263666638643836396234
|
62653463306332346530336130623633393636393762633230323063646237626130353335336563
|
||||||
66613235643365316233353262663739313831373561633339396165386530313538653538633039
|
65646538313634333535333564663337623233613238303638616263383764653230343661343065
|
||||||
36353364656136306161636135663464343033306161653365653166643762343035336166316164
|
30626533643465333733366430316162316134323431643166316631623965323334316263396131
|
||||||
66346363646238636438353036353631663135356538663762323030353965386635306630643131
|
64303932663464383131386239336166353363333664353031646462363862663165376565356562
|
||||||
36666330663063626334386431663635646561643464616636633735626132383961663265626631
|
65346539633036343031396662623233613739656633653430663737613662303331396230353038
|
||||||
61653365373863313832366538363864333537643336623633343334633333323561373638613961
|
62333333383462653138653162626333326466653631643634636135663232393437343332333964
|
||||||
34396433366665333531373031336362306364633537646565656630323334643038383535383036
|
38623062656633313762353438313839373935373964393938396536633233613161666166626139
|
||||||
36633431633930313065663337366533323463333162326435626264393162333162656236306536
|
66323363323039323666303934373631343737313866343665643131386431373336333630346261
|
||||||
62323032616162313666303265636636323163303234656230313061363862343736373630626435
|
65646666303633623166643635326165303838646532393663666266303536343634633533346664
|
||||||
37616662393737656132326537336534623633613133343833313135336330303536393364336363
|
31386264656165376264653266663332316536303665323239613630386233373565353763323739
|
||||||
34383136376363306264306237653236326166323032383939616563353432323565666263646164
|
31373232623735366563303731626163356537393034313962653263333831363632343561316337
|
||||||
38346663373335636131366236313134623435633766653266383661363532353931323831363065
|
33663430333836633061643566303638316133323361343931303331353234613065366133363931
|
||||||
34663732643435623436323236316365313861346162613636323138303831353062616263626436
|
33316564313939383137653330353935396532386563623939363336383537386639376262376561
|
||||||
37356230643833323062336465306366613637633834303762323838656132333130646462653766
|
31323837346166396332373735316230653531343166393539386364646461376265373564636637
|
||||||
32393532636463363634636665643439366531303162306133626138653839316433343532646530
|
65663430333564393861343261316562386564393836303834633638346638383633353164303438
|
||||||
36383066303539386136643436393764613937313933396335376262323064663363396133373739
|
34343463353538663431306332636166623066376135323261613735393837363666666465323866
|
||||||
66643332333135333437623266626563356337366665336632333035323537343762376132373331
|
63356530346532656131383835363133353938636630333231353964373765636162613062363937
|
||||||
66303630353430313961356234616438363436323938656466636264636364313431396165623530
|
32393461356530383531363131326164356562383762643832353037393931316666353331396437
|
||||||
33306334656338623237313333386135313961633666363031376334653433366330303262363832
|
37616230383338613931646135306539636335636238373065346236333966393631346266343737
|
||||||
32333139643434623031653137393134343961353261366464383537643237623166396431356430
|
37383135613133383763306531326436323233393231376165633538653036393833373538653030
|
||||||
64356435356566336461663766393537303737393139653132363737343238366361316463306136
|
61336331313430326638373138376661633731306165653661313766663134383163653731393934
|
||||||
38313365333863303734336162666130393531356531623865356431653237343432613238633566
|
63643438353663646539623935356635303236616231646431356262383233643333393531376534
|
||||||
35323166623935336361653530373466653063383339396466356334663066393539356432616362
|
65663437633761646564333366626335336534626362353964316236643066306439366565373735
|
||||||
65643536316137326533363930333966666632393334333536326431633962353362313532383033
|
30373965366137643363653533366430303965636631383664303239623938616135303030373962
|
||||||
63376566643166353136353733353862643033363035353534643062303133633064366332623938
|
65346635663562306566303236663563623036366465353739626564393839643530353033323737
|
||||||
33363862306263663737616364633462363736666530613562643865393466656131663663663166
|
66643665303132346566333866663736376637393966366263636462396637663461646434376566
|
||||||
33313839623739386565323133323263613939623838396365313265363466666235316263313535
|
61353464313337333938303866663036646566623032393036646431343739646436356632353361
|
||||||
64393936396332376635343362396564346634336634336663383566376365353836383739333665
|
39366262376235663139666631643734376461613862613838653062313466663266646139323831
|
||||||
30653038346161623536633361363961623931326265666566316565636662633032663639373361
|
37313961383638353139353635633133636561306361323065323733633532333235323031653930
|
||||||
62653665366338656532373734663138353238356535353036336331383635306236656130613633
|
39306534343635646135646337353161303737396266343135363938383664346338373730366531
|
||||||
34323962316636333664616664313163643061623836316432366664623164303365613161653466
|
32396533323264666433616330373232623330383762323263333432383066313838663931333838
|
||||||
39333538343435383066343336313564386136363363326231353838373437643136616336393561
|
30636230333235653238356633353030663136343462353530643531376234336161656361633438
|
||||||
62626430393735326366353839393166313030376435303039643739373337653635316466643236
|
64336361636437636236373532346131393431373537633663396534363764653563393064313665
|
||||||
64646237303064306663396439316431393931626431616630396538626639636432326561653562
|
36616266333938643061613361653532356133663431333435343634373564656531383035353632
|
||||||
61363338623863363731356566306231366336363739623735313134613933373238303765386234
|
38383863616133363937633937643062623831356232346238326236313536393738346263313530
|
||||||
61363034313139626636643533643738663266343235333465663632343533646131393130666632
|
35303865666530393330343936356131326164653765623031323535363266393265333232333464
|
||||||
31366562643166616430613030333832303636616231646239646435616530343032376663653031
|
36356536613264353962323665333238666638373637306431343566656532383735363863313366
|
||||||
65636561353734656562626463643361633362313861373233346633636438653335323761373165
|
61666465316437383934653534363766356635666364383036366664356534333465366530633365
|
||||||
31613130306437346630313466393735643434376133336565656566323132653962636630313431
|
62353134313939373666623966666335663031616563373931386564383039613833323434326238
|
||||||
30386136643462306261383135336362356161666435323132626637346365396436653835363733
|
66373132323762653031363763353831353336396137376364383138366264396134343262306133
|
||||||
33393732633464626437366439303931623962313433626533363136653939316633326432356565
|
34393865653666663035633637373661366463303335353031373431386161616437353161326638
|
||||||
62393332656238303763363732623163653663373536393265343763373365316337323564353064
|
32633339356434633363313030636464376535383534346436663634396636376630613634653635
|
||||||
31616634653837396438303938343562613761363766616561643032643065346238383734376433
|
61383734313234396666303136393633363536326265643863656562356239326536353632363764
|
||||||
34613931366331366138346636376566646539303430353665626263643465313133323939613361
|
32383663306139373532643037303365346336643331336331663630333136323437663930333366
|
||||||
30613063656665666439646362656136656439643934623730383736353031363633653036643161
|
34366162356435316436313663633936323931383832373465323537653436623237323666333737
|
||||||
39366163663035663135343462336564383862336531656138313431316263653064306334633062
|
35303962396266336633623563663064613934613837356563636330323431346334326234663433
|
||||||
39386538616361623265663536636130383362633934313764373538643636353237323133303266
|
31313665653836616631653661626162613032313737373939323232386635393533363962373966
|
||||||
65613765313838363236656334663635636264356331666134643063636334633131353734623632
|
61396639366131323131623436633439363234333032636137646230656463306265333334393736
|
||||||
38316637306163363839366536333365663337383632376230373137386535303666343362316663
|
32323262643066306631316336356461633764646664653339626162366366353566373565653635
|
||||||
62663333313634313361373463313566363063363665323565633935303738316135393264663935
|
63363438396264666565383164363639303637646330333064363762393731613133633934666131
|
||||||
63643662333430633535663130343930373763366430306337656162383537376564653439343430
|
65323932373235373863623434373439343661396137656564396663653336396633393331663032
|
||||||
35656430366365636165386333663566313034646564636261333361373030663633373861323065
|
65363432373338313137356666623338303064393232636535356262383038323166393235653338
|
||||||
33363463303135336436613563633135353531623339616530353538356130656263383265353735
|
63613536626365666437376638633665626531366330313230663135353538303232356366326161
|
||||||
66383964313665623133303130323335393339663331323431363438613637636665623634313862
|
64303863643030316564643262303664653463653463393461313538343464313638653461643966
|
||||||
66336533626266343831363732613331653431306462313830343830336339663636396566343134
|
61653733343339613864666665373033353062363165376530323362663763306132336230396533
|
||||||
34396137396461376433653962613361323334393766303330633237623963653033363532613761
|
64373762626263626638643763336635616364323561633536326165326466393736333836376462
|
||||||
37316139336234366633343064313965393965353461383433366630633935336233383962616662
|
37303365396539376538663866633761313136326563623365373739373439393630376262373138
|
||||||
30356234663661343232633435346532313737376234623336366161303937623964656331633935
|
35663064636561626434313035346131633530326431653664373036356464313435333333386438
|
||||||
62333537303536333062613132646237373031663766623730666330666364313634366362363437
|
34396166643435663734363434316436386637643836313438333731323261303737653932666664
|
||||||
34643634303366633236363665643934323864303732613461326238323637393733393964663562
|
62313761383832373235353239313830326635633430313762656265313838623163366637633736
|
||||||
30386664636139313335333238373632353936646432326436666665326365646565383766316362
|
30316230353338626561393763663932663666613331653834346131343434663232623434353838
|
||||||
62376463626338643937663163336166663465353936626332303739393762313235323535366636
|
66396262383937353530366163313532343731353338633065393632353264343136386538616334
|
||||||
63666438636230383031333838613333633734623134363833383035623564613637633562323263
|
31383365666534303564616364343464313233643932326539313738386131646535316638396133
|
||||||
64356662376532333261316537656234636338393738613730656530613232373662363431393630
|
35636339396339323765336137626438643631653963333362356438663531626634633336303733
|
||||||
32323230346332613038373530643563353162366366636338623566373065373637623638333533
|
34626138336238323063623536333837336666613630626462636661353962303330323134326231
|
||||||
34633333663832393438616266653563623338626431636634303234333636613033333831316264
|
66396665383033386634373766383461616334383130356337323763353130653732333136303166
|
||||||
36333664366262343261373163373763373036653866663539343337323761633431313435323465
|
32303364616239616439336639633233666230396334666531643464653433333532313737363835
|
||||||
35386539636338613666393064303363303263323565613837623866633031303834363837626531
|
63623737383830646334643039643739636235333963643163336464373133333731633765393764
|
||||||
31363838326235643163353731333938386264323730323036333066326463326537613530373832
|
66616236346430323464393739326364333735663937643737356631393766623734636262336266
|
||||||
65303663613361336565643366316238336135396334303432393936336635306434653930643631
|
31376535613266613362313363666262383837663533316636323262316364633461656262363762
|
||||||
65393438643630313330383061643031343663393265646664356636313035313961383634316466
|
39613635343165326164646636343431623230353561363566666636656237373363646230363435
|
||||||
36633139373564623466386264303664373763386534326234323337633732633232346136366133
|
64343435633039336337313562353638363664373137396366303839303766316230323861396331
|
||||||
61363939336531633265396136623031396534363031393164646661383339653232393463636439
|
36346230353163336538303961626261383031366330613636323263346139356339616537393236
|
||||||
63306438613837313136646232333434323430643130313937376230613531383461306262366636
|
30306537663861323931323536373833643330313338363435383461383330656266613439333262
|
||||||
61633137643164653739343663393335373065386339393531343930373161376434356666306132
|
39643930313537386538323436636134643639656265336137666663666637363135643137333063
|
||||||
35383763613966353662333362396265326532303238656133316332363335346437306164653230
|
63346133313066643566643432356537383532326163356437386363353965303737656261656132
|
||||||
38333263323064653533373365336166613431303231373062376663343261663135623937373163
|
63386338643565383033353832663634633736323432336561383763356239303936333038396639
|
||||||
63613239343034303263306138376430613335643862303633343637373430336131623037373537
|
33616364346165323962313033666335323233343462353434663832386561376339666366393161
|
||||||
64323838306233396435656339306132623266363961643166303733373537333135303665336239
|
62656532636238613264643833336264616634623236643534363565303531656131646263613830
|
||||||
62623439623865363736343631613432633264363733353834386335333330313933383433346137
|
30323161623864386130316431313435383637316337333032633035303564636534326634646565
|
||||||
32386432393765356133353736306430376665626665663964313037323136326164653435383332
|
32333465303061626536303231386434363033636665366339376432376432383134353938663331
|
||||||
61346361313063333137356662383533623734633435646638363537303037393638316439316561
|
31323633386631383038376438313761323839646139616564383734366534376134366330336166
|
||||||
62376361363532623266383635323330633033333536636262386239353732633237663139393839
|
32323430663335653335376637313065383731383936663765303664356661636366616136656535
|
||||||
33633537353136376163613334393430663366636533616265633133373563653962393238373862
|
65346637313766326233376232646137313839616339636161613066653133613230623838326662
|
||||||
61376639663631323231376631353964643162616538353835363937663661313632333333373933
|
62313037393231643966353638316164333563343264616563633365333437356133373336386561
|
||||||
62323539643630303062333063386163656530623061613664376436623732386666336162396533
|
65323665356463326537326239396339613839306131656538323066656134333833656539623462
|
||||||
36343563643032356261363234663264303762663439363664353966313561623761336138313165
|
61386263646434636236643534636464363731656566383831656635336437363962306263633233
|
||||||
37633630663131313565346539396234363565646530386266666232306262663961663636326536
|
36646263636638396461313138306563656565613336356132393532626161343863633239636438
|
||||||
64653638643935636365313561313964653764363766623433353365376161623333356164346232
|
30393663323562623764663638646431336265303764356233323566613934306132333735623962
|
||||||
36646630666333383234343931343161646463633134316466616436336634336661356161303764
|
36626462343039313765333535633835303664393664366635393264373139346639636537313038
|
||||||
62646362333139373761333035386666663561643837623665656630393432303839666666303339
|
30383133376361303336363239653931653436663630666665383161333138346239616338313338
|
||||||
31646461613838353033343665396239616538356164333436353133653134313738343530313366
|
33343138666263623534396239633462383936646564313833373435346432323532336466373937
|
||||||
33646530373561633834383865393961353032656339633732646533363535343630613937323461
|
64386537653332646439643764643563393738323565346630616432316164663161313337366531
|
||||||
65363262643937373638633838343266323966623039616333656538363362333962353066323363
|
38396564386635323466386339613765326638333532623639303136656331633038336539393062
|
||||||
36326138323132333837343130363735623861616334343130363064303030306361336161303832
|
66613035306632373562666466306336353333333039346134383263313834666434313237376530
|
||||||
32343534623662393566653131363866343637373930643434326338333362646233383231636235
|
64373539383166363864366531323333323565366465383937333732613437366264396532616433
|
||||||
33636433346464363766376634636533366163333064313534313831636238376639393533616566
|
37623438653438656162626666343033346638636564653061396562343564626535616233353334
|
||||||
66333633613261336361366234643966663536653736323235663030626234386166663033376234
|
63373565383039383737333534663637666231323132313162373436666362383963303162373835
|
||||||
32623435383837336330616235313165636537623462626230346535663662386337366532363733
|
62656434313463336462643764333735653166306265636638326131613736393230326634333135
|
||||||
39306631626262623630393137653763326234366139626461643931613736653932316466396163
|
36666633626235343630306637386164313562323439663037386166303430393065386238383434
|
||||||
66643038343336326533366438356562333561616138383330643235663633336262303266613630
|
35396664336331336561633232333934343832626230343935393433326430653632393562366532
|
||||||
61346237313730303666346639366439313865663430643536336338313130656438613831383633
|
37346235343762336430653733353235643831633230646135333761613832336234646262623865
|
||||||
30613862363961373732333863636134623431626361396562393437613533383738613761363835
|
34386230623338613764303536343533623432336332396665626131363364643465666639636664
|
||||||
30363831363963623133653038383031323364346237326164313232363936656132353732373463
|
32376461393532313839393766623463386537643733616161313330623739626533643562303262
|
||||||
39383737303034346163623737353537646433366539663963313566363032343836383134636131
|
37356339353836303235343762393838343363623331646464646163363363663638626433636230
|
||||||
66363664336435376563343765623737366632383431656166303639366661633565623331613530
|
38323038363931353830633737383865353934373238313634613961373566336639326131653335
|
||||||
39396131613161663436333736666536656364663361626633333637316130646266346165323632
|
32623238313339396261333531373437336266363964386237366663336164323566313864396337
|
||||||
35623062353732616535373166336564383166663861613239373836646637616333613639613862
|
35636566616462353565383731386236356230303333336431666633613264373331663430346434
|
||||||
66396434333039393135623733653938336233613235363134636362666363303337383335303136
|
36663166363430373534346236363332346562653463373261346331653032613436616637623936
|
||||||
66313061323938643432623833363930303236633462363663323362626332363565656135363464
|
63393663666230663835623835633936656538323739356231353563623939383463353330326464
|
||||||
63376266653132333565616363383564353135656431393062393333626336663738366331383737
|
38373531653537376637633436616335633866656163303937636438643162653231393963356537
|
||||||
30626239366666366263353662333166303063386364366530653731353135313761343461646238
|
63306630313635363637656665373337363232336135346230643765376631343734303766633764
|
||||||
39353531656232616530633330393130663862623964363565326233643431343432313039333234
|
66356236643338356631373832323863353866336333316333626232396463303466643434646232
|
||||||
63336232336535373032383933393763623538353061396534373632323165666134333465303066
|
65623838353539353962346661383865626532613930363330326334316565343330343538636434
|
||||||
39333366303231363863333133643062333230316361623635656663323737643965663563376564
|
33626433623361313430313361323966353732303035626366323835383631343634316634623562
|
||||||
34343134306333386663356339616461646639303835333333623363383364666436643436343533
|
39313166376365373638303338326263616566643566636361633332373862623037336163636366
|
||||||
38373364363765343265663138616439316633623865343939353465353466613065653366336230
|
31626365663862303563626130393266316237643266383139643864613134373734663238633639
|
||||||
39636664303335316538326136356361633366373761333635613139633665616433386162623965
|
33613761383431383463383037313139393833633761633635356538313635383166386639326362
|
||||||
32636266303031336162626132363931363163313235363865353334616137643934353637643633
|
36346662626139353837376565633633363964343665356632646232393433326437353532613365
|
||||||
33626137376132353735303737343433323462343830633731396565383037313232313862396330
|
34323666633131653233393062366637336630303662306634363262666439353964653436363534
|
||||||
65383564386564353634636430393562623833373533626632333530336266633038663333663063
|
64656238363036343164656434343262393936306230333639393932663233363062666264656361
|
||||||
32313534666162666134373533313730663730333933613762303238323065363236363166326361
|
64363233303636646138323662623532333063646339306635633039653931333865356535396665
|
||||||
39633938373861366133316261303665356661393830366131343138623138616233623961386233
|
38346362366165336339646162623739653433353966613762373534306634643530316538356433
|
||||||
65356634333833313061313134333364306238623637333733666366373762383732663037643338
|
65393034366562663466313432396431636462383239326438643437316361343561393234306338
|
||||||
33333263356262633366346130386337396263646333636134333935343835386366306663663962
|
37333664666132343263343763333137316564363963306663363265376263393964386630646636
|
||||||
62306232653735656365643533653635366232326638386563393365376464653532656136656266
|
66316334323439663630373131643330636266633032376236383564643138356337353566363966
|
||||||
62393830656339393033343638643334313765383766336338363331386135373864383939643166
|
31323662613461353336396637366636366336353763666264626431653935663332363930303633
|
||||||
66316437666434643266326235663632363161663338336434313134643533383634633936336665
|
64643862393963613936323838653661646464353662613430336535363833663161323764303036
|
||||||
62653638346633336331333364623363376538373562663437373933323932663533333733633036
|
34396130633438323566366566303932363937653536653638303931646535353239393738323337
|
||||||
39386333653636646133366461633237656166396664623136633863653339633463346135393566
|
31316237356531373034646565653138396536326339376135613935366130333061636135613164
|
||||||
39316161613532386432306364333662313261346631636430363061376332633431356132663066
|
62386134626234623734333339393763393536623964343032316666366632633863643839353530
|
||||||
65643137303961383539663733653863346131396232346263393934326239383735353565376533
|
63366631646535323133353235613338383864613265613562653861653138333066396134653833
|
||||||
33356438336463323531326561326264396635623734663835363039613162613762393662363562
|
30313237646466333139323037376564646161613131393232333233633863396536363038396165
|
||||||
32646634643934393263616335633933386564373162303766376661323864306230376533633539
|
66643163386337333666626261336537353461666331353162346330616332616330653936616439
|
||||||
38363032353639633735356266616330396333373665373035643334336566636534383831323862
|
62356635323036323363653566313764396566346533646437323932366134303830356331303730
|
||||||
33623866386539376131393531653562643430663765316539613438663733366566366533393761
|
63363933396337653239343163366236386162333032356465326237623563386234656339643536
|
||||||
31303836636533656535383165323232353065663338646337323530363133393263653930666334
|
38393636376536616638303637393537393465656563353232623435323730353561303830363434
|
||||||
62323836353563366637383138383864323438353339653434306532336164306238633437646235
|
64346532303834363338643838346163313963303638346239373936303335373039323636343534
|
||||||
64366536393039323339616130663939393562363935633435373932396462323533363838356663
|
62623563343361613166633465373966343534633864303564393766656465393964396237353464
|
||||||
34343138353130383734363234366661386638653834643661353031353031343562613461393966
|
30666532393332323162313464663931356335333263643332373431343036636536316435303765
|
||||||
31386463366635353965386462653461653866393864613063636564313638623734663864333334
|
66633161653136653038366162353538623563653237666131383163333636373035643062626236
|
||||||
36386239336431316338643761363566636137333161663833623633376262623830383961613931
|
36303837376161363762353964363239656565396666356462633864343833373237303362316161
|
||||||
33666136653631663131626631643266656430356364343138353663326435656663373035333934
|
35323931616535633166646163616535363863616237363238316264363733636230323864363530
|
||||||
33636339386639666532333763393735386231376666323733333030353239373333383264323130
|
32353533666339656464303234366562616332633434393736663631656434373730313165313062
|
||||||
35303437616433393231653739653230326263616233373166316237346138336364353339653339
|
63373939313332346235386566613537343339363365623836313835343165386133643837336161
|
||||||
33353132636262323030643465313231393763613861343734363031386563646337336562336463
|
31666139393664363765646233323965653831333435376565653132313861613565393833613463
|
||||||
37303232663235313335336435333032626432633531306638313561646236616230306135313639
|
34623338313765386331643235303361363033393438353432383963663133306465613530323434
|
||||||
63633938326266656138383635653339393238303964386663366638643734633866333432373737
|
64306364326261303433623062313261613532303333656136363536353066313630363633353938
|
||||||
34636433613566366132613864643135356136336162643066633035663533666237376437333762
|
63623566613262646531616332376333633934666337376638616231653939646661316264376531
|
||||||
64376262633064333635623438353732376466366338323636643662323234326139316534646533
|
37346436323339633839393665383735653465313538366262366538623462306439363730386666
|
||||||
33666432303636373963666334376263636634323532643837316361376338616430346464656465
|
66383736623636333139346434663933303766333130323333383536316333363165633762336235
|
||||||
33373336653539303163336239613038386230363839353532336335303564306363666665333530
|
34613638626161326165346662613433316164323533376265353261303535336431323065623964
|
||||||
63393863363232653138356234323161323931353932326137653430363836323863393035356230
|
32636265653736363763316430636133376539376361616631643965353732656336333835356536
|
||||||
61633161623761303335646435383934663135633734346433643639383932643662393264626430
|
38373933623063316135353762663735376533663065336233363133613833373464633365346339
|
||||||
38303735343033303765316365353834633461386636653662393538643339346230643335656438
|
36613530663735663735353264373966643134306464343431633963366337616239623135663361
|
||||||
66386164326535646335356661383735366134613939636130383034633734313739333635376233
|
61303532323564306266656537636264663739356235393535633139646131346662353437306138
|
||||||
35623130353239323237386337623962616536643463376632613631646436353630366137343838
|
63313239623832376362353632326563633562663265623534643032306562343535613035656162
|
||||||
30376136616261303235643437613864613031346564363465646533363033303566613061363330
|
31353862366233663535396134643433306338333638613737623161376334653631316236383532
|
||||||
63306530323938383737313438333139363862366331336665623139626135353236316664643661
|
66316364313637633732363935636266393364636635373061353132383866306330333734616562
|
||||||
38343165383537643136623661663730383635376263346131303332646464376263303464373734
|
64633132336634643034633834623536383631363933653463663364643262623565393461613735
|
||||||
62366331316166633631663163306237343862306365326339653363336638633830646233363665
|
31343962333461393431643233396561376365666337313961616338313736373439323433393061
|
||||||
61393461326535653736363834346163343736666665623838643937306539376266393761633263
|
34636665323331653731346566656164316163666131396431333063656634373433306262666437
|
||||||
39316163356262633137313261353463373166633735663035663135326437383534616239343661
|
62363939326131663035633264613861376131623332666533653666373262613035636437346461
|
||||||
64633762613833653365353234363539643533353239316238636537323064353337316361353630
|
66336362393935633862323631393163373633306334366434346434333664313732636337313165
|
||||||
39343463643236656431623861333734313838346430356630656131383763386534373635666331
|
31356132646265346537363761393462393630653239396535383164323739636632306531626531
|
||||||
34333137346266353265376431623935336366623461666237666334616634613162623761346161
|
63643536366464383831333663376136643435636361666266333035616530333832336163636436
|
||||||
35366334313837333939306563316236313839623236373234623635656138656165313662656632
|
32613437333336323332376238623336373465323937353130643232656238643334313332303465
|
||||||
31343961363864346438333932656264363932646132366136663339363930386435666639663135
|
32306539626632323062343230666636653566376530633263373964623364363234316264346462
|
||||||
61313037303762626537393330366131616532646666613338393032353837643835353436383539
|
31656665336165643831333039376163623332633634643961316635396533356531323134356162
|
||||||
30323064656161636534653664336232613565626162383035636131393539386633643436653633
|
36363731373337376265303261313737663235306164666532303539656565343462333034366530
|
||||||
37343339663531396533323432643464393431623338343537646138653236366331366261663631
|
66643165646461303235653435613266383034333664323165646238616236663339633361383565
|
||||||
65386666313531323031353762363437653561653534316463666539636439383361373861646331
|
31616438623866633664323037343238383663343936643636333361633666336638333262316531
|
||||||
66626434326566653534393964643938393463303930613734336161356431363230363261383139
|
36663139313466303736336331326666666638333938613031353439313765343530626537393933
|
||||||
38356663323539636533663237306566623062333036666636663637306332626139333738386365
|
38383863643337363038386135326463363736373434646434343035613063343738383965366434
|
||||||
38363635386439646363626631366430393636613763646539303939643764643966353461343466
|
63613038353065353538323130323163383763383030666335383066373536336166393666333763
|
||||||
39363331343334653637656561383638626637646230346665393366313366633366653839663839
|
37663861353636633464396532666434313363323838346235643364393039366566313264333538
|
||||||
39333263313566626135326633653238626566633536353461376462613462303830353638393161
|
30353962613263623838656336353134373564383864383130356664376531626137353064643562
|
||||||
36366433656261313436663466366433663138613561386336383262646663313836633564353266
|
33393261346662346538666130303138623765383166626537636363313138646536623436643666
|
||||||
61623139323961346564623939303339366233643231373031626165646364376138383935626166
|
39626461383038633461306431363364636364623333663766396365653732333433346664316139
|
||||||
38373833613033633630313965336438626363353031346630376532393932353961363732623631
|
39333961366433633238386335343934383134663133633030386133386433633261383332306163
|
||||||
35613038366664663337653230653134373861666430343666623165306130306137383163396235
|
34373536346335616265666666386364646461653433386239653730656635663665643233323630
|
||||||
66633035353534386262663336646361303236623365613736383938333261653462386636376531
|
39616336386238333830363465636437323762633833633938323731653538613636653239373333
|
||||||
37616539376637386462373063316535636365626634613462623232333435313733326261373230
|
30636464343262356165313932356666663765376136663065666433616639613539323237303238
|
||||||
66373061336231663230386133613665396263646563376534313830316439623363653165613666
|
61386637373130626265373336326531653631343339613938376136346262626362653237633164
|
||||||
62373063633863656566643333636461353339396635396331633734623266306430
|
61616636626563613738613964376561376262343837396430653438623066663937653032326530
|
||||||
|
33396334653131356337376638356664313333343366373139623766393736326438376130646564
|
||||||
|
63333230306139303238323235353061616666643764333864646365356135376638643038313037
|
||||||
|
66316636643165333739356530663962346135323431386535356161393239616231326432393439
|
||||||
|
38326163363938373330386262386664313761613631356430633861346631623765663062643865
|
||||||
|
61353937383533633638313138316466313863656136393666353730303434613031313731333534
|
||||||
|
31306361613234626332386339653162393734623536613638343164663963326532623133663732
|
||||||
|
37343336326133626132383465306266623132643861376564636461383132306561613635373135
|
||||||
|
65386432316133393134386334653633666330623039336335396433623530633765383732333635
|
||||||
|
6431363665353131633562626461643631353938376163613361
|
||||||
@@ -93,6 +93,15 @@ paperclip_server:
|
|||||||
ansible_user: wed
|
ansible_user: wed
|
||||||
ansible_become: true
|
ansible_become: true
|
||||||
|
|
||||||
|
multica_agent_hosts:
|
||||||
|
hosts:
|
||||||
|
haunted-mansion:
|
||||||
|
ansible_host: haunted-mansion.wdw-labs.cloud
|
||||||
|
multica_device_name: haunted-mansion
|
||||||
|
spaceship-earth:
|
||||||
|
ansible_host: spaceship-earth.wdw-labs.cloud
|
||||||
|
multica_device_name: spaceship-earth
|
||||||
|
|
||||||
multica:
|
multica:
|
||||||
hosts:
|
hosts:
|
||||||
muppet-vision:
|
muppet-vision:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
hosts: claude_servers
|
hosts: claude_servers
|
||||||
become: false # the role escalates only where it needs to
|
become: false # the role escalates only where it needs to
|
||||||
roles:
|
roles:
|
||||||
- role: claude_code
|
- role: claude-code
|
||||||
vars:
|
vars:
|
||||||
claude_code_install_method: apt # or: native
|
claude_code_install_method: apt # or: native
|
||||||
claude_code_apt_channel: stable # or: latest
|
claude_code_apt_channel: stable # or: latest
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
# Example playbook. The role takes multica_pat as a VALUE — pick ONE of the
|
||||||
|
# lookups below (or any other) and delete the rest. Changing secret provider
|
||||||
|
# later is a change to this file only; the role is untouched.
|
||||||
|
- name: Provision Multica agent hosts
|
||||||
|
hosts: multica_agent_hosts
|
||||||
|
become: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
multica_server_url: "https://multica.wdw-labs.cloud"
|
||||||
|
multica_app_url: "https://multica.wdw-labs.cloud"
|
||||||
|
multica_version: "0.6.5"
|
||||||
|
multica_node_version: "24.22.1"
|
||||||
|
multica_workspaces_root: "/opt/multica/workspaces"
|
||||||
|
# No NAS mount under the workspaces root yet, so the mount guard is off.
|
||||||
|
# When utilidor is mounted, switch both lines together — never set the
|
||||||
|
# root onto a mount without also setting the guard, or a play that runs
|
||||||
|
# while the mount is down fills the root filesystem instead of failing.
|
||||||
|
# multica_workspaces_root: "/mnt/utilidor/multica/workspaces"
|
||||||
|
# multica_workspaces_root_mount: "/mnt/utilidor"
|
||||||
|
multica_agent_clis:
|
||||||
|
- claude
|
||||||
|
# Merged into the daemon user's ~/.claude/settings.json. Mirrors the model
|
||||||
|
# mix already live on haunted-mansion (unpinned runs on sonnet, subagents
|
||||||
|
# on haiku), and stops the npm install self-updating past the pin.
|
||||||
|
multica_claude_settings:
|
||||||
|
model: sonnet
|
||||||
|
alwaysThinkingEnabled: false
|
||||||
|
env:
|
||||||
|
DISABLE_AUTOUPDATER: "1"
|
||||||
|
CLAUDE_CODE_SUBAGENT_MODEL: haiku
|
||||||
|
MAX_THINKING_TOKENS: "4000"
|
||||||
|
BASH_MAX_OUTPUT_LENGTH: "20000"
|
||||||
|
|
||||||
|
# --- Secret source ----------------------------------------------------
|
||||||
|
# This homelab keeps Ansible secrets in ansible-vault encrypted files, so
|
||||||
|
# that is the live form. One PAT per host (named daemon-<host>), held in
|
||||||
|
# the global vault (group_vars/all/vault) as a dictionary keyed by short
|
||||||
|
# hostname:
|
||||||
|
# vault_multica_pats: { haunted-mansion: mul_…, spaceship-earth: mul_… }
|
||||||
|
# A host with no entry fails the pre_task below, by name, before anything
|
||||||
|
# runs. The indirection keeps the name greppable and the file swappable.
|
||||||
|
multica_pat: "{{ vault_multica_pats[inventory_hostname_short] }}"
|
||||||
|
#
|
||||||
|
# Alternatives, if the backend ever consolidates. Each is a one-line
|
||||||
|
# change here; the role is untouched by any of them.
|
||||||
|
#
|
||||||
|
# HashiCorp Vault (community.hashi_vault):
|
||||||
|
# multica_pat: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
||||||
|
# 'multica/agent-host').secret.pat }}"
|
||||||
|
#
|
||||||
|
# 1Password (community.general):
|
||||||
|
# multica_pat: "{{ lookup('community.general.onepassword',
|
||||||
|
# 'Multica Agent Host PAT', field='credential') }}"
|
||||||
|
#
|
||||||
|
# Environment variable on the control node:
|
||||||
|
# multica_pat: "{{ lookup('env', 'MULTICA_PAT') }}"
|
||||||
|
|
||||||
|
# The role's own PAT assert is no_log, so a missing dictionary entry would
|
||||||
|
# surface there only as "censored". This checks key presence, never the
|
||||||
|
# value, so its failure can name the host.
|
||||||
|
pre_tasks:
|
||||||
|
- name: Require a per-host Multica PAT in the vault
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- vault_multica_pats is defined
|
||||||
|
- vault_multica_pats is mapping
|
||||||
|
- inventory_hostname_short in vault_multica_pats
|
||||||
|
fail_msg: >-
|
||||||
|
No Multica PAT for {{ inventory_hostname_short }}: add
|
||||||
|
vault_multica_pats['{{ inventory_hostname_short }}'] to
|
||||||
|
group_vars/all/vault.
|
||||||
|
quiet: true
|
||||||
|
tags: [always, multica_pat_check]
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: multica_agent_host
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
# =============================================================================
|
||||||
|
# provision-wong-host.yml — spaceship-earth (WDWL-22)
|
||||||
|
#
|
||||||
|
# spaceship-earth runs exactly ONE agent: wong, the personal vault's librarian
|
||||||
|
# and its only writer. No other agent may be pinned to its runtime. The
|
||||||
|
# isolation is enforced by the OS, not by agent instructions: Obsidian Sync
|
||||||
|
# runs as `obsync`, and Wong's daemon user (multica) can reach inbox/ only.
|
||||||
|
#
|
||||||
|
# Run order on a fresh host:
|
||||||
|
# 1. ansible-playbook playbooks/provision-agent-host.yml --limit spaceship-earth
|
||||||
|
# (multica daemon user, Node.js, multica CLI, Claude Code)
|
||||||
|
# 2. ansible-playbook playbooks/provision-wong-host.yml
|
||||||
|
# (root LV growth, headless Obsidian, vault folder isolation)
|
||||||
|
#
|
||||||
|
# Step 2 needs Node.js and the multica user from step 1, and asserts both.
|
||||||
|
# The Obsidian account sign-in is manual; see roles/obsidian_headless/README.md.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- name: Grow the root filesystem to fill the disk
|
||||||
|
hosts: spaceship-earth
|
||||||
|
become: true
|
||||||
|
tags: [wong_disk]
|
||||||
|
|
||||||
|
vars:
|
||||||
|
root_vg: ubuntu-vg
|
||||||
|
root_lv: ubuntu-lv
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Disk | Find the PV backing the root VG
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_root_pv: >-
|
||||||
|
{{ ansible_facts['lvm']['pvs'] | dict2items
|
||||||
|
| selectattr('value.vg', 'equalto', root_vg)
|
||||||
|
| map(attribute='key') | first }}
|
||||||
|
|
||||||
|
- name: Disk | Split the PV into disk and partition number
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_root_disk: "{{ _root_pv | regex_replace('p?[0-9]+$', '') }}"
|
||||||
|
_root_partno: "{{ _root_pv | regex_search('[0-9]+$') }}"
|
||||||
|
|
||||||
|
- name: Disk | Install growpart
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: cloud-guest-utils
|
||||||
|
state: present
|
||||||
|
|
||||||
|
# growpart exits 1 with "NOCHANGE" when the partition already fills the disk.
|
||||||
|
- name: Disk | Grow the partition
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [growpart, "{{ _root_disk }}", "{{ _root_partno }}"]
|
||||||
|
register: _growpart
|
||||||
|
changed_when: _growpart.rc == 0 and 'CHANGED' in _growpart.stdout
|
||||||
|
failed_when: _growpart.rc != 0 and 'NOCHANGE' not in _growpart.stdout
|
||||||
|
|
||||||
|
- name: Disk | Grow the PV and VG
|
||||||
|
community.general.lvg:
|
||||||
|
vg: "{{ root_vg }}"
|
||||||
|
pvs: "{{ _root_pv }}"
|
||||||
|
pvresize: true
|
||||||
|
|
||||||
|
- name: Disk | Extend the root LV and its filesystem into the free space
|
||||||
|
community.general.lvol:
|
||||||
|
vg: "{{ root_vg }}"
|
||||||
|
lv: "{{ root_lv }}"
|
||||||
|
size: +100%FREE
|
||||||
|
resizefs: true
|
||||||
|
|
||||||
|
- name: Headless Obsidian Sync with vault folder isolation
|
||||||
|
hosts: spaceship-earth
|
||||||
|
become: true
|
||||||
|
tags: [obsidian]
|
||||||
|
|
||||||
|
vars:
|
||||||
|
obsidian_headless_vault_path: /srv/obsidian/vault
|
||||||
|
obsidian_headless_device_name: spaceship-earth
|
||||||
|
obsidian_headless_private_folders:
|
||||||
|
- everpure
|
||||||
|
- wip
|
||||||
|
obsidian_headless_shared_folders:
|
||||||
|
- path: inbox
|
||||||
|
user: multica
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: obsidian_headless
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
# Directory layout on mad-tea-party
|
# Directory layout on mad-tea-party
|
||||||
gitea_compose_dir: /opt/gitea
|
gitea_compose_dir: /opt/gitea
|
||||||
|
|
||||||
|
# Image versions
|
||||||
|
gitea_version: "28.2.0"
|
||||||
|
gitea_postgres_version: "17.11"
|
||||||
|
|
||||||
# Gitea application settings
|
# Gitea application settings
|
||||||
gitea_domain: gitea.mk-labs.cloud
|
gitea_domain: gitea.mk-labs.cloud
|
||||||
gitea_root_url: "https://{{ gitea_domain }}"
|
gitea_root_url: "https://{{ gitea_domain }}"
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
# ── Compose & env ──────────────────────────────────────────────────────────────
|
# ── Compose & env ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
- name: Deploy Gitea compose file from boilerplate
|
- name: Deploy Gitea compose file
|
||||||
ansible.builtin.copy:
|
ansible.builtin.template:
|
||||||
src: "{{ playbook_dir }}/../../boilerplates/gitea/compose.yaml"
|
src: compose.yaml.j2
|
||||||
dest: "{{ gitea_compose_dir }}/compose.yaml"
|
dest: "{{ gitea_compose_dir }}/compose.yaml"
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: ansible/roles/gitea/templates/compose.yaml.j2
|
||||||
|
# DESCRIPTION: Gitea + PostgreSQL compose stack. Values come from role defaults.
|
||||||
|
# Postgres stays on 17.x; a major bump needs dump + restore.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: docker.io/gitea/gitea:{{ gitea_version }}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=1000
|
||||||
|
- GITEA__database__DB_TYPE=postgres
|
||||||
|
- GITEA__database__HOST=gitea_db
|
||||||
|
- GITEA__database__NAME={{ gitea_db_name }}
|
||||||
|
- GITEA__database__USER={{ gitea_db_user }}
|
||||||
|
- GITEA__database__PASSWD=${DATABASE_PASSWORD}
|
||||||
|
- GITEA__server__SSH_PORT={{ gitea_ssh_port }}
|
||||||
|
- GITEA__server__ROOT_URL={{ gitea_root_url }}
|
||||||
|
- GITEA__server__DOMAIN={{ gitea_domain }}
|
||||||
|
- GITEA__server__HTTP_PORT={{ gitea_http_port }}
|
||||||
|
- GITEA__service__TRUSTED_PROXIES={{ gitea_trusted_proxies }}
|
||||||
|
networks:
|
||||||
|
- gitea_backend
|
||||||
|
ports:
|
||||||
|
- "{{ gitea_http_port }}:{{ gitea_http_port }}"
|
||||||
|
- "{{ gitea_ssh_port }}:22"
|
||||||
|
volumes:
|
||||||
|
- gitea_data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
depends_on:
|
||||||
|
gitea_db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
gitea_db:
|
||||||
|
image: docker.io/library/postgres:{{ gitea_postgres_version }}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- TZ={{ gitea_timezone }}
|
||||||
|
- POSTGRES_USER={{ gitea_db_user }}
|
||||||
|
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
|
||||||
|
- POSTGRES_DB={{ gitea_db_name }}
|
||||||
|
networks:
|
||||||
|
- gitea_backend
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U {{ gitea_db_user }}"]
|
||||||
|
start_period: 30s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- gitea_db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
gitea_data:
|
||||||
|
driver: local
|
||||||
|
gitea_db:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
gitea_backend:
|
||||||
|
driver: bridge
|
||||||
@@ -117,4 +117,4 @@ baseline_journald_max_retention: "30day"
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# MOTD branding
|
# MOTD branding
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
baseline_motd_banner: "mk-labs"
|
baseline_motd_banner: "wdw-labs"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
# The role's public variables are named in the issue's spec (multica_version,
|
||||||
|
# multica_server_url, multica_workspaces_root, multica_agent_clis, ...) and
|
||||||
|
# match the product's own namespace, including its MULTICA_* environment
|
||||||
|
# variables. Prefixing them with the role name instead would make every call
|
||||||
|
# site read multica_agent_host_multica_version. Internal facts use a leading
|
||||||
|
# underscore (_multica_*) to stay clearly private.
|
||||||
|
skip_list:
|
||||||
|
- var-naming[no-role-prefix]
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
# multica_agent_host
|
||||||
|
|
||||||
|
Provisions a Multica agent host from bare Linux to a **registered, running
|
||||||
|
daemon with its provider runtimes online**, and re-runs cleanly on a host that
|
||||||
|
is already provisioned.
|
||||||
|
|
||||||
|
Reaches "daemon running, runtimes registered" unattended. The one step it
|
||||||
|
cannot do is the provider CLI's own subscription login, which needs a browser
|
||||||
|
once per host.
|
||||||
|
|
||||||
|
## If your repo runs ansible-lint
|
||||||
|
|
||||||
|
This role keeps the variable names from its spec (`multica_version`,
|
||||||
|
`multica_server_url`, `multica_workspaces_root`, ...), which match the
|
||||||
|
product's own `MULTICA_*` namespace. ansible-lint's `var-naming[no-role-prefix]`
|
||||||
|
wants them prefixed with the role name instead — `multica_agent_host_multica_version`
|
||||||
|
at every call site.
|
||||||
|
|
||||||
|
The role ships the exemption at `roles/multica_agent_host/.ansible-lint`, but
|
||||||
|
**ansible-lint only reads that file from the directory you invoke it in**. If
|
||||||
|
you lint from your repo root, copy these two lines into your repo's own
|
||||||
|
`.ansible-lint`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
skip_list:
|
||||||
|
- var-naming[no-role-prefix]
|
||||||
|
```
|
||||||
|
|
||||||
|
Without it a repo-root lint run reports ~78 violations against a role that is
|
||||||
|
otherwise clean at the `production` profile.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
| Step | Handled |
|
||||||
|
|---|---|
|
||||||
|
| Dedicated `multica` service account (real home, linger) | yes |
|
||||||
|
| Node.js, pinned, from nodejs.org with SHASUMS256 verification | yes, when an npm-installed agent CLI is selected |
|
||||||
|
| `multica` CLI, pinned, from the GitHub release with `checksums.txt` verification | yes |
|
||||||
|
| Agent CLIs (`claude`, `hermes`, …) | yes |
|
||||||
|
| `workspaces_root`, on the intended mount, writable by the daemon user | yes |
|
||||||
|
| Multica authentication (`multica login --token`) | yes |
|
||||||
|
| CLI config (`server_url`, `app_url`, `workspaces_root`, `device_name`, …) | yes |
|
||||||
|
| Boot autostart (systemd user unit + `loginctl enable-linger`) | yes |
|
||||||
|
| Daemon start | yes |
|
||||||
|
| Runtime registration | follows automatically |
|
||||||
|
| **Provider CLI subscription login (e.g. `claude`)** | **no — browser, once per host** |
|
||||||
|
| Provider CLI credentials from a secret manager | out of scope; the role reports it as a remaining step |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux target, `x86_64` or `aarch64`.
|
||||||
|
- systemd, for boot autostart. Without it the CLI falls back to an XDG autostart
|
||||||
|
entry, which does not help a headless host — set `multica_autostart: false`
|
||||||
|
and supervise the daemon yourself.
|
||||||
|
- Privilege escalation on the target (`become: true`).
|
||||||
|
- Ansible 2.14+.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
**The role does not know where your secrets live.** It consumes one plain
|
||||||
|
variable:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
multica_pat: "{{ ... }}" # mul_… user PAT, or mcn_… Cloud Node PAT
|
||||||
|
```
|
||||||
|
|
||||||
|
The playbook supplies it however your environment already does. In this
|
||||||
|
homelab that means an **`ansible-vault` encrypted var**, which is what the
|
||||||
|
example playbook uses; a `community.hashi_vault` lookup, a 1Password lookup and
|
||||||
|
a plain environment variable are written out beside it as commented
|
||||||
|
alternatives.
|
||||||
|
|
||||||
|
See [Getting and storing the token](#getting-and-storing-the-token) below for
|
||||||
|
the end-to-end procedure.
|
||||||
|
|
||||||
|
Three consequences worth stating:
|
||||||
|
|
||||||
|
1. The role is reusable across environments that do not share a secrets backend.
|
||||||
|
2. Consolidating providers later is a one-line playbook change; this role is
|
||||||
|
untouched.
|
||||||
|
3. It sidesteps the HashiCorp Vault / `ansible-vault` ambiguity entirely — a
|
||||||
|
role that takes a value rather than fetching one works with either today.
|
||||||
|
|
||||||
|
`no_log: true` is set on the login task regardless of where the token comes
|
||||||
|
from. Ansible logs command arguments by default; without it the PAT lands in
|
||||||
|
play output, in any CI log, and in every callback plugin.
|
||||||
|
|
||||||
|
This matters just as much with `ansible-vault` as with a remote lookup: an
|
||||||
|
encrypted var is decrypted in memory for the duration of the play, so
|
||||||
|
encryption at rest does nothing for the runtime path. `no_log` is what covers
|
||||||
|
it.
|
||||||
|
|
||||||
|
**Prefer a Cloud Node PAT (`mcn_…`) over a user PAT** where its scope is
|
||||||
|
narrower — a daemon host does not need everything a person can do. Confirm the
|
||||||
|
scope difference against your Multica deployment before deciding.
|
||||||
|
|
||||||
|
## Getting and storing the token
|
||||||
|
|
||||||
|
### Minting it
|
||||||
|
|
||||||
|
**There is no CLI command for this.** `multica user` only manages your profile
|
||||||
|
and `multica auth` only reports status and logs out; nothing in the command
|
||||||
|
tree mints a token of either kind — the CLI only ever consumes one. A PAT is
|
||||||
|
created in the Multica web app under account settings, in a Tokens section.
|
||||||
|
(The exact menu path could not be confirmed from a headless runtime, so treat
|
||||||
|
that as "where to look", not a click path.)
|
||||||
|
|
||||||
|
Two prefixes exist, and `multica login --token` accepts either:
|
||||||
|
|
||||||
|
| Prefix | Kind |
|
||||||
|
|---|---|
|
||||||
|
| `mul_` | User PAT — acts as you, with everything your account can reach |
|
||||||
|
| `mcn_` | "Cloud Node" PAT |
|
||||||
|
|
||||||
|
**On a self-hosted server, check whether `mcn_` is offered at all before
|
||||||
|
planning around it.** The string `mcn_` appears in exactly one place in the
|
||||||
|
shipped CLI — the `login --token` help text — and the only other place the
|
||||||
|
product uses "Cloud" as a noun is `multica setup cloud`, which points at
|
||||||
|
Multica Cloud (multica.ai) as opposed to `multica setup self-host`. That is
|
||||||
|
suggestive, not conclusive: Cloud Node PATs may well be a Multica Cloud
|
||||||
|
concept with no equivalent on a self-hosted deployment. The web UI's token
|
||||||
|
screen is the authority — whatever token types it offers you is the real
|
||||||
|
answer.
|
||||||
|
|
||||||
|
Either prefix satisfies the role; preflight validates both.
|
||||||
|
|
||||||
|
### Scoping a token on a self-hosted deployment
|
||||||
|
|
||||||
|
If `mul_` is the only kind available, a user PAT on a long-lived daemon host
|
||||||
|
means every run that host claims carries your full authority, and revoking it
|
||||||
|
later cuts off your own access too.
|
||||||
|
|
||||||
|
The lever that does exist is a **dedicated service member**. Workspace roles
|
||||||
|
are `owner`, `admin` and `member`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
multica workspace member list --output json # see who exists and their roles
|
||||||
|
multica workspace member invite agents@example.com --role member
|
||||||
|
```
|
||||||
|
|
||||||
|
Have that account accept the invitation, log in as it, mint *its* PAT, and give
|
||||||
|
that to the host. `member` is coarser than a purpose-built node credential, but
|
||||||
|
it is strictly narrower than `owner`, and revoking it touches nothing you use.
|
||||||
|
|
||||||
|
Do **not** reuse the session token your desktop CLI already holds. It is not a
|
||||||
|
PAT, the server renews it on its own schedule, and it ties the host's identity
|
||||||
|
to your desktop login — revoking one breaks the other.
|
||||||
|
|
||||||
|
### Storing it with ansible-vault
|
||||||
|
|
||||||
|
One-time, if you do not already have a vault password file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.ansible
|
||||||
|
openssl rand -base64 48 > ~/.ansible/vault-pass-homelab
|
||||||
|
chmod 600 ~/.ansible/vault-pass-homelab
|
||||||
|
```
|
||||||
|
|
||||||
|
Point `ansible.cfg` at it so no play needs `--ask-vault-pass`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[defaults]
|
||||||
|
vault_password_file = ~/.ansible/vault-pass-homelab
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the encrypted var file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p group_vars/multica_agent_hosts
|
||||||
|
ansible-vault create group_vars/multica_agent_hosts/vault.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
with a single line in it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vault_multica_pat: "mul_...."
|
||||||
|
```
|
||||||
|
|
||||||
|
The playbook already reads `multica_pat: "{{ vault_multica_pat }}"`. The
|
||||||
|
indirection is deliberate: the `vault_` prefix makes it obvious at a glance
|
||||||
|
which variables are encrypted, and it keeps the name greppable without
|
||||||
|
decrypting anything.
|
||||||
|
|
||||||
|
**Alternative, if you would rather not keep a whole encrypted file:** encrypt
|
||||||
|
just the string and paste the block into an ordinary `group_vars` file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-vault encrypt_string --stdin-name 'vault_multica_pat'
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the token, press Ctrl-D. Because it reads stdin, the token never enters
|
||||||
|
your shell history — which `ansible-vault encrypt_string 'mul_...'` as an
|
||||||
|
argument would.
|
||||||
|
|
||||||
|
### Checks worth running once
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# the file is actually encrypted, not plaintext you forgot to encrypt
|
||||||
|
head -1 group_vars/multica_agent_hosts/vault.yml # => $ANSIBLE_VAULT;1.1;AES256
|
||||||
|
|
||||||
|
# the var resolves and the role's prefix check will pass, without printing it
|
||||||
|
ansible -i inventory/hosts.yml multica_agent_hosts -m debug \
|
||||||
|
-a 'msg="pat resolves: {{ (vault_multica_pat | default("")) is match("^(mul_|mcn_)") }}"'
|
||||||
|
```
|
||||||
|
|
||||||
|
That second command prints only `true` or `false`, never the token.
|
||||||
|
|
||||||
|
Add the vault password file to `.gitignore` if it lives anywhere near the repo.
|
||||||
|
The encrypted `vault.yml` is safe to commit; the password file never is.
|
||||||
|
|
||||||
|
## Ansible owns the version
|
||||||
|
|
||||||
|
`multica_version` is pinned and `disable_auto_update` is set to `true` in CLI
|
||||||
|
config. Re-running the play **is** the upgrade path. Without this the CLI
|
||||||
|
self-updates out from under the role's declared state and the host silently
|
||||||
|
stops matching what the repo says it is.
|
||||||
|
|
||||||
|
The same reasoning applies to `multica_node_version` (full semver, not a major
|
||||||
|
track) and to `package_version` on each npm-installed agent CLI — left empty by
|
||||||
|
default, which tracks `latest`; set it to own that version too.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
See `defaults/main.yml` for the annotated list. The ones you will actually set:
|
||||||
|
|
||||||
|
| Variable | Default | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `multica_server_url` | — | **Required.** |
|
||||||
|
| `multica_pat` | — | **Required.** See above. |
|
||||||
|
| `multica_version` | `0.6.5` | Pinned CLI release. |
|
||||||
|
| `multica_node_version` | `24.22.1` | Pinned, full semver. |
|
||||||
|
| `multica_workspaces_root` | `/opt/multica/workspaces` | Local disk. Repoint at a mount when one exists. |
|
||||||
|
| `multica_workspaces_root_mount` | `""` | Set it and the play aborts if the mount is missing. |
|
||||||
|
| `multica_agent_clis` | `[claude]` | Keys of `multica_agent_cli_catalog`. |
|
||||||
|
| `multica_device_name` | short inventory hostname | Shows up in `runtime list` → `device_info`. |
|
||||||
|
| `multica_user` | `multica` | Daemon service account. |
|
||||||
|
| `multica_profile` | `""` | `--profile`, to isolate config/daemon state/workspaces. |
|
||||||
|
| `multica_allow_remote_install_scripts` | `false` | Required for `hermes`; see below. |
|
||||||
|
|
||||||
|
### Adding an agent CLI
|
||||||
|
|
||||||
|
Extend `multica_agent_cli_catalog`. Two install methods are implemented:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
multica_agent_cli_catalog:
|
||||||
|
codex:
|
||||||
|
provider: codex # must match `provider` in `multica runtime list`
|
||||||
|
binary: codex
|
||||||
|
method: npm
|
||||||
|
package: "@openai/codex"
|
||||||
|
package_version: ""
|
||||||
|
version_command: "codex --version"
|
||||||
|
manual_login: true
|
||||||
|
manual_login_hint: "run `codex login` once as the daemon user"
|
||||||
|
```
|
||||||
|
|
||||||
|
`method: script` fetches `install_url` and runs it as the daemon user. That is a
|
||||||
|
supply-chain decision, so the role **refuses** unless
|
||||||
|
`multica_allow_remote_install_scripts: true`. `hermes` uses this method
|
||||||
|
(`https://hermes-agent.nousresearch.com/install.sh`). Vendoring the installer
|
||||||
|
into your repo and switching to a local `script` source is the stricter option.
|
||||||
|
|
||||||
|
Agent CLI binaries are symlinked into `/usr/local/bin`. This matters: the daemon
|
||||||
|
runs from a systemd **user unit**, which gets systemd's default `PATH` and does
|
||||||
|
*not* source `/etc/profile.d`. A CLI reachable only from an interactive login
|
||||||
|
shell is invisible to the daemon. `MULTICA_<PROVIDER>_PATH` is the escape hatch
|
||||||
|
if you need the binary somewhere else.
|
||||||
|
|
||||||
|
Both install methods resolve the binary with `command -v` as the daemon user
|
||||||
|
and link *that* path, rather than assuming where they put it. This matters when
|
||||||
|
a CLI was already installed by other means — the install step is skipped, so
|
||||||
|
assuming the npm prefix would link to nothing. The role asserts the binary is
|
||||||
|
resolvable before linking, and linking the resolved path repairs a dangling
|
||||||
|
link left behind by an earlier run.
|
||||||
|
|
||||||
|
## Moving the workspaces root onto a mount later
|
||||||
|
|
||||||
|
`workspaces_root` holds per-run working directories, not durable state — the
|
||||||
|
daemon recreates what it needs. Moving it is therefore a drain-and-repoint, not
|
||||||
|
a migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
multica daemon disk-usage --output json # see what is actually there first
|
||||||
|
sudo -iu multica multica daemon stop # let in-flight runs finish first
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set `multica_workspaces_root` and `multica_workspaces_root_mount` together
|
||||||
|
in the playbook and re-run. The role creates the new directory, proves the
|
||||||
|
daemon user can write to it, updates `config set workspaces_root`, and restarts
|
||||||
|
the daemon.
|
||||||
|
|
||||||
|
**Set both variables in the same change.** Pointing the root at a mount path
|
||||||
|
without setting the guard means a play that runs while the mount is down
|
||||||
|
silently writes run workspaces onto the root filesystem — which nobody notices
|
||||||
|
until it fills. The guard turns that into a failed assertion instead.
|
||||||
|
|
||||||
|
The old directory is left behind deliberately; remove it by hand once the new
|
||||||
|
one has taken runs.
|
||||||
|
|
||||||
|
## Idempotency
|
||||||
|
|
||||||
|
Every step reads before it writes:
|
||||||
|
|
||||||
|
| Step | Guard |
|
||||||
|
|---|---|
|
||||||
|
| Node install | `node --version` equals the pin |
|
||||||
|
| CLI install | `multica --version` equals the pin |
|
||||||
|
| Agent CLI install | `command -v <binary>` and its version command |
|
||||||
|
| Config | `multica config show` parsed and compared key by key |
|
||||||
|
| Login | `multica auth status` already names `multica_server_url` (it prints to **stderr**; the guard reads both streams) |
|
||||||
|
| Autostart | `multica daemon autostart status` |
|
||||||
|
| Daemon start | `systemctl --user is-active <unit>`; a daemon running *outside* the unit is stopped first, then the unit is started |
|
||||||
|
|
||||||
|
A binary, config or credential change notifies a `systemctl --user restart`
|
||||||
|
handler, which is flushed *before* verification reads the daemon's state back.
|
||||||
|
|
||||||
|
The role never calls `multica daemon start` or `multica daemon restart`. Both
|
||||||
|
fork the daemon inside the caller's session (Ansible's SSH session scope),
|
||||||
|
outside the unit's cgroup. The unit then shows `inactive`, nothing supervises
|
||||||
|
the process, and a later `systemctl --user start` collides with it on
|
||||||
|
`daemon.lock`.
|
||||||
|
|
||||||
|
A converged host reports zero changed tasks.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
The role ends in assertions, not hope:
|
||||||
|
|
||||||
|
- `multica daemon status --output json` reports `status: running`, and its
|
||||||
|
`cli_version` and `server_url` match the declared values.
|
||||||
|
- `multica runtime list --output json` contains an **online** runtime for every
|
||||||
|
entry in `multica_agent_clis`, filtered to this host's `daemon_id`, with
|
||||||
|
`device_info` naming `multica_device_name`. Retried, because registration is
|
||||||
|
asynchronous after the daemon starts.
|
||||||
|
- `workspaces_root` exists, is a directory, is owned by the daemon user, is
|
||||||
|
actually writable by it (ownership is not writability on a root-squashed
|
||||||
|
network mount), and sits on `multica_workspaces_root_mount` when that is set.
|
||||||
|
|
||||||
|
It finishes by printing the manual steps that remain — the per-host provider CLI
|
||||||
|
logins.
|
||||||
|
|
||||||
|
## Testing against a throwaway VM
|
||||||
|
|
||||||
|
Molecule is fine but a scratch VM is proportionate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. clean host
|
||||||
|
ansible-playbook -i inventory/hosts.yml playbooks/provision-agent-host.yml -l scratch-vm
|
||||||
|
|
||||||
|
# 2. re-run: must report changed=0
|
||||||
|
ansible-playbook -i inventory/hosts.yml playbooks/provision-agent-host.yml -l scratch-vm
|
||||||
|
|
||||||
|
# 3. reboot, then confirm the daemon came back on its own
|
||||||
|
ssh scratch-vm 'sudo -iu multica multica daemon status --output json'
|
||||||
|
|
||||||
|
# 4. confirm from the workspace side
|
||||||
|
multica runtime list --output json | jq '.[] | select(.device_info | startswith("scratch-vm"))'
|
||||||
|
```
|
||||||
|
|
||||||
|
Then complete the browser login for each provider CLI once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -t scratch-vm 'sudo -iu multica claude'
|
||||||
|
```
|
||||||
|
|
||||||
|
That step only becomes automatable if the host moves from a subscription to an
|
||||||
|
API key.
|
||||||
|
|
||||||
|
## Known gaps
|
||||||
|
|
||||||
|
- **Provider CLI login.** Unavoidable for subscription auth.
|
||||||
|
- **Hermes credentials.** Hermes reads 1Password natively; wiring that is not
|
||||||
|
implemented here because the right item layout is deployment-specific. The
|
||||||
|
role installs Hermes and reports the credential step as outstanding.
|
||||||
|
- **Non-systemd hosts.** Autostart degrades to an XDG entry that a headless host
|
||||||
|
never triggers.
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
| Tag | Covers |
|
||||||
|
|---|---|
|
||||||
|
| `multica_workspaces` | workspaces root |
|
||||||
|
| `multica_node` | Node.js |
|
||||||
|
| `multica_cli` | multica CLI |
|
||||||
|
| `multica_agents` | agent CLIs |
|
||||||
|
| `multica_config` | `multica config set` |
|
||||||
|
| `multica_auth` | `multica login` |
|
||||||
|
| `multica_daemon` | autostart + start |
|
||||||
|
| `multica_verify` | the assertions |
|
||||||
|
| `multica` | all of the above |
|
||||||
|
|
||||||
|
Preflight and the service-account step are tagged `always`: they build
|
||||||
|
`_multica_uid` and `_multica_user_env`, which every `become_user` task below
|
||||||
|
depends on, so a `--tags` subset still runs them. Both are idempotent.
|
||||||
|
|
||||||
|
## Offline checks
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-lint playbooks/provision-agent-host.yml roles/multica_agent_host
|
||||||
|
ansible-playbook --syntax-check playbooks/provision-agent-host.yml
|
||||||
|
ansible-playbook -i localhost, tests/expressions.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
`tests/expressions.yml` runs the role's filter chains — `config show` parsing,
|
||||||
|
version extraction, the online-runtime difference, the mount containment check
|
||||||
|
— against fixtures captured from a real `multica` CLI, with no target host.
|
||||||
|
It catches the class of bug where a filter chain silently returns the wrong
|
||||||
|
type or matches nothing; it does not replace the scratch-VM run above.
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
# =============================================================================
|
||||||
|
# multica_agent_host — defaults
|
||||||
|
#
|
||||||
|
# Everything the role needs is a plain variable. The role never reaches into a
|
||||||
|
# secrets backend itself; see `multica_pat` below.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Service account
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The daemon runs as a dedicated unprivileged user with a real home directory.
|
||||||
|
# It needs a real home (CLI config, agent CLI credentials) and systemd linger,
|
||||||
|
# so this is NOT a `system: true` account.
|
||||||
|
multica_manage_user: true
|
||||||
|
multica_user: multica
|
||||||
|
multica_group: multica
|
||||||
|
multica_user_home: "/home/{{ multica_user }}"
|
||||||
|
multica_user_shell: /bin/bash
|
||||||
|
# Extra groups for the daemon user (e.g. docker). Left empty by default.
|
||||||
|
multica_user_extra_groups: []
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# multica CLI — Ansible owns the version
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Pin it. Re-running the play IS the upgrade path; see multica_disable_auto_update.
|
||||||
|
multica_version: "0.6.5"
|
||||||
|
multica_install_dir: /usr/local/bin
|
||||||
|
multica_release_download_base_url: "https://github.com/multica-ai/multica/releases/download"
|
||||||
|
# Verify the release tarball against the release's own checksums.txt.
|
||||||
|
multica_verify_checksum: true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Server / workspace
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
multica_server_url: "" # REQUIRED, e.g. https://multica.wdw-labs.cloud
|
||||||
|
multica_app_url: "" # optional frontend URL
|
||||||
|
multica_workspace_id: "" # optional: pin this profile's default workspace
|
||||||
|
multica_profile: "" # optional: --profile <name> (isolates config + daemon state)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Daemon configuration (mirrors `multica config set` keys)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Local disk by default. Repoint this at a mount when one exists, and set
|
||||||
|
# multica_workspaces_root_mount at the same time so the play fails loudly if
|
||||||
|
# the mount is missing rather than silently filling the root filesystem.
|
||||||
|
multica_workspaces_root: "/opt/multica/workspaces"
|
||||||
|
# Optional guard: assert workspaces_root actually sits on this mount point.
|
||||||
|
# e.g. "/mnt/utilidor". Empty disables the check.
|
||||||
|
multica_workspaces_root_mount: ""
|
||||||
|
multica_workspaces_root_mode: "0750"
|
||||||
|
|
||||||
|
multica_device_name: "{{ inventory_hostname.split('.')[0] }}"
|
||||||
|
multica_runtime_name: "" # "" = leave unset, CLI derives it
|
||||||
|
multica_max_concurrent_tasks: "" # "" = leave unset
|
||||||
|
multica_poll_interval: "" # "" = leave unset
|
||||||
|
multica_heartbeat_interval: "" # "" = leave unset
|
||||||
|
multica_agent_timeout: "" # "" = leave unset ("0s" disables the cap)
|
||||||
|
|
||||||
|
# Ansible owns the version: stop the CLI self-updating out from under the
|
||||||
|
# role's declared state. Set false only if you want the daemon to self-update.
|
||||||
|
multica_disable_auto_update: true
|
||||||
|
|
||||||
|
# Boot autostart. On Linux `multica daemon autostart enable` writes a systemd
|
||||||
|
# *user* unit, which only starts at boot when the user has lingering enabled.
|
||||||
|
multica_autostart: true
|
||||||
|
multica_enable_linger: true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Authentication — backend-agnostic by design
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# REQUIRED. A Multica personal access token: `mul_...` (user PAT) or
|
||||||
|
# `mcn_...` (Cloud Node PAT). The role takes a VALUE; it does not know or care
|
||||||
|
# where it came from. Supply it from the playbook with whatever your
|
||||||
|
# environment already uses:
|
||||||
|
#
|
||||||
|
# HashiCorp Vault:
|
||||||
|
# multica_pat: "{{ lookup('community.hashi_vault.vault_kv2_get',
|
||||||
|
# 'multica/agent-host').secret.pat }}"
|
||||||
|
# ansible-vault encrypted var:
|
||||||
|
# multica_pat: "{{ vaulted_multica_pat }}"
|
||||||
|
# 1Password:
|
||||||
|
# multica_pat: "{{ lookup('community.general.onepassword',
|
||||||
|
# 'Multica Agent Host PAT', field='credential') }}"
|
||||||
|
# Environment:
|
||||||
|
# multica_pat: "{{ lookup('env', 'MULTICA_PAT') }}"
|
||||||
|
#
|
||||||
|
# Consolidating secret providers later is a one-line playbook change; this role
|
||||||
|
# is untouched by it.
|
||||||
|
multica_pat: ""
|
||||||
|
|
||||||
|
# Force `multica login --token` even when the host already reports an
|
||||||
|
# authenticated session against multica_server_url.
|
||||||
|
multica_force_relogin: false
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Agent CLIs
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Which provider CLIs this host should offer. Each must be a key in
|
||||||
|
# multica_agent_cli_catalog. The daemon registers one runtime per CLI it finds.
|
||||||
|
multica_agent_clis:
|
||||||
|
- claude
|
||||||
|
|
||||||
|
# Some agent CLIs are installed by piping a vendor script into a shell. That is
|
||||||
|
# a real supply-chain decision, so it is opt-in and off by default.
|
||||||
|
multica_allow_remote_install_scripts: false
|
||||||
|
|
||||||
|
multica_agent_cli_catalog:
|
||||||
|
claude:
|
||||||
|
provider: claude # matches `provider` in `multica runtime list`
|
||||||
|
binary: claude
|
||||||
|
method: npm
|
||||||
|
package: "@anthropic-ai/claude-code"
|
||||||
|
# Pinned: npm "latest" is 2.1.306 while "stable" is 2.1.304, so an empty
|
||||||
|
# value silently tracks whichever npm serves that day. 2.1.304 is the
|
||||||
|
# version running on haunted-mansion.
|
||||||
|
package_version: "2.1.304"
|
||||||
|
version_command: "claude --version"
|
||||||
|
# Subscription auth needs a browser once per host. Not automatable here.
|
||||||
|
manual_login: true
|
||||||
|
manual_login_hint: "run `claude` once as {{ multica_user }} and complete the login"
|
||||||
|
hermes:
|
||||||
|
provider: hermes
|
||||||
|
binary: hermes
|
||||||
|
method: script
|
||||||
|
install_url: "https://hermes-agent.nousresearch.com/install.sh"
|
||||||
|
install_dir: "{{ multica_user_home }}/.hermes/hermes-agent"
|
||||||
|
version_command: "hermes --version"
|
||||||
|
manual_login: true
|
||||||
|
manual_login_hint: "configure Hermes credentials as {{ multica_user }} (Hermes reads 1Password natively; confirm against its docs)"
|
||||||
|
|
||||||
|
# Keys MERGED (recursively) into the daemon user's ~/.claude/settings.json.
|
||||||
|
# Merged, not templated: Claude Code writes that file itself. Keys not named
|
||||||
|
# here are left alone. Empty = don't touch the file.
|
||||||
|
#
|
||||||
|
# An npm-installed Claude Code updates itself in place, which silently breaks
|
||||||
|
# the package_version pin above (and the next play downgrades it again).
|
||||||
|
# Setting env.DISABLE_AUTOUPDATER: "1" here is what makes the pin hold.
|
||||||
|
multica_claude_settings: {}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Node.js (installed only when a selected agent CLI needs npm)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Full semver — this is a pin, not a major-version track.
|
||||||
|
multica_node_version: "24.22.1"
|
||||||
|
multica_node_dist_base_url: "https://nodejs.org/dist"
|
||||||
|
multica_node_prefix: /usr/local/lib/nodejs
|
||||||
|
# npm global prefix for the daemon user, so agent CLI installs need no root.
|
||||||
|
multica_npm_prefix: "{{ multica_user_home }}/.npm-global"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Verification
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
multica_verify: true
|
||||||
|
# How long to wait for runtimes to appear online after the daemon starts.
|
||||||
|
multica_verify_retries: 24
|
||||||
|
multica_verify_delay: 5
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# Through systemd, not `multica daemon restart`: that respawns the daemon in
|
||||||
|
# the caller's session, outside the unit (see tasks/daemon.yml).
|
||||||
|
- name: Restart multica daemon
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [systemctl, --user, restart, "{{ _multica_unit | default('multica-daemon.service') }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
changed_when: true
|
||||||
|
listen: restart multica daemon
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: multica_agent_host
|
||||||
|
namespace: wdwlabs
|
||||||
|
author: WDW Labs
|
||||||
|
description: >-
|
||||||
|
Provision a Multica agent host from bare Linux to a registered, running
|
||||||
|
daemon with its provider runtimes online.
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.14"
|
||||||
|
platforms:
|
||||||
|
- name: Debian
|
||||||
|
versions: [bullseye, bookworm, trixie]
|
||||||
|
- name: Ubuntu
|
||||||
|
versions: [jammy, noble]
|
||||||
|
- name: EL
|
||||||
|
versions: ["9"]
|
||||||
|
galaxy_tags:
|
||||||
|
- multica
|
||||||
|
- agents
|
||||||
|
- daemon
|
||||||
|
dependencies: []
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
# Resolve where the binary ACTUALLY is before linking to it.
|
||||||
|
#
|
||||||
|
# The npm path used to link unconditionally from multica_npm_prefix. When the
|
||||||
|
# CLI was already installed by some other means — a distro package, a vendor
|
||||||
|
# installer, a previous hand-rollout — nothing was installed at that prefix and
|
||||||
|
# `file: state=link force=true` happily created a DANGLING symlink in
|
||||||
|
# /usr/local/bin. PATH resolution skips a dangling link, so discovery still
|
||||||
|
# worked by luck wherever the real binary was already on systemd's PATH, and
|
||||||
|
# the role's actual guarantee silently did not hold.
|
||||||
|
- name: "Agents | Resolve the installed binary | {{ _agent_key }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["/bin/sh", "-c", "command -v {{ _agent.binary }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _agent_which
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: "Agents | Require the binary to be resolvable | {{ _agent_key }}"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _agent_which.rc == 0
|
||||||
|
- (_agent_which.stdout | trim) | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
{{ _agent.binary }} is not on {{ multica_user }}'s PATH after the install
|
||||||
|
step. The daemon discovers provider CLIs by PATH, so it would register no
|
||||||
|
{{ _agent_key }} runtime.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
# Linking the resolved path also repairs a dangling link left by an older run:
|
||||||
|
# `command -v` skips the broken link, so this overwrites it with a working one.
|
||||||
|
- name: "Agents | Link onto the default PATH | {{ _agent_key }}"
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ _agent_which.stdout | trim }}"
|
||||||
|
dest: "{{ multica_install_dir }}/{{ _agent.binary }}"
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
become: true
|
||||||
|
when: (_agent_which.stdout | trim) != (multica_install_dir ~ '/' ~ _agent.binary)
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
- name: "Agents | Resolve catalog entry | {{ _agent_key }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_agent: "{{ multica_agent_cli_catalog[_agent_key] }}"
|
||||||
|
|
||||||
|
- name: "Agents | Read the installed version | {{ _agent_key }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["/bin/sh", "-c", "command -v {{ _agent.binary }} >/dev/null 2>&1 && {{ _agent.version_command }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _agent_installed
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# Version formats differ per CLI ("2.1.304 (Claude Code)", "Hermes Agent
|
||||||
|
# v0.21.5+..."), so this matches the pinned version anywhere in the output —
|
||||||
|
# but on a version-component boundary. A plain substring test would accept
|
||||||
|
# 2.1.3045 for a pin of 2.1.304, and silently never upgrade.
|
||||||
|
- name: "Agents | Decide whether an install is needed | {{ _agent_key }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_agent_needs_install: >-
|
||||||
|
{{ _agent_installed.rc != 0
|
||||||
|
or (_agent.package_version | default('') | length > 0
|
||||||
|
and not ((_agent_installed.stdout | default('')) is search(
|
||||||
|
'(^|[^0-9.])' ~ (_agent.package_version | regex_escape) ~ '([^0-9.]|$)'))) }}
|
||||||
|
|
||||||
|
- name: "Agents | Install via npm as the daemon user | {{ _agent_key }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- "{{ multica_install_dir }}/npm"
|
||||||
|
- install
|
||||||
|
- --global
|
||||||
|
- --no-fund
|
||||||
|
- --no-audit
|
||||||
|
- "{{ _agent.package }}{{ ('@' ~ _agent.package_version) if _agent.package_version | default('') | length > 0 else '' }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when: _agent_needs_install | bool
|
||||||
|
changed_when: true
|
||||||
|
notify: restart multica daemon
|
||||||
|
|
||||||
|
- name: "Agents | Resolve and link onto the default PATH | {{ _agent_key }}"
|
||||||
|
ansible.builtin.include_tasks: _link.yml
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
- name: "Agents | Resolve catalog entry | {{ _agent_key }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_agent: "{{ multica_agent_cli_catalog[_agent_key] }}"
|
||||||
|
|
||||||
|
- name: "Agents | Read the installed version | {{ _agent_key }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["/bin/sh", "-c", "command -v {{ _agent.binary }} >/dev/null 2>&1 && {{ _agent.version_command }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _agent_installed
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# Piping a vendor script into a shell is a supply-chain decision, so the role
|
||||||
|
# refuses rather than making it quietly on the operator's behalf.
|
||||||
|
- name: "Agents | Refuse to run a remote installer unless allowed | {{ _agent_key }}"
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
{{ _agent_key }} installs by executing {{ _agent.install_url }}.
|
||||||
|
Set multica_allow_remote_install_scripts: true to permit that, vendor the
|
||||||
|
installer yourself, or drop {{ _agent_key }} from multica_agent_clis.
|
||||||
|
when:
|
||||||
|
- _agent_installed.rc != 0
|
||||||
|
- not (multica_allow_remote_install_scripts | bool)
|
||||||
|
|
||||||
|
- name: "Agents | Install via the vendor script | {{ _agent_key }}"
|
||||||
|
when:
|
||||||
|
- _agent_installed.rc != 0
|
||||||
|
- multica_allow_remote_install_scripts | bool
|
||||||
|
block:
|
||||||
|
- name: "Agents | Fetch the installer | {{ _agent_key }}"
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ _agent.install_url }}"
|
||||||
|
dest: "{{ multica_user_home }}/.cache/{{ _agent_key }}-install.sh"
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: "Agents | Run it as the daemon user | {{ _agent_key }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["/bin/bash", "{{ multica_user_home }}/.cache/{{ _agent_key }}-install.sh"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
changed_when: true
|
||||||
|
notify: restart multica daemon
|
||||||
|
|
||||||
|
- name: "Agents | Resolve and link onto the default PATH | {{ _agent_key }}"
|
||||||
|
ansible.builtin.include_tasks: _link.yml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
- name: Agents | Install each selected agent CLI
|
||||||
|
ansible.builtin.include_tasks: "agent_cli/{{ multica_agent_cli_catalog[_agent_key].method }}.yml"
|
||||||
|
loop: "{{ multica_agent_clis }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: _agent_key
|
||||||
|
label: "{{ _agent_key }}"
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
# `multica auth status` prints the server and user when authenticated and exits
|
||||||
|
# non-zero otherwise, so it is the idempotency guard for login.
|
||||||
|
#
|
||||||
|
# It prints to STDERR, not stdout (0.6.5). A stdout-only guard never matches,
|
||||||
|
# so every run re-logs in and restarts the daemon. It also echoes a token
|
||||||
|
# prefix, hence no_log.
|
||||||
|
- name: Auth | Read the current authentication state
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['auth', 'status'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_auth_status
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Auth | Decide whether a login is needed
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_needs_login: >-
|
||||||
|
{{ multica_force_relogin | bool
|
||||||
|
or _multica_auth_status.rc != 0
|
||||||
|
or multica_server_url.rstrip('/') not in
|
||||||
|
((_multica_auth_status.stdout | default('')) ~ (_multica_auth_status.stderr | default(''))) }}
|
||||||
|
|
||||||
|
# no_log is mandatory: without it Ansible records the full argv — including the
|
||||||
|
# PAT — in play output, CI logs, and every callback plugin.
|
||||||
|
- name: Auth | Log in with the personal access token
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['login', '--token', multica_pat] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when: _multica_needs_login | bool
|
||||||
|
changed_when: true
|
||||||
|
no_log: true
|
||||||
|
notify: restart multica daemon
|
||||||
|
|
||||||
|
- name: Auth | Pin the default workspace
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['workspace', 'switch', multica_workspace_id] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when: multica_workspace_id | length > 0
|
||||||
|
register: _multica_ws_switch
|
||||||
|
changed_when: _multica_needs_login | bool
|
||||||
|
notify: restart multica daemon
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
- name: Claude settings | Ensure ~/.claude exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_user_home }}/.claude"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Claude settings | Read the current settings
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ multica_user_home }}/.claude/settings.json"
|
||||||
|
register: _multica_claude_settings_raw
|
||||||
|
failed_when: false
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Claude settings | Merge the declared keys over them
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_claude_settings_current: >-
|
||||||
|
{{ (_multica_claude_settings_raw.content | b64decode | from_json)
|
||||||
|
if _multica_claude_settings_raw.content is defined else {} }}
|
||||||
|
|
||||||
|
- name: Claude settings | Write them back if anything differs
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ multica_user_home }}/.claude/settings.json"
|
||||||
|
content: "{{ _multica_claude_settings_current | combine(multica_claude_settings, recursive=true) | to_nice_json }}\n"
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0600"
|
||||||
|
become: true
|
||||||
|
when: >-
|
||||||
|
_multica_claude_settings_current | combine(multica_claude_settings, recursive=true)
|
||||||
|
!= _multica_claude_settings_current
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
# The CLI owns its config file format, so the role drives `multica config set`
|
||||||
|
# rather than templating config.json. Each key is read back first, so a
|
||||||
|
# converged host reports zero changes.
|
||||||
|
|
||||||
|
- name: Config | Build the desired configuration
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_desired_config: >-
|
||||||
|
{{ {
|
||||||
|
'server_url': multica_server_url,
|
||||||
|
'app_url': multica_app_url,
|
||||||
|
'workspaces_root': multica_workspaces_root,
|
||||||
|
'device_name': multica_device_name,
|
||||||
|
'runtime_name': multica_runtime_name,
|
||||||
|
'max_concurrent_tasks': multica_max_concurrent_tasks,
|
||||||
|
'poll_interval': multica_poll_interval,
|
||||||
|
'heartbeat_interval': multica_heartbeat_interval,
|
||||||
|
'agent_timeout': multica_agent_timeout,
|
||||||
|
'disable_auto_update': ('true' if multica_disable_auto_update | bool else 'false'),
|
||||||
|
} | dict2items
|
||||||
|
| rejectattr('value', 'equalto', '')
|
||||||
|
| list | items2dict }}
|
||||||
|
|
||||||
|
- name: Config | Read the current configuration
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['config', 'show'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_config_show
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# `config show` prints "key:<spaces>value", with "(not set)" for unset keys.
|
||||||
|
- name: Config | Parse it
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_current_config: >-
|
||||||
|
{{ dict(
|
||||||
|
_multica_config_show.stdout_lines
|
||||||
|
| select('match', '^[a-z_]+:')
|
||||||
|
| map('regex_replace', '^([a-z_]+):\s*(.*?)\s*$', '\1|\2')
|
||||||
|
| map('split', '|')
|
||||||
|
) }}
|
||||||
|
|
||||||
|
- name: Config | Apply the keys that differ
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['config', 'set', item.key, item.value] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
loop: "{{ _multica_desired_config | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}={{ item.value }}"
|
||||||
|
when: (_multica_current_config[item.key] | default('(not set)')) != item.value
|
||||||
|
changed_when: true
|
||||||
|
notify: restart multica daemon
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
# On Linux `multica daemon autostart enable` writes a systemd *user* unit.
|
||||||
|
# A user unit does not start at boot unless the user lingers — without this the
|
||||||
|
# daemon only comes back when someone logs in, which on a headless agent host
|
||||||
|
# is never.
|
||||||
|
- name: Daemon | Enable systemd lingering for the daemon user
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["loginctl", "enable-linger", "{{ multica_user }}"]
|
||||||
|
creates: "/var/lib/systemd/linger/{{ multica_user }}"
|
||||||
|
become: true
|
||||||
|
when:
|
||||||
|
- multica_enable_linger | bool
|
||||||
|
- multica_autostart | bool
|
||||||
|
|
||||||
|
- name: Daemon | Wait for the user's runtime directory
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
path: "/run/user/{{ _multica_uid }}"
|
||||||
|
state: present
|
||||||
|
timeout: 30
|
||||||
|
become: true
|
||||||
|
when:
|
||||||
|
- multica_enable_linger | bool
|
||||||
|
- multica_autostart | bool
|
||||||
|
|
||||||
|
- name: Daemon | Read the current autostart registration
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'autostart', 'status'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_autostart_status
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Daemon | Register boot autostart
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'autostart', 'enable'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when:
|
||||||
|
- multica_autostart | bool
|
||||||
|
- _multica_autostart_status.rc != 0
|
||||||
|
or 'enabled' not in (_multica_autostart_status.stdout | default('') | lower)
|
||||||
|
register: _multica_autostart_enable
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
# Lifecycle goes through systemd, never `multica daemon start|restart`. Those
|
||||||
|
# fork the daemon inside the CALLER's session (here, Ansible's SSH session
|
||||||
|
# scope), outside the unit's cgroup: the unit stays inactive, nothing
|
||||||
|
# supervises the process, and the next `systemctl --user start` collides with
|
||||||
|
# it on daemon.lock. Seen on haunted-mansion (2026-10-21) and spaceship-earth
|
||||||
|
# (2026-10-22).
|
||||||
|
#
|
||||||
|
# The unit name comes from `autostart status` ("Location: .../<name>.service")
|
||||||
|
# so a --profile host resolves its own unit.
|
||||||
|
- name: Daemon | Re-read the autostart registration
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'autostart', 'status'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_autostart_reread
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
when: _multica_autostart_enable is changed
|
||||||
|
|
||||||
|
- name: Daemon | Resolve the systemd user unit
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_unit: >-
|
||||||
|
{{ ((_s.stdout | default('')) ~ '\n' ~ (_s.stderr | default('')))
|
||||||
|
| regex_search('Location:\s*\S*/([^/\s]+\.service)', '\1')
|
||||||
|
| default(['multica-daemon.service'], true) | first }}
|
||||||
|
vars:
|
||||||
|
_s: "{{ _multica_autostart_reread if _multica_autostart_enable is changed else _multica_autostart_status }}"
|
||||||
|
|
||||||
|
# `autostart enable` may rewrite the unit file while the user manager still
|
||||||
|
# holds the old definition.
|
||||||
|
- name: Daemon | Reload the user manager after a (re)registration
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [systemctl, --user, daemon-reload]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when: _multica_autostart_enable is changed
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Daemon | Read the unit state
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [systemctl, --user, is-active, "{{ _multica_unit }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_unit_state
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Daemon | Read daemon status
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'status', '--output', 'json'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_daemon_status
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# Running, but not under the unit: an orphan. Stop it so the unit can own the
|
||||||
|
# lock and the process.
|
||||||
|
- name: Daemon | Stop a daemon running outside its unit
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'stop'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when:
|
||||||
|
- (_multica_unit_state.stdout | trim) != 'active'
|
||||||
|
- _multica_daemon_status.rc == 0
|
||||||
|
- ((_multica_daemon_status.stdout | from_json).status | default('')) == 'running'
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Daemon | Start the unit if it is not active
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [systemctl, --user, start, "{{ _multica_unit }}"]
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
when: (_multica_unit_state.stdout | trim) != 'active'
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
# Anything that changed the binary, the config or the credentials restarts the
|
||||||
|
# daemon here, before verification reads its state back.
|
||||||
|
- name: Daemon | Flush handlers
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
- name: Preflight
|
||||||
|
ansible.builtin.import_tasks: preflight.yml
|
||||||
|
# Prerequisite for every other block: always runs, even under a --tags subset.
|
||||||
|
tags: [always]
|
||||||
|
|
||||||
|
- name: Service account
|
||||||
|
ansible.builtin.import_tasks: user.yml
|
||||||
|
# Builds _multica_uid / _multica_user_env, which every later become_user task
|
||||||
|
# needs; creating an existing user is a no-op.
|
||||||
|
tags: [always]
|
||||||
|
|
||||||
|
- name: Workspaces root
|
||||||
|
ansible.builtin.import_tasks: workspaces_root.yml
|
||||||
|
tags: [multica, multica_workspaces]
|
||||||
|
|
||||||
|
- name: Node.js
|
||||||
|
ansible.builtin.import_tasks: node.yml
|
||||||
|
when: _multica_node_required | bool
|
||||||
|
tags: [multica, multica_node]
|
||||||
|
|
||||||
|
- name: Multica CLI
|
||||||
|
ansible.builtin.import_tasks: multica_cli.yml
|
||||||
|
tags: [multica, multica_cli]
|
||||||
|
|
||||||
|
- name: Agent CLIs
|
||||||
|
ansible.builtin.import_tasks: agent_clis.yml
|
||||||
|
tags: [multica, multica_agents]
|
||||||
|
|
||||||
|
- name: Claude Code settings
|
||||||
|
ansible.builtin.import_tasks: claude_settings.yml
|
||||||
|
when:
|
||||||
|
- "'claude' in multica_agent_clis"
|
||||||
|
- multica_claude_settings | length > 0
|
||||||
|
tags: [multica, multica_agents, multica_claude_settings]
|
||||||
|
|
||||||
|
- name: CLI configuration
|
||||||
|
ansible.builtin.import_tasks: config.yml
|
||||||
|
tags: [multica, multica_config]
|
||||||
|
|
||||||
|
- name: Authentication
|
||||||
|
ansible.builtin.import_tasks: auth.yml
|
||||||
|
tags: [multica, multica_auth]
|
||||||
|
|
||||||
|
- name: Daemon
|
||||||
|
ansible.builtin.import_tasks: daemon.yml
|
||||||
|
tags: [multica, multica_daemon]
|
||||||
|
|
||||||
|
- name: Verify
|
||||||
|
ansible.builtin.import_tasks: verify.yml
|
||||||
|
when: multica_verify | bool
|
||||||
|
tags: [multica, multica_verify]
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
- name: CLI | Check whether the binary is already present
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ _multica_bin }}"
|
||||||
|
register: _multica_cli_stat
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: CLI | Read the installed version
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["{{ _multica_bin }}", "--version"]
|
||||||
|
register: _multica_cli_installed
|
||||||
|
when: _multica_cli_stat.stat.exists
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# `multica --version` prints e.g. "multica 0.6.5 (commit: ..., built: ...)".
|
||||||
|
- name: CLI | Extract the installed version string
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_cli_version_found: >-
|
||||||
|
{{ ((_multica_cli_installed.stdout_lines | default([]) | first | default('')).split() + ['', ''])[1]
|
||||||
|
if (_multica_cli_installed.rc | default(1)) == 0 else '' }}
|
||||||
|
|
||||||
|
- name: CLI | Decide whether an install is needed
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_cli_needs_install: "{{ (_multica_cli_version_found | trim) != multica_version }}"
|
||||||
|
|
||||||
|
- name: CLI | Install the pinned release
|
||||||
|
when: _multica_cli_needs_install | bool
|
||||||
|
block:
|
||||||
|
- name: CLI | Create a staging directory
|
||||||
|
ansible.builtin.tempfile:
|
||||||
|
state: directory
|
||||||
|
suffix: multica-cli
|
||||||
|
register: _multica_stage
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: CLI | Download the release tarball
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ _multica_release_url }}"
|
||||||
|
dest: "{{ _multica_stage.path }}/{{ _multica_tarball }}"
|
||||||
|
checksum: "{{ ('sha256:' ~ _multica_checksums_url) if multica_verify_checksum | bool else omit }}"
|
||||||
|
mode: "0644"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: CLI | Unpack the binary
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ _multica_stage.path }}/{{ _multica_tarball }}"
|
||||||
|
dest: "{{ _multica_stage.path }}"
|
||||||
|
remote_src: true
|
||||||
|
become: true
|
||||||
|
|
||||||
|
# Replacing the binary while the daemon runs is fine — the handler restarts
|
||||||
|
# it, and the daemon also watches its own binary for version changes.
|
||||||
|
- name: CLI | Install it onto the PATH
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ _multica_stage.path }}/multica"
|
||||||
|
dest: "{{ _multica_bin }}"
|
||||||
|
remote_src: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
become: true
|
||||||
|
notify: restart multica daemon
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: CLI | Remove the staging directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ _multica_stage.path }}"
|
||||||
|
state: absent
|
||||||
|
become: true
|
||||||
|
when: _multica_stage.path is defined
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
# Installed from the official nodejs.org tarball rather than a distro repo so
|
||||||
|
# that multica_node_version is a real pin on every distribution.
|
||||||
|
|
||||||
|
- name: Node | Check whether the pinned runtime is already present
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ multica_install_dir }}/node"
|
||||||
|
register: _multica_node_stat
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Node | Read the installed version
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["{{ multica_install_dir }}/node", "--version"]
|
||||||
|
register: _multica_node_installed
|
||||||
|
when: _multica_node_stat.stat.exists
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Node | Decide whether an install is needed
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_node_needs_install: >-
|
||||||
|
{{ (not _multica_node_stat.stat.exists)
|
||||||
|
or (_multica_node_installed.rc | default(1)) != 0
|
||||||
|
or (_multica_node_installed.stdout | default('') | trim) != ('v' ~ multica_node_version) }}
|
||||||
|
|
||||||
|
- name: Node | Install the pinned runtime
|
||||||
|
when: _multica_node_needs_install | bool
|
||||||
|
block:
|
||||||
|
- name: Node | Create the install prefix
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_node_prefix }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
# get_url resolves `sha256:<url>` against the file's basename in SHASUMS256.txt.
|
||||||
|
- name: Node | Download the tarball with checksum verification
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ multica_node_dist_base_url }}/v{{ multica_node_version }}/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}.tar.xz"
|
||||||
|
dest: "/tmp/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}.tar.xz"
|
||||||
|
checksum: "sha256:{{ multica_node_dist_base_url }}/v{{ multica_node_version }}/SHASUMS256.txt"
|
||||||
|
mode: "0644"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Node | Unpack it
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "/tmp/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}.tar.xz"
|
||||||
|
dest: "{{ multica_node_prefix }}"
|
||||||
|
remote_src: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
creates: "{{ multica_node_prefix }}/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}/bin/node"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Node | Remove the downloaded tarball
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "/tmp/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}.tar.xz"
|
||||||
|
state: absent
|
||||||
|
become: true
|
||||||
|
|
||||||
|
# Symlinked into /usr/local/bin, which is on systemd's default PATH — the
|
||||||
|
# daemon's autostart unit does not source /etc/profile.d.
|
||||||
|
- name: Node | Link node, npm and npx onto the default PATH
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ multica_node_prefix }}/node-v{{ multica_node_version }}-linux-{{ _multica_node_arch }}/bin/{{ item }}"
|
||||||
|
dest: "{{ multica_install_dir }}/{{ item }}"
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
become: true
|
||||||
|
loop: [node, npm, npx]
|
||||||
|
|
||||||
|
- name: Node | Create the daemon user's npm global prefix
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_npm_prefix }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0755"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Node | Point the daemon user's npm at that prefix
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ multica_user_home }}/.npmrc"
|
||||||
|
regexp: '^prefix='
|
||||||
|
line: "prefix={{ multica_npm_prefix }}"
|
||||||
|
create: true
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
become: true
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
- name: Preflight | Require a Linux target
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_facts['system'] == 'Linux'
|
||||||
|
fail_msg: >-
|
||||||
|
multica_agent_host supports Linux hosts only
|
||||||
|
(found {{ ansible_facts['system'] }}).
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Preflight | Require a supported CPU architecture
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ansible_facts['architecture'] in _multica_arch_map
|
||||||
|
fail_msg: >-
|
||||||
|
No multica CLI release asset for architecture
|
||||||
|
{{ ansible_facts['architecture'] }}.
|
||||||
|
Supported: {{ _multica_arch_map.keys() | list | join(', ') }}.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Preflight | Require the server URL
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_server_url | length > 0
|
||||||
|
- multica_server_url is match('^https?://')
|
||||||
|
fail_msg: "multica_server_url must be set to an http(s) URL."
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
# no_log: a failed assert would otherwise render the variables it inspected.
|
||||||
|
- name: Preflight | Require a Multica personal access token
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_pat | length > 0
|
||||||
|
- multica_pat is match('^(mul_|mcn_)')
|
||||||
|
fail_msg: >-
|
||||||
|
multica_pat must be set to a Multica personal access token
|
||||||
|
(mul_... user PAT, or mcn_... Cloud Node PAT). Supply it from the
|
||||||
|
playbook; see this role's README for Vault / 1Password / env examples.
|
||||||
|
quiet: true
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Preflight | Require every selected agent CLI to be in the catalog
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_agent_clis | difference(multica_agent_cli_catalog.keys() | list) | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
Unknown agent CLI(s):
|
||||||
|
{{ multica_agent_clis | difference(multica_agent_cli_catalog.keys() | list) | join(', ') }}.
|
||||||
|
Add them to multica_agent_cli_catalog or drop them from multica_agent_clis.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Preflight | Require at least one agent CLI
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_agent_clis | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
multica_agent_clis is empty; the daemon would register no runtimes and
|
||||||
|
the host could never claim a run.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Preflight | Resolve release artefact names and the invocation prefix
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_arch: "{{ _multica_arch_map[ansible_facts['architecture']] }}"
|
||||||
|
_multica_node_arch: "{{ _multica_node_arch_map[ansible_facts['architecture']] }}"
|
||||||
|
_multica_bin: "{{ multica_install_dir }}/multica"
|
||||||
|
_multica_cmd: >-
|
||||||
|
{{ [multica_install_dir ~ '/multica']
|
||||||
|
+ (['--profile', multica_profile] if multica_profile | length > 0 else []) }}
|
||||||
|
_multica_node_required: >-
|
||||||
|
{{ multica_agent_clis
|
||||||
|
| map('extract', multica_agent_cli_catalog, 'method')
|
||||||
|
| select('equalto', 'npm') | list | length > 0 }}
|
||||||
|
|
||||||
|
- name: Preflight | Name the release tarball
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_tarball: "multica-cli-{{ multica_version }}-linux-{{ _multica_arch }}.tar.gz"
|
||||||
|
_multica_release_url: >-
|
||||||
|
{{ multica_release_download_base_url }}/v{{ multica_version }}/multica-cli-{{ multica_version }}-linux-{{ _multica_arch }}.tar.gz
|
||||||
|
_multica_checksums_url: >-
|
||||||
|
{{ multica_release_download_base_url }}/v{{ multica_version }}/checksums.txt
|
||||||
|
|
||||||
|
- name: Preflight | Require base tooling on the target
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: "{{ ['tar', 'xz-utils' if ansible_facts['os_family'] == 'Debian' else 'xz', 'ca-certificates', 'git'] }}"
|
||||||
|
state: present
|
||||||
|
become: true
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
- name: User | Create the daemon group
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ multica_group }}"
|
||||||
|
state: present
|
||||||
|
become: true
|
||||||
|
when: multica_manage_user | bool
|
||||||
|
|
||||||
|
# Deliberately not a system account: the daemon needs a real home for CLI
|
||||||
|
# config and agent CLI credentials, and systemd linger needs a real user.
|
||||||
|
- name: User | Create the daemon user
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
groups: "{{ multica_user_extra_groups }}"
|
||||||
|
append: true
|
||||||
|
home: "{{ multica_user_home }}"
|
||||||
|
shell: "{{ multica_user_shell }}"
|
||||||
|
create_home: true
|
||||||
|
state: present
|
||||||
|
become: true
|
||||||
|
when: multica_manage_user | bool
|
||||||
|
|
||||||
|
- name: User | Look up the daemon user
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ multica_user }}"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: User | Record uid and the per-user runtime environment
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_uid: "{{ ansible_facts.getent_passwd[multica_user][1] }}"
|
||||||
|
|
||||||
|
# systemd user units — which is what `multica daemon autostart enable` writes on
|
||||||
|
# Linux — are unreachable over a plain `sudo -u` shell without these. Without
|
||||||
|
# XDG_RUNTIME_DIR, `systemctl --user` fails with "Failed to connect to bus".
|
||||||
|
- name: User | Build the daemon user's command environment
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_user_env:
|
||||||
|
HOME: "{{ multica_user_home }}"
|
||||||
|
USER: "{{ multica_user }}"
|
||||||
|
LOGNAME: "{{ multica_user }}"
|
||||||
|
XDG_RUNTIME_DIR: "/run/user/{{ _multica_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ _multica_uid }}/bus"
|
||||||
|
PATH: "{{ multica_npm_prefix }}/bin:{{ multica_user_home }}/.local/bin:{{ multica_install_dir }}:/usr/local/bin:/usr/bin:/bin"
|
||||||
|
|
||||||
|
- name: User | Ensure the user's local bin directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_user_home }}/.local/bin"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0755"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: User | Ensure the user's cache directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_user_home }}/.cache"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
become: true
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
# Assertions, not hope. Each of these has failed in practice for a different
|
||||||
|
# reason, so each gets its own check and its own error message.
|
||||||
|
|
||||||
|
- name: Verify | Daemon reports running
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['daemon', 'status', '--output', 'json'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_verify_daemon
|
||||||
|
until:
|
||||||
|
- _multica_verify_daemon.rc == 0
|
||||||
|
- (_multica_verify_daemon.stdout | from_json).status | default('') == 'running'
|
||||||
|
retries: "{{ multica_verify_retries }}"
|
||||||
|
delay: "{{ multica_verify_delay }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Daemon is the pinned version and sees the configured workspaces root
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- (_multica_verify_daemon.stdout | from_json).cli_version == multica_version
|
||||||
|
- (_multica_verify_daemon.stdout | from_json).server_url | regex_replace('/$', '')
|
||||||
|
== multica_server_url | regex_replace('/$', '')
|
||||||
|
fail_msg: >-
|
||||||
|
Daemon is running but does not match declared state:
|
||||||
|
{{ _multica_verify_daemon.stdout }}
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Verify | Every selected agent CLI is registered as an online runtime
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: "{{ _multica_cmd + ['runtime', 'list', '--output', 'json'] }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
environment: "{{ _multica_user_env }}"
|
||||||
|
register: _multica_verify_runtimes
|
||||||
|
until:
|
||||||
|
- _multica_verify_runtimes.rc == 0
|
||||||
|
- _multica_expected_providers
|
||||||
|
| difference(_multica_verify_runtimes.stdout | from_json
|
||||||
|
| selectattr('daemon_id', 'equalto', (_multica_verify_daemon.stdout | from_json).daemon_id)
|
||||||
|
| selectattr('status', 'equalto', 'online')
|
||||||
|
| map(attribute='provider') | list)
|
||||||
|
| length == 0
|
||||||
|
retries: "{{ multica_verify_retries }}"
|
||||||
|
delay: "{{ multica_verify_delay }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
vars:
|
||||||
|
_multica_expected_providers: >-
|
||||||
|
{{ multica_agent_clis | map('extract', multica_agent_cli_catalog, 'provider') | list }}
|
||||||
|
|
||||||
|
- name: Verify | Runtimes name this host
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _multica_verify_runtimes.stdout | from_json
|
||||||
|
| selectattr('daemon_id', 'equalto', (_multica_verify_daemon.stdout | from_json).daemon_id)
|
||||||
|
| rejectattr('device_info', 'search', '^' ~ (multica_device_name | regex_escape))
|
||||||
|
| list | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
Runtimes registered under this daemon do not report device_name
|
||||||
|
"{{ multica_device_name }}".
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Verify | Stat the workspaces root
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ multica_workspaces_root }}"
|
||||||
|
register: _multica_verify_ws
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: Verify | Re-prove the daemon user can write to the workspaces root
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'd="$1"; f="$d/.multica-write-test.$$"; : > "$f" && rm -f "$f"'
|
||||||
|
- sh
|
||||||
|
- "{{ multica_workspaces_root }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
register: _multica_verify_ws_write
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Workspaces root assertions
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _multica_verify_ws.stat.exists
|
||||||
|
- _multica_verify_ws.stat.isdir
|
||||||
|
- _multica_verify_ws.stat.pw_name == multica_user
|
||||||
|
- _multica_verify_ws_write.rc == 0
|
||||||
|
fail_msg: >-
|
||||||
|
{{ multica_workspaces_root }} is missing, not a directory, or not
|
||||||
|
owned/writable by {{ multica_user }}.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Verify | Workspaces root is on the expected mount
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_workspaces_root_mount in (ansible_facts['mounts'] | map(attribute='mount') | list)
|
||||||
|
fail_msg: "{{ multica_workspaces_root_mount }} is not mounted."
|
||||||
|
quiet: true
|
||||||
|
when: multica_workspaces_root_mount | length > 0
|
||||||
|
|
||||||
|
- name: Verify | Report what still needs a human
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ multica_device_name }} is provisioned: multica {{ multica_version }},
|
||||||
|
daemon running, runtimes online for
|
||||||
|
{{ multica_agent_clis | join(', ') }}.
|
||||||
|
Remaining manual step(s):
|
||||||
|
{{ multica_agent_clis
|
||||||
|
| select('in', multica_agent_cli_catalog)
|
||||||
|
| map('extract', multica_agent_cli_catalog)
|
||||||
|
| selectattr('manual_login', 'defined')
|
||||||
|
| selectattr('manual_login')
|
||||||
|
| map(attribute='manual_login_hint')
|
||||||
|
| list | join('; ') | default('none', true) }}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
# Guard against the mount not being present: without it the daemon happily
|
||||||
|
# writes run workspaces onto the root filesystem and nobody notices until it
|
||||||
|
# fills up.
|
||||||
|
- name: Workspaces root | Assert it lands on the expected mount
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- multica_workspaces_root_mount in (ansible_facts['mounts'] | map(attribute='mount') | list)
|
||||||
|
- multica_workspaces_root == multica_workspaces_root_mount
|
||||||
|
or multica_workspaces_root.startswith(multica_workspaces_root_mount.rstrip('/') ~ '/')
|
||||||
|
fail_msg: >-
|
||||||
|
multica_workspaces_root ({{ multica_workspaces_root }}) is not on
|
||||||
|
{{ multica_workspaces_root_mount }}, which is not currently mounted or
|
||||||
|
does not contain it. Mount it before provisioning.
|
||||||
|
quiet: true
|
||||||
|
when: multica_workspaces_root_mount | length > 0
|
||||||
|
|
||||||
|
- name: Workspaces root | Create the directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ multica_workspaces_root }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ multica_user }}"
|
||||||
|
group: "{{ multica_group }}"
|
||||||
|
mode: "{{ multica_workspaces_root_mode }}"
|
||||||
|
become: true
|
||||||
|
|
||||||
|
# Ownership is not writability: a read-only or root-squashed network mount
|
||||||
|
# passes the file module and fails the first run.
|
||||||
|
- name: Workspaces root | Prove the daemon user can write to it
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'd="$1"; f="$d/.multica-write-test.$$"; : > "$f" && rm -f "$f"'
|
||||||
|
- sh
|
||||||
|
- "{{ multica_workspaces_root }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ multica_user }}"
|
||||||
|
changed_when: false
|
||||||
|
register: _multica_ws_write
|
||||||
|
failed_when: _multica_ws_write.rc != 0
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
# Internal, not meant to be overridden.
|
||||||
|
|
||||||
|
# ansible_architecture -> release asset arch
|
||||||
|
_multica_arch_map: # noqa: var-naming[no-role-prefix]
|
||||||
|
x86_64: amd64
|
||||||
|
amd64: amd64
|
||||||
|
aarch64: arm64
|
||||||
|
arm64: arm64
|
||||||
|
|
||||||
|
# ansible_architecture -> nodejs.org dist arch
|
||||||
|
_multica_node_arch_map: # noqa: var-naming[no-role-prefix]
|
||||||
|
x86_64: x64
|
||||||
|
amd64: x64
|
||||||
|
aarch64: arm64
|
||||||
|
arm64: arm64
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# obsidian_headless
|
||||||
|
|
||||||
|
Runs Obsidian's official headless Sync client
|
||||||
|
([`obsidian-headless`](https://obsidian.md/help/sync/headless), command `ob`,
|
||||||
|
open beta) as a dedicated unprivileged user under a systemd unit. It lays out
|
||||||
|
the vault folder so that other local users reach only the folders granted to
|
||||||
|
them.
|
||||||
|
|
||||||
|
Written for `spaceship-earth` (WDWL-22). Wong's daemon user (`multica`) may
|
||||||
|
read and write `inbox/` and nothing else. `everpure/` and `wip/` are `obsync`
|
||||||
|
only. Entry point: `playbooks/provision-wong-host.yml`.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. Creates the system user `obsync`, with a `0700` home in `/var/lib/obsync`.
|
||||||
|
That home holds the Obsidian account token and per-vault sync state.
|
||||||
|
2. Installs the pinned `obsidian-headless` into that user's npm prefix and
|
||||||
|
links `ob` into `/usr/local/bin`. Node.js is **not** installed here. The
|
||||||
|
role asserts that the runtime `multica_agent_host` installs is present and
|
||||||
|
is at least version 22.
|
||||||
|
3. Lays out the vault folder and its permissions:
|
||||||
|
|
||||||
|
| Path | Mode | ACL |
|
||||||
|
|---|---|---|
|
||||||
|
| `/srv/obsidian/` | `0710 obsync` | `user:multica:--x` |
|
||||||
|
| `/srv/obsidian/vault/` | `0710 obsync` | `user:multica:--x`, so `multica` can traverse but not list |
|
||||||
|
| `vault/everpure/`, `vault/wip/` | `0700 obsync` | none |
|
||||||
|
| `vault/inbox/` | `0770 obsync` | `user:multica:rwx` and `user:obsync:rwx`, each also as a **default** entry |
|
||||||
|
|
||||||
|
On a directory with an ACL, the mode's group bits are the ACL mask. A
|
||||||
|
`chmod 0700` would therefore void every named entry, which is why the
|
||||||
|
shared paths use `0710` and `0770`. The owning group is `obsync`'s private
|
||||||
|
group, so those bits grant nobody anything.
|
||||||
|
4. Installs `obsidian-sync.service` (`User=obsync`, `UMask=0077`, sandboxed)
|
||||||
|
and enables it. It is started only once a vault has been set up.
|
||||||
|
`Condition*=` lines keep it inert until then, including across reboots.
|
||||||
|
5. Verifies the isolation as the users themselves:
|
||||||
|
- `multica` writes, reads and deletes in `inbox/`.
|
||||||
|
- A folder and file created by `obsync` under umask `0077` (the way Sync
|
||||||
|
creates them) stay readable and writable by `multica`.
|
||||||
|
- What `multica` creates stays readable and writable by `obsync`. Those
|
||||||
|
files are owned `multica:multica`, so without the named `user:obsync`
|
||||||
|
default entry Sync could not read them and would never upload them.
|
||||||
|
- `multica` is refused both listing the vault root and listing or writing
|
||||||
|
`everpure/` and `wip/`.
|
||||||
|
|
||||||
|
## Do modes and ACLs survive Sync?
|
||||||
|
|
||||||
|
Obsidian Sync carries no Unix modes or ACLs. Files are created locally by
|
||||||
|
`obsync`, so their permissions come from this host, not from the remote:
|
||||||
|
|
||||||
|
- In `inbox/`, the default ACL applies and the umask is ignored, so new
|
||||||
|
content is granted to `multica`.
|
||||||
|
- Everywhere else, `UMask=0077` applies, so new content is private to
|
||||||
|
`obsync`. That covers any new top-level folder and `.obsidian/`.
|
||||||
|
- If Sync deletes and recreates `inbox/` itself, the recreated folder is
|
||||||
|
private and `multica` loses access. The failure is closed, not open, and
|
||||||
|
re-running the play restores the grant.
|
||||||
|
|
||||||
|
## Manual step: sign in and set up the vault (once per host)
|
||||||
|
|
||||||
|
The role never handles the Obsidian account. Run these as `obsync`; `-H`
|
||||||
|
makes `HOME` point to `/var/lib/obsync`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u obsync -H ob login
|
||||||
|
sudo -u obsync -H ob sync-list-remote
|
||||||
|
sudo -u obsync -H ob sync-setup --vault "<vault name>" \
|
||||||
|
--path /srv/obsidian/vault --device-name spaceship-earth
|
||||||
|
sudo systemctl start obsidian-sync
|
||||||
|
journalctl -u obsidian-sync -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-running the play after this starts the unit as well. Do not also run
|
||||||
|
desktop Obsidian Sync on this host: Obsidian warns that two Sync clients on
|
||||||
|
one device conflict.
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
`obsidian_user`, `obsidian_install`, `obsidian_layout`, `obsidian_service`,
|
||||||
|
`obsidian_verify`. The play itself is tagged `obsidian`.
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
# =============================================================================
|
||||||
|
# obsidian_headless — defaults
|
||||||
|
#
|
||||||
|
# Runs Obsidian's official headless Sync client (`ob`, npm obsidian-headless)
|
||||||
|
# as a dedicated unprivileged user under a systemd unit, and lays out the vault
|
||||||
|
# folder so that other local users reach only the folders granted to them.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Service account
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Holds the Obsidian account token (~/.config/obsidian-headless/auth_token) and
|
||||||
|
# the per-vault sync state, so its home is private.
|
||||||
|
obsidian_headless_user: obsync
|
||||||
|
obsidian_headless_group: obsync
|
||||||
|
obsidian_headless_home: "/var/lib/{{ obsidian_headless_user }}"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Client
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Pinned. The package is in open beta and versions move quickly.
|
||||||
|
obsidian_headless_version: "0.0.14"
|
||||||
|
obsidian_headless_npm_prefix: "{{ obsidian_headless_home }}/.npm-global"
|
||||||
|
# Node.js is NOT installed by this role. It expects the runtime that
|
||||||
|
# multica_agent_host already puts on this host. obsidian-headless needs >= 22.
|
||||||
|
obsidian_headless_node_bin_dir: /usr/local/bin
|
||||||
|
obsidian_headless_node_min_version: "22.0.0"
|
||||||
|
# Where `ob` is linked, so `sudo -u obsync -H ob ...` works without a PATH.
|
||||||
|
obsidian_headless_link_dir: /usr/local/bin
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Vault
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
obsidian_headless_vault_path: /srv/obsidian/vault
|
||||||
|
# Shown in Sync version history. Applied by the manual `ob sync-setup`.
|
||||||
|
obsidian_headless_device_name: "{{ inventory_hostname_short }}"
|
||||||
|
|
||||||
|
# Top-level folders only obsidian_headless_user may enter (0700, no ACL).
|
||||||
|
obsidian_headless_private_folders: []
|
||||||
|
|
||||||
|
# Top-level folders another local user may read and write. Granted with named
|
||||||
|
# ACL entries plus matching DEFAULT entries, so files and folders Sync creates
|
||||||
|
# later inherit the grant. Everything not listed here is private: the unit runs
|
||||||
|
# with UMask=0077, and the vault root grants listed users traverse (--x) only,
|
||||||
|
# never list.
|
||||||
|
# - path: inbox
|
||||||
|
# user: multica
|
||||||
|
obsidian_headless_shared_folders: []
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Service
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
obsidian_headless_service_name: obsidian-sync
|
||||||
|
# Enabled either way; started only once `ob login` and `ob sync-setup` have
|
||||||
|
# been done by hand. Its Condition*= lines keep it inert until then.
|
||||||
|
obsidian_headless_service_enabled: true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Verification
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Prove the isolation as the shared users themselves: read/write in their
|
||||||
|
# folders, denied in every private folder and on listing the vault root.
|
||||||
|
obsidian_headless_verify: true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
# Restarts only a unit that is already running: before the manual sign-in the
|
||||||
|
# unit is inert, and starting it here would just trip its conditions.
|
||||||
|
- name: Restart obsidian sync
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: [systemctl, try-restart, "{{ obsidian_headless_service_name }}.service"]
|
||||||
|
changed_when: true
|
||||||
|
listen: restart obsidian sync
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: obsidian_headless
|
||||||
|
author: JARVIS
|
||||||
|
description: >-
|
||||||
|
Obsidian's official headless Sync client as a dedicated user under a
|
||||||
|
systemd unit, with per-folder isolation for other local users.
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.16"
|
||||||
|
platforms:
|
||||||
|
- name: Ubuntu
|
||||||
|
versions:
|
||||||
|
- noble
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
- name: Install | Read the installed client version
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ obsidian_headless_npm_prefix }}/lib/node_modules/obsidian-headless/package.json"
|
||||||
|
register: _obh_pkg
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Install | Decide whether an install is needed
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_obh_needs_install: >-
|
||||||
|
{{ _obh_pkg.content is not defined
|
||||||
|
or (_obh_pkg.content | b64decode | from_json).version != obsidian_headless_version }}
|
||||||
|
|
||||||
|
- name: Install | Install the pinned client as the service user
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- "{{ obsidian_headless_node_bin_dir }}/npm"
|
||||||
|
- install
|
||||||
|
- --global
|
||||||
|
- --prefix
|
||||||
|
- "{{ obsidian_headless_npm_prefix }}"
|
||||||
|
- --no-fund
|
||||||
|
- --no-audit
|
||||||
|
- "obsidian-headless@{{ obsidian_headless_version }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ obsidian_headless_user }}"
|
||||||
|
environment:
|
||||||
|
HOME: "{{ obsidian_headless_home }}"
|
||||||
|
PATH: "{{ obsidian_headless_node_bin_dir }}:/usr/bin:/bin"
|
||||||
|
when: _obh_needs_install | bool
|
||||||
|
changed_when: true
|
||||||
|
notify: restart obsidian sync
|
||||||
|
|
||||||
|
- name: Install | Link ob onto the default PATH
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ obsidian_headless_npm_prefix }}/bin/ob"
|
||||||
|
dest: "{{ obsidian_headless_link_dir }}/ob"
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
# Permission model
|
||||||
|
#
|
||||||
|
# <parent>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
||||||
|
# <vault>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
||||||
|
# <vault>/<private>/ obsync 0700 nobody else
|
||||||
|
# <vault>/<shared>/ obsync 0770 + user:<u>:rwx and user:obsync:rwx,
|
||||||
|
# each also as a DEFAULT entry, so new
|
||||||
|
# children inherit both
|
||||||
|
#
|
||||||
|
# The user:obsync entry matters for content the SHARED user creates: that is
|
||||||
|
# owned <u>:<u>, so without a named entry obsync falls into "other" (---)
|
||||||
|
# and Sync can neither read nor upload it.
|
||||||
|
#
|
||||||
|
# The owning group is obsync's private group, so its bits grant nobody. On a
|
||||||
|
# directory that carries an ACL, the group bits of the mode ARE the ACL mask,
|
||||||
|
# which is why the modes here are 0710/0770 rather than 0700: a 0700 chmod
|
||||||
|
# would zero the mask and silently void every named entry.
|
||||||
|
#
|
||||||
|
# Anything else in the vault (folders Sync creates later, .obsidian/) is born
|
||||||
|
# private: the service runs with UMask=0077, and the umask is ignored only
|
||||||
|
# under a directory that has a default ACL — i.e. only in the shared folders.
|
||||||
|
|
||||||
|
- name: Layout | Collect the users granted shared folders
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_obh_shared_users: "{{ obsidian_headless_shared_folders | map(attribute='user') | unique | list }}"
|
||||||
|
|
||||||
|
- name: Layout | Create the vault and its parent
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ obsidian_headless_user }}"
|
||||||
|
group: "{{ obsidian_headless_group }}"
|
||||||
|
mode: "{{ '0710' if _obh_shared_users | length > 0 else '0700' }}"
|
||||||
|
loop:
|
||||||
|
- "{{ obsidian_headless_vault_path | dirname }}"
|
||||||
|
- "{{ obsidian_headless_vault_path }}"
|
||||||
|
|
||||||
|
- name: Layout | Let shared users traverse (not list) the vault and its parent
|
||||||
|
ansible.posix.acl:
|
||||||
|
path: "{{ item.0 }}"
|
||||||
|
entity: "{{ item.1 }}"
|
||||||
|
etype: user
|
||||||
|
permissions: x
|
||||||
|
state: present
|
||||||
|
loop: >-
|
||||||
|
{{ [obsidian_headless_vault_path | dirname, obsidian_headless_vault_path]
|
||||||
|
| product(_obh_shared_users) | list }}
|
||||||
|
|
||||||
|
- name: Layout | Create the private folders
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ obsidian_headless_vault_path }}/{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ obsidian_headless_user }}"
|
||||||
|
group: "{{ obsidian_headless_group }}"
|
||||||
|
mode: "0700"
|
||||||
|
loop: "{{ obsidian_headless_private_folders }}"
|
||||||
|
|
||||||
|
- name: Layout | Create the shared folders
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ obsidian_headless_user }}"
|
||||||
|
group: "{{ obsidian_headless_group }}"
|
||||||
|
mode: "0770"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
|
||||||
|
# Each shared folder gets its user AND the service user, both as access and
|
||||||
|
# default entries.
|
||||||
|
- name: Layout | Build the shared-folder grants
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_obh_grants: >-
|
||||||
|
{{ (obsidian_headless_shared_folders
|
||||||
|
+ (obsidian_headless_shared_folders
|
||||||
|
| map(attribute='path')
|
||||||
|
| map('community.general.dict_kv', 'path') | list
|
||||||
|
| map('combine', {'user': obsidian_headless_user}) | list))
|
||||||
|
| unique | list }}
|
||||||
|
|
||||||
|
- name: Layout | Grant shared folders (access and default entries)
|
||||||
|
ansible.posix.acl:
|
||||||
|
path: "{{ obsidian_headless_vault_path }}/{{ item.0.path }}"
|
||||||
|
entity: "{{ item.0.user }}"
|
||||||
|
etype: user
|
||||||
|
permissions: rwx
|
||||||
|
default: "{{ item.1 }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ _obh_grants | product([false, true]) | list }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.0.path }} -> {{ item.0.user }}{{ ' (default)' if item.1 else '' }}"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
# Not `always`: that would also fire under unrelated tags of the calling
|
||||||
|
# playbook (e.g. --tags wong_disk). Every tag of this role carries it instead.
|
||||||
|
- name: Preflight
|
||||||
|
ansible.builtin.import_tasks: preflight.yml
|
||||||
|
tags: [obsidian, obsidian_user, obsidian_install, obsidian_layout, obsidian_service, obsidian_verify]
|
||||||
|
|
||||||
|
- name: Service account
|
||||||
|
ansible.builtin.import_tasks: user.yml
|
||||||
|
tags: [obsidian_user]
|
||||||
|
|
||||||
|
- name: Client
|
||||||
|
ansible.builtin.import_tasks: install.yml
|
||||||
|
tags: [obsidian_install]
|
||||||
|
|
||||||
|
- name: Vault layout
|
||||||
|
ansible.builtin.import_tasks: layout.yml
|
||||||
|
tags: [obsidian_layout]
|
||||||
|
|
||||||
|
- name: Service
|
||||||
|
ansible.builtin.import_tasks: service.yml
|
||||||
|
tags: [obsidian_service]
|
||||||
|
|
||||||
|
- name: Verify
|
||||||
|
ansible.builtin.import_tasks: verify.yml
|
||||||
|
when: obsidian_headless_verify | bool
|
||||||
|
tags: [obsidian_verify]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Preflight | Read the Node.js version
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: ["{{ obsidian_headless_node_bin_dir }}/node", "--version"]
|
||||||
|
register: _obh_node
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Preflight | Require Node.js new enough for obsidian-headless
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _obh_node.rc == 0
|
||||||
|
- (_obh_node.stdout | trim | regex_replace('^v', '')) is version(obsidian_headless_node_min_version, '>=')
|
||||||
|
fail_msg: >-
|
||||||
|
{{ obsidian_headless_node_bin_dir }}/node is missing or older than
|
||||||
|
{{ obsidian_headless_node_min_version }} (found:
|
||||||
|
{{ _obh_node.stdout | default('nothing') | trim }}). This role uses the
|
||||||
|
runtime multica_agent_host installs; run provision-agent-host.yml first.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Preflight | Look up the users granted shared folders
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ item }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders | map(attribute='user') | unique | list }}"
|
||||||
|
|
||||||
|
- name: Preflight | Require shared folders to stay disjoint from private ones
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- obsidian_headless_shared_folders | map(attribute='path') | intersect(obsidian_headless_private_folders) | length == 0
|
||||||
|
fail_msg: A folder cannot be both private and shared.
|
||||||
|
quiet: true
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
- name: Service | Install the unit
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: obsidian-sync.service.j2
|
||||||
|
dest: "/etc/systemd/system/{{ obsidian_headless_service_name }}.service"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: restart obsidian sync
|
||||||
|
|
||||||
|
- name: Service | Enable it at boot
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
name: "{{ obsidian_headless_service_name }}"
|
||||||
|
enabled: "{{ obsidian_headless_service_enabled | bool }}"
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
# Starting it before the manual sign-in would be a no-op (its conditions fail)
|
||||||
|
# that reports "changed" on every run. Start it only once it can actually sync.
|
||||||
|
- name: Service | Check whether the manual sign-in and vault setup are done
|
||||||
|
ansible.builtin.find:
|
||||||
|
paths: "{{ obsidian_headless_home }}/.config/obsidian-headless/sync"
|
||||||
|
file_type: directory
|
||||||
|
register: _obh_sync_configs
|
||||||
|
|
||||||
|
- name: Service | Start it once a vault is set up
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
name: "{{ obsidian_headless_service_name }}"
|
||||||
|
state: started
|
||||||
|
when:
|
||||||
|
- obsidian_headless_service_enabled | bool
|
||||||
|
- _obh_sync_configs.matched | default(0) > 0
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
- name: User | Create the group
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ obsidian_headless_group }}"
|
||||||
|
system: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
# nologin: nobody needs a shell as this user. `sudo -u obsync -H ob ...` runs
|
||||||
|
# the command directly and does not consult the login shell.
|
||||||
|
- name: User | Create the user
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ obsidian_headless_user }}"
|
||||||
|
group: "{{ obsidian_headless_group }}"
|
||||||
|
home: "{{ obsidian_headless_home }}"
|
||||||
|
shell: /usr/sbin/nologin
|
||||||
|
system: true
|
||||||
|
create_home: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: User | Keep the home private (holds the Obsidian account token)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ obsidian_headless_home }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ obsidian_headless_user }}"
|
||||||
|
group: "{{ obsidian_headless_group }}"
|
||||||
|
mode: "0700"
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
# Each check runs AS the user it is about, because file modes and ACLs only
|
||||||
|
# mean something when exercised. Probes are cleaned up; nothing is left behind.
|
||||||
|
|
||||||
|
- name: Verify | Shared users can write, read and delete in their folders
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'f="$1/.obh-verify.$$"; echo ok > "$f" && grep -q ok "$f" && rm -f "$f"'
|
||||||
|
- sh
|
||||||
|
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ item.user }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.user }} -> {{ item.path }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
# The reverse direction: what the shared user writes, Sync must be able to
|
||||||
|
# read (to upload it) and rewrite (to merge remote edits into it).
|
||||||
|
- name: Verify | Content a shared user creates stays usable by the service (create)
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'd="$1/.obh-verify-rev"; mkdir -p "$d" && echo ok > "$d/note.md"'
|
||||||
|
- sh
|
||||||
|
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ item.user }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.user }} -> {{ item.path }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Content a shared user creates stays usable by the service (use)
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'd="$1/.obh-verify-rev"; grep -q ok "$d/note.md" && echo more >> "$d/note.md" && echo ok > "$d/reply.md"'
|
||||||
|
- sh
|
||||||
|
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ obsidian_headless_user }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
register: _obh_reverse
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Remove the reverse probes
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ obsidian_headless_vault_path }}/{{ item.path }}/.obh-verify-rev"
|
||||||
|
state: absent
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Verify | The service can read and rewrite shared users' content
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _obh_reverse.results | rejectattr('rc', 'equalto', 0) | list | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
{{ obsidian_headless_user }} cannot read or write content a shared user
|
||||||
|
created; Sync would never upload it. Check the user:{{ obsidian_headless_user }}
|
||||||
|
default ACL entry on the shared folder.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
# Simulates the service: obsync, umask 0077, creating a folder and a file the
|
||||||
|
# way Sync does. The shared user must still reach both, via the default ACL.
|
||||||
|
- name: Verify | Content the service creates in a shared folder inherits the grant (create)
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'umask 0077; d="$1/.obh-verify-dir"; mkdir -p "$d" && echo ok > "$d/note.md"'
|
||||||
|
- sh
|
||||||
|
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ obsidian_headless_user }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Content the service creates in a shared folder inherits the grant (use)
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'd="$1/.obh-verify-dir"; grep -q ok "$d/note.md" && echo ok > "$d/reply.md"'
|
||||||
|
- sh
|
||||||
|
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||||
|
become: true
|
||||||
|
become_user: "{{ item.user }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.user }} -> {{ item.path }}"
|
||||||
|
register: _obh_inherit
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false
|
||||||
|
|
||||||
|
- name: Verify | Remove the inheritance probes
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ obsidian_headless_vault_path }}/{{ item.path }}/.obh-verify-dir"
|
||||||
|
state: absent
|
||||||
|
loop: "{{ obsidian_headless_shared_folders }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Verify | Inheritance held
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _obh_inherit.results | rejectattr('rc', 'equalto', 0) | list | length == 0
|
||||||
|
fail_msg: >-
|
||||||
|
A shared user could not read or write content created by
|
||||||
|
{{ obsidian_headless_user }} under umask 0077; the default ACL is not
|
||||||
|
being inherited.
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
# Every shared user must be refused: listing the vault root, and listing,
|
||||||
|
# reading into or writing into each private folder.
|
||||||
|
- name: Verify | Shared users are denied the vault root listing and every private folder
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: >-
|
||||||
|
{{ ['/bin/sh', '-c', _obh_deny_script, 'sh', obsidian_headless_vault_path]
|
||||||
|
+ obsidian_headless_private_folders }}
|
||||||
|
vars:
|
||||||
|
_obh_deny_script: |
|
||||||
|
v="$1"; shift
|
||||||
|
ls "$v" >/dev/null 2>&1 && { echo "can list $v"; exit 1; }
|
||||||
|
for p in "$@"; do
|
||||||
|
ls "$v/$p" >/dev/null 2>&1 && { echo "can list $p"; exit 1; }
|
||||||
|
touch "$v/$p/.obh-verify" 2>/dev/null && { rm -f "$v/$p/.obh-verify"; echo "can write $p"; exit 1; }
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
become: true
|
||||||
|
become_user: "{{ item }}"
|
||||||
|
loop: "{{ obsidian_headless_shared_folders | map(attribute='user') | unique | list }}"
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# {{ ansible_managed }}
|
||||||
|
[Unit]
|
||||||
|
Description=Obsidian Headless Sync ({{ obsidian_headless_vault_path }})
|
||||||
|
Documentation=https://obsidian.md/help/sync/headless
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
# Inert until `ob login` and `ob sync-setup` have been run by hand.
|
||||||
|
ConditionPathExists={{ obsidian_headless_home }}/.config/obsidian-headless/auth_token
|
||||||
|
ConditionDirectoryNotEmpty={{ obsidian_headless_home }}/.config/obsidian-headless/sync
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ obsidian_headless_user }}
|
||||||
|
Group={{ obsidian_headless_group }}
|
||||||
|
# Everything Sync creates is private unless it lands under a folder with a
|
||||||
|
# default ACL (the shared folders), where the umask does not apply.
|
||||||
|
UMask=0077
|
||||||
|
Environment=HOME={{ obsidian_headless_home }}
|
||||||
|
Environment=PATH={{ obsidian_headless_node_bin_dir }}:/usr/bin:/bin
|
||||||
|
WorkingDirectory={{ obsidian_headless_vault_path }}
|
||||||
|
ExecStart={{ obsidian_headless_link_dir }}/ob sync --continuous --path {{ obsidian_headless_vault_path }}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=30
|
||||||
|
# 2: missing encryption key, 3: no sync configuration. Both need a human.
|
||||||
|
RestartPreventExitStatus=2 3
|
||||||
|
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
ReadWritePaths={{ obsidian_headless_vault_path }} {{ obsidian_headless_home }}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
---
|
||||||
|
# Offline regression tests for the role's Jinja expressions, run against
|
||||||
|
# fixtures captured from a real `multica` CLI. No target host required:
|
||||||
|
#
|
||||||
|
# ansible-playbook -i localhost, tests/expressions.yml
|
||||||
|
#
|
||||||
|
# This does NOT replace the scratch-VM run described in the role README; it
|
||||||
|
# catches the class of bug where a filter chain silently returns the wrong
|
||||||
|
# type or quietly matches nothing.
|
||||||
|
- name: Expressions | parsing real multica CLI output
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
multica_server_url: "https://multica.wdw-labs.cloud"
|
||||||
|
multica_app_url: ""
|
||||||
|
multica_workspaces_root: "/srv/multica/workspaces"
|
||||||
|
multica_device_name: "rblundon--Mac16"
|
||||||
|
multica_runtime_name: ""
|
||||||
|
multica_max_concurrent_tasks: ""
|
||||||
|
multica_poll_interval: ""
|
||||||
|
multica_heartbeat_interval: ""
|
||||||
|
multica_agent_timeout: ""
|
||||||
|
multica_disable_auto_update: true
|
||||||
|
multica_version: "0.6.5"
|
||||||
|
multica_agent_clis: [claude, hermes]
|
||||||
|
multica_agent_cli_catalog:
|
||||||
|
claude: {provider: claude, method: npm}
|
||||||
|
hermes: {provider: hermes, method: script}
|
||||||
|
cursor: {provider: cursor, method: npm}
|
||||||
|
_multica_arch_map: {x86_64: amd64, aarch64: arm64}
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Load fixtures
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
rt: "{{ lookup('file', '{{ playbook_dir }}/fixtures/runtime-list.json') }}"
|
||||||
|
ds: "{{ lookup('file', '{{ playbook_dir }}/fixtures/daemon-status.json') }}"
|
||||||
|
cs_lines: "{{ lookup('file', '{{ playbook_dir }}/fixtures/config-show.txt').splitlines() }}"
|
||||||
|
ver_out: "multica 0.6.5 (commit: 654b8ca95, built: 2026-10-21T09:41:38Z)"
|
||||||
|
|
||||||
|
# --- CLI version extraction (multica_cli.yml) ---------------------------
|
||||||
|
- name: Extract installed CLI version
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
found: >-
|
||||||
|
{{ ((([ver_out] | first | default('')).split() + ['', ''])[1]) }}
|
||||||
|
- name: Assert version extraction
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [(found | trim) == multica_version]
|
||||||
|
success_msg: "CLI version extraction OK -> {{ found }}"
|
||||||
|
|
||||||
|
# --- config show parsing (config.yml) -----------------------------------
|
||||||
|
- name: Parse config show
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
current: >-
|
||||||
|
{{ dict(
|
||||||
|
cs_lines
|
||||||
|
| select('match', '^[a-z_]+:')
|
||||||
|
| map('regex_replace', '^([a-z_]+):\s*(.*?)\s*$', '\1|\2')
|
||||||
|
| map('split', '|')
|
||||||
|
) }}
|
||||||
|
- name: Build desired config
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
desired: >-
|
||||||
|
{{ {
|
||||||
|
'server_url': multica_server_url,
|
||||||
|
'app_url': multica_app_url,
|
||||||
|
'workspaces_root': multica_workspaces_root,
|
||||||
|
'device_name': multica_device_name,
|
||||||
|
'runtime_name': multica_runtime_name,
|
||||||
|
'max_concurrent_tasks': multica_max_concurrent_tasks,
|
||||||
|
'poll_interval': multica_poll_interval,
|
||||||
|
'heartbeat_interval': multica_heartbeat_interval,
|
||||||
|
'agent_timeout': multica_agent_timeout,
|
||||||
|
'disable_auto_update': ('true' if multica_disable_auto_update | bool else 'false'),
|
||||||
|
} | dict2items
|
||||||
|
| rejectattr('value', 'equalto', '')
|
||||||
|
| list | items2dict }}
|
||||||
|
- name: Show parse results
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
parsed_keys: "{{ current.keys() | list }}"
|
||||||
|
disable_auto_update_now: "{{ current['disable_auto_update'] | default('MISSING') }}"
|
||||||
|
server_url_now: "{{ current['server_url'] | default('MISSING') }}"
|
||||||
|
desired: "{{ desired }}"
|
||||||
|
would_change: >-
|
||||||
|
{{ desired | dict2items
|
||||||
|
| selectattr('key', 'in', current.keys() | list)
|
||||||
|
| rejectattr('value', 'equalto', 'IMPOSSIBLE')
|
||||||
|
| map(attribute='key') | list }}
|
||||||
|
- name: Assert config parsing found the expected keys
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "'server_url' in current"
|
||||||
|
- "'disable_auto_update' in current"
|
||||||
|
- current['server_url'] == '(not set)'
|
||||||
|
- current['disable_auto_update'] == 'false'
|
||||||
|
- desired | length == 4
|
||||||
|
- "'app_url' not in desired"
|
||||||
|
success_msg: "config show parsing + desired-config filtering OK"
|
||||||
|
|
||||||
|
# --- runtime verification (verify.yml) ----------------------------------
|
||||||
|
- name: Compute online providers for this daemon
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
expected: "{{ multica_agent_clis | map('extract', multica_agent_cli_catalog, 'provider') | list }}"
|
||||||
|
online: >-
|
||||||
|
{{ rt | from_json
|
||||||
|
| selectattr('daemon_id', 'equalto', (ds | from_json).daemon_id)
|
||||||
|
| selectattr('status', 'equalto', 'online')
|
||||||
|
| map(attribute='provider') | list }}
|
||||||
|
- name: Assert every expected provider is online
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [(expected | difference(online)) | length == 0]
|
||||||
|
success_msg: "runtime check OK: expected={{ expected }} online={{ online }}"
|
||||||
|
|
||||||
|
- name: Assert device_info naming check
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- rt | from_json
|
||||||
|
| selectattr('daemon_id', 'equalto', (ds | from_json).daemon_id)
|
||||||
|
| rejectattr('device_info', 'search', '^' ~ (multica_device_name | regex_escape))
|
||||||
|
| list | length == 0
|
||||||
|
success_msg: "device_info check OK for {{ multica_device_name }}"
|
||||||
|
|
||||||
|
- name: Assert daemon status fields
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- (ds | from_json).status == 'running'
|
||||||
|
- (ds | from_json).cli_version == multica_version
|
||||||
|
- ((ds | from_json).server_url | regex_replace('/$','')) == (multica_server_url | regex_replace('/$',''))
|
||||||
|
success_msg: "daemon status assertions OK"
|
||||||
|
|
||||||
|
# --- negative case: a provider that is NOT online ------------------------
|
||||||
|
- name: Assert a missing provider is detected
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- (['codex'] | difference(online)) | length == 1
|
||||||
|
success_msg: "missing-provider detection OK"
|
||||||
|
|
||||||
|
- name: Expressions | preflight fact-building
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
multica_install_dir: /usr/local/bin
|
||||||
|
multica_agent_cli_catalog:
|
||||||
|
claude: {provider: claude, method: npm}
|
||||||
|
hermes: {provider: hermes, method: script}
|
||||||
|
multica_release_download_base_url: "https://github.com/multica-ai/multica/releases/download"
|
||||||
|
multica_version: "0.6.5"
|
||||||
|
_multica_arch_map: {x86_64: amd64, aarch64: arm64}
|
||||||
|
tasks:
|
||||||
|
- name: Case - profile unset, npm CLI selected
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
multica_profile: ""
|
||||||
|
multica_agent_clis: [claude, hermes]
|
||||||
|
_multica_arch: "{{ _multica_arch_map['x86_64'] }}"
|
||||||
|
- name: Build facts
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_cmd: >-
|
||||||
|
{{ [multica_install_dir ~ '/multica']
|
||||||
|
+ (['--profile', multica_profile] if multica_profile | length > 0 else []) }}
|
||||||
|
_multica_node_required: >-
|
||||||
|
{{ multica_agent_clis
|
||||||
|
| map('extract', multica_agent_cli_catalog, 'method')
|
||||||
|
| select('equalto', 'npm') | list | length > 0 }}
|
||||||
|
_multica_release_url: >-
|
||||||
|
{{ multica_release_download_base_url }}/v{{ multica_version }}/multica-cli-{{ multica_version }}-linux-{{ _multica_arch }}.tar.gz
|
||||||
|
- name: Assert types and values
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _multica_cmd is sequence
|
||||||
|
- _multica_cmd is not string
|
||||||
|
- _multica_cmd == ['/usr/local/bin/multica']
|
||||||
|
- (_multica_cmd + ['daemon', 'status']) == ['/usr/local/bin/multica', 'daemon', 'status']
|
||||||
|
- _multica_node_required | bool
|
||||||
|
- _multica_release_url == 'https://github.com/multica-ai/multica/releases/download/v0.6.5/multica-cli-0.6.5-linux-amd64.tar.gz'
|
||||||
|
success_msg: "argv prefix + node detection + URL OK: {{ _multica_cmd }}"
|
||||||
|
|
||||||
|
- name: Case - profile set, script-only CLI selected
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
multica_profile: "haunted"
|
||||||
|
multica_agent_clis: [hermes]
|
||||||
|
- name: Rebuild facts
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_multica_cmd: >-
|
||||||
|
{{ [multica_install_dir ~ '/multica']
|
||||||
|
+ (['--profile', multica_profile] if multica_profile | length > 0 else []) }}
|
||||||
|
_multica_node_required: >-
|
||||||
|
{{ multica_agent_clis
|
||||||
|
| map('extract', multica_agent_cli_catalog, 'method')
|
||||||
|
| select('equalto', 'npm') | list | length > 0 }}
|
||||||
|
- name: Assert profile + no-node case
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _multica_cmd == ['/usr/local/bin/multica', '--profile', 'haunted']
|
||||||
|
- not (_multica_node_required | bool)
|
||||||
|
success_msg: "profile prefix + node-not-required OK: {{ _multica_cmd }}"
|
||||||
|
|
||||||
|
- name: Assert PAT prefix validation accepts both token kinds and rejects junk
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "'mul_abc123' is match('^(mul_|mcn_)')"
|
||||||
|
- "'mcn_abc123' is match('^(mul_|mcn_)')"
|
||||||
|
- "not ('ghp_abc123' is match('^(mul_|mcn_)'))"
|
||||||
|
- "not ('' is match('^(mul_|mcn_)'))"
|
||||||
|
success_msg: "PAT validation OK"
|
||||||
|
|
||||||
|
- name: Assert unknown-agent detection
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "['claude', 'bogus'] | difference(multica_agent_cli_catalog.keys() | list) == ['bogus']"
|
||||||
|
- "['claude', 'hermes'] | difference(multica_agent_cli_catalog.keys() | list) | length == 0"
|
||||||
|
success_msg: "catalog validation OK"
|
||||||
|
|
||||||
|
- name: Assert workspaces-root-under-mount logic
|
||||||
|
vars:
|
||||||
|
root: "/mnt/utilidor/multica/workspaces"
|
||||||
|
mnt: "/mnt/utilidor"
|
||||||
|
bad_root: "/mnt/utilidor-other/workspaces"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- root == mnt or root.startswith(mnt.rstrip('/') ~ '/')
|
||||||
|
- not (bad_root == mnt or bad_root.startswith(mnt.rstrip('/') ~ '/'))
|
||||||
|
success_msg: "mount containment check OK (rejects /mnt/utilidor-other)"
|
||||||
|
|
||||||
|
- name: Expressions | pinned agent-CLI version guard
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
# Mirrors the expression in tasks/agent_cli/npm.yml.
|
||||||
|
guard: >-
|
||||||
|
{{ rc != 0
|
||||||
|
or (pin | default('') | length > 0
|
||||||
|
and not ((out | default('')) is search(
|
||||||
|
'(^|[^0-9.])' ~ (pin | regex_escape) ~ '([^0-9.]|$)'))) }}
|
||||||
|
tasks:
|
||||||
|
- name: Pinned version already installed - must NOT reinstall
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [not (guard | bool)]
|
||||||
|
success_msg: "2.1.304 pin vs '2.1.304 (Claude Code)' -> no install"
|
||||||
|
vars: {rc: 0, pin: "2.1.304", out: "2.1.304 (Claude Code)"}
|
||||||
|
|
||||||
|
- name: Different version installed - must reinstall
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [guard | bool]
|
||||||
|
success_msg: "2.1.304 pin vs 2.1.306 installed -> install"
|
||||||
|
vars: {rc: 0, pin: "2.1.304", out: "2.1.306 (Claude Code)"}
|
||||||
|
|
||||||
|
- name: Longer version must not satisfy a shorter pin
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [guard | bool]
|
||||||
|
success_msg: "2.1.304 pin vs 2.1.3045 installed -> install (no substring false-match)"
|
||||||
|
vars: {rc: 0, pin: "2.1.304", out: "2.1.3045 (Claude Code)"}
|
||||||
|
|
||||||
|
- name: Prefix of installed version must not satisfy the pin
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [guard | bool]
|
||||||
|
success_msg: "2.1.30 pin vs 2.1.304 installed -> install"
|
||||||
|
vars: {rc: 0, pin: "2.1.30", out: "2.1.304 (Claude Code)"}
|
||||||
|
|
||||||
|
- name: Version embedded mid-string still matches (hermes-style output)
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [not (guard | bool)]
|
||||||
|
success_msg: "0.21.5 pin vs 'Hermes Agent v0.21.5+2024.gb609c74' -> no install"
|
||||||
|
vars: {rc: 0, pin: "0.21.5", out: "Hermes Agent v0.21.5+2024.gb609c74 (2026.9.24)"}
|
||||||
|
|
||||||
|
- name: Binary absent - must install regardless of pin
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [guard | bool]
|
||||||
|
success_msg: "rc!=0 -> install"
|
||||||
|
vars: {rc: 1, pin: "2.1.304", out: ""}
|
||||||
|
|
||||||
|
- name: Unpinned tracks latest - never reinstalls once present
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: [not (guard | bool)]
|
||||||
|
success_msg: "empty pin + present -> no install"
|
||||||
|
vars: {rc: 0, pin: "", out: "2.1.306 (Claude Code)"}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
Config file: /home/multica/.config/multica/config.json
|
||||||
|
server_url: (not set)
|
||||||
|
app_url: (not set)
|
||||||
|
workspace_id: (not set)
|
||||||
|
device_name: (not set)
|
||||||
|
runtime_name: (not set)
|
||||||
|
workspaces_root: (not set)
|
||||||
|
max_concurrent_tasks: (not set)
|
||||||
|
poll_interval: (not set)
|
||||||
|
ws_claim_poll_interval: (not set)
|
||||||
|
heartbeat_interval: (not set)
|
||||||
|
agent_timeout: (not set)
|
||||||
|
codex_semantic_inactivity_timeout: (not set)
|
||||||
|
codex_handshake_timeout: (not set)
|
||||||
|
disable_auto_update: false
|
||||||
|
auto_update_check_interval: (not set)
|
||||||
|
disable_auto_reload: false
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"active_task_count": 1,
|
||||||
|
"agents": [
|
||||||
|
"claude",
|
||||||
|
"opencode",
|
||||||
|
"hermes",
|
||||||
|
"cursor"
|
||||||
|
],
|
||||||
|
"cli_commit": "654b8ca954034f8181eb6bbb4fc298dede760156",
|
||||||
|
"cli_version": "0.6.5",
|
||||||
|
"daemon_id": "01a15ad9-f27e-72da-a1a5-53ec15b53f9d",
|
||||||
|
"device_name": "rblundon--Mac16",
|
||||||
|
"failed_terminal_report_bytes": 0,
|
||||||
|
"failed_terminal_report_count": 0,
|
||||||
|
"launched_by": "desktop",
|
||||||
|
"os": "darwin",
|
||||||
|
"pending_terminal_report_bytes": 0,
|
||||||
|
"pending_terminal_report_count": 0,
|
||||||
|
"pid": 34832,
|
||||||
|
"profile": "desktop-multica.wdw-labs.cloud",
|
||||||
|
"resource_wait_task_count": 0,
|
||||||
|
"running_task_count": 1,
|
||||||
|
"server_url": "https://multica.wdw-labs.cloud",
|
||||||
|
"status": "running",
|
||||||
|
"uptime": "4h12m4s",
|
||||||
|
"workspaces": [
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000001",
|
||||||
|
"runtimes": [
|
||||||
|
"ea210ad4-b0e8-4663-a212-7d6878668ec7",
|
||||||
|
"eb68ea3f-78d6-4214-974d-9022aff55216",
|
||||||
|
"245f271d-339b-47d5-8ea6-1e7eb6576ece",
|
||||||
|
"c8316de7-5668-4276-8b31-3e7f873b8499"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"created_at": "2026-10-20T19:56:21Z",
|
||||||
|
"custom_name": null,
|
||||||
|
"daemon_id": "01a15ad9-f27e-72da-a1a5-53ec15b53f9d",
|
||||||
|
"device_info": "rblundon--Mac16 \u00b7 2.1.274 (Claude Code)",
|
||||||
|
"id": "ea210ad4-b0e8-4663-a212-7d6878668ec7",
|
||||||
|
"last_seen_at": "2026-10-21T19:47:35Z",
|
||||||
|
"launch_header": "claude (stream-json)",
|
||||||
|
"metadata": {
|
||||||
|
"capabilities": [
|
||||||
|
"skill-bundles-v1",
|
||||||
|
"coalesced-comments-v1",
|
||||||
|
"execution-manifest-v1",
|
||||||
|
"agent-skill-v1",
|
||||||
|
"remote-mcp-v1",
|
||||||
|
"local-worktree-v1",
|
||||||
|
"source_context_quick_create_v1",
|
||||||
|
"rpc-v1",
|
||||||
|
"platform-skill-v1",
|
||||||
|
"checkout-keeps-work-v1",
|
||||||
|
"joined-wakeups-v1"
|
||||||
|
],
|
||||||
|
"cli_version": "0.6.5",
|
||||||
|
"launched_by": "desktop",
|
||||||
|
"version": "2.1.274 (Claude Code)"
|
||||||
|
},
|
||||||
|
"name": "Claude (rblundon--Mac16)",
|
||||||
|
"owner_id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"profile_id": null,
|
||||||
|
"provider": "claude",
|
||||||
|
"runtime_mode": "local",
|
||||||
|
"status": "online",
|
||||||
|
"updated_at": "2026-10-21T17:09:10Z",
|
||||||
|
"visibility": "private",
|
||||||
|
"workspace_id": "00000000-0000-0000-0000-000000000001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created_at": "2026-10-20T19:56:22Z",
|
||||||
|
"custom_name": null,
|
||||||
|
"daemon_id": "01a15ad9-f27e-72da-a1a5-53ec15b53f9d",
|
||||||
|
"device_info": "rblundon--Mac16 \u00b7 2025.09.18-7ae6800",
|
||||||
|
"id": "eb68ea3f-78d6-4214-974d-9022aff55216",
|
||||||
|
"last_seen_at": "2026-10-21T19:47:35Z",
|
||||||
|
"launch_header": "cursor-agent (stream-json)",
|
||||||
|
"metadata": {
|
||||||
|
"capabilities": [
|
||||||
|
"skill-bundles-v1",
|
||||||
|
"coalesced-comments-v1",
|
||||||
|
"execution-manifest-v1",
|
||||||
|
"agent-skill-v1",
|
||||||
|
"remote-mcp-v1",
|
||||||
|
"local-worktree-v1",
|
||||||
|
"source_context_quick_create_v1",
|
||||||
|
"rpc-v1",
|
||||||
|
"platform-skill-v1",
|
||||||
|
"checkout-keeps-work-v1",
|
||||||
|
"joined-wakeups-v1"
|
||||||
|
],
|
||||||
|
"cli_version": "0.6.5",
|
||||||
|
"launched_by": "desktop",
|
||||||
|
"version": "2025.09.18-7ae6800"
|
||||||
|
},
|
||||||
|
"name": "Cursor (rblundon--Mac16)",
|
||||||
|
"owner_id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"profile_id": null,
|
||||||
|
"provider": "cursor",
|
||||||
|
"runtime_mode": "local",
|
||||||
|
"status": "online",
|
||||||
|
"updated_at": "2026-10-21T17:09:10Z",
|
||||||
|
"visibility": "private",
|
||||||
|
"workspace_id": "00000000-0000-0000-0000-000000000001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created_at": "2026-10-20T19:56:22Z",
|
||||||
|
"custom_name": null,
|
||||||
|
"daemon_id": "01a15ad9-f27e-72da-a1a5-53ec15b53f9d",
|
||||||
|
"device_info": "rblundon--Mac16 \u00b7 Hermes Agent v0.21.5+2024.gb609c74 (2026.9.24) \u00b7 upstream f1247d2e",
|
||||||
|
"id": "245f271d-339b-47d5-8ea6-1e7eb6576ece",
|
||||||
|
"last_seen_at": "2026-10-21T19:47:35Z",
|
||||||
|
"launch_header": "hermes acp",
|
||||||
|
"metadata": {
|
||||||
|
"capabilities": [
|
||||||
|
"skill-bundles-v1",
|
||||||
|
"coalesced-comments-v1",
|
||||||
|
"execution-manifest-v1",
|
||||||
|
"agent-skill-v1",
|
||||||
|
"remote-mcp-v1",
|
||||||
|
"local-worktree-v1",
|
||||||
|
"source_context_quick_create_v1",
|
||||||
|
"rpc-v1",
|
||||||
|
"platform-skill-v1",
|
||||||
|
"checkout-keeps-work-v1",
|
||||||
|
"joined-wakeups-v1"
|
||||||
|
],
|
||||||
|
"cli_version": "0.6.5",
|
||||||
|
"launched_by": "desktop",
|
||||||
|
"version": "Hermes Agent v0.21.5+2024.gb609c74 (2026.9.24) \u00b7 upstream f1247d2e"
|
||||||
|
},
|
||||||
|
"name": "Hermes (rblundon--Mac16)",
|
||||||
|
"owner_id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"profile_id": null,
|
||||||
|
"provider": "hermes",
|
||||||
|
"runtime_mode": "local",
|
||||||
|
"status": "online",
|
||||||
|
"updated_at": "2026-10-21T17:09:10Z",
|
||||||
|
"visibility": "private",
|
||||||
|
"workspace_id": "00000000-0000-0000-0000-000000000001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created_at": "2026-10-20T19:56:22Z",
|
||||||
|
"custom_name": null,
|
||||||
|
"daemon_id": "01a15ad9-f27e-72da-a1a5-53ec15b53f9d",
|
||||||
|
"device_info": "rblundon--Mac16 \u00b7 1.18.20",
|
||||||
|
"id": "c8316de7-5668-4276-8b31-3e7f873b8499",
|
||||||
|
"last_seen_at": "2026-10-21T19:47:35Z",
|
||||||
|
"launch_header": "opencode run (json)",
|
||||||
|
"metadata": {
|
||||||
|
"capabilities": [
|
||||||
|
"skill-bundles-v1",
|
||||||
|
"coalesced-comments-v1",
|
||||||
|
"execution-manifest-v1",
|
||||||
|
"agent-skill-v1",
|
||||||
|
"remote-mcp-v1",
|
||||||
|
"local-worktree-v1",
|
||||||
|
"source_context_quick_create_v1",
|
||||||
|
"rpc-v1",
|
||||||
|
"platform-skill-v1",
|
||||||
|
"checkout-keeps-work-v1",
|
||||||
|
"joined-wakeups-v1"
|
||||||
|
],
|
||||||
|
"cli_version": "0.6.5",
|
||||||
|
"launched_by": "desktop",
|
||||||
|
"version": "1.18.20"
|
||||||
|
},
|
||||||
|
"name": "Opencode (rblundon--Mac16)",
|
||||||
|
"owner_id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"profile_id": null,
|
||||||
|
"provider": "opencode",
|
||||||
|
"runtime_mode": "local",
|
||||||
|
"status": "online",
|
||||||
|
"updated_at": "2026-10-21T17:09:10Z",
|
||||||
|
"visibility": "private",
|
||||||
|
"workspace_id": "00000000-0000-0000-0000-000000000001"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
services:
|
|
||||||
gitea:
|
|
||||||
image: docker.io/gitea/gitea:1.25.4
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- USER_UID=1000
|
|
||||||
- USER_GID=1000
|
|
||||||
- GITEA__database__DB_TYPE=postgres
|
|
||||||
- GITEA__database__HOST=gitea_db
|
|
||||||
- GITEA__database__NAME=gitea
|
|
||||||
- GITEA__database__USER=gitea
|
|
||||||
- GITEA__database__PASSWD=${DATABASE_PASSWORD}
|
|
||||||
- GITEA__server__SSH_PORT=2221
|
|
||||||
- GITEA__server__ROOT_URL=https://gitea.mk-labs.cloud
|
|
||||||
- GITEA__server__DOMAIN=gitea.mk-labs.cloud
|
|
||||||
- GITEA__server__HTTP_PORT=3000
|
|
||||||
- GITEA__service__TRUSTED_PROXIES=10.1.71.35
|
|
||||||
networks:
|
|
||||||
- gitea_backend
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
- "2221:22"
|
|
||||||
volumes:
|
|
||||||
- gitea_data:/data
|
|
||||||
- /etc/timezone:/etc/timezone:ro
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
depends_on:
|
|
||||||
gitea_db:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
gitea_db:
|
|
||||||
image: docker.io/library/postgres:17.8
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- TZ=America/Chicago
|
|
||||||
- POSTGRES_USER=gitea
|
|
||||||
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
|
|
||||||
- POSTGRES_DB=gitea
|
|
||||||
networks:
|
|
||||||
- gitea_backend
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
|
||||||
start_period: 30s
|
|
||||||
interval: 10s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- gitea_db:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
gitea_data:
|
|
||||||
driver: local
|
|
||||||
gitea_db:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
gitea_backend:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
http:
|
http:
|
||||||
routers:
|
routers:
|
||||||
gitea:
|
gitea:
|
||||||
rule: "Host(`gitea.mk-labs.cloud`)"
|
rule: "Host(`gitea.mk-labs.cloud`) || Host(`mad-tea-party.wdw-labs.cloud`)"
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
tls:
|
tls:
|
||||||
certResolver: cloudflare
|
certResolver: cloudflare
|
||||||
domains:
|
domains:
|
||||||
- main: "gitea.mk-labs.cloud"
|
- main: "gitea.mk-labs.cloud"
|
||||||
|
sans:
|
||||||
|
- "mad-tea-party.wdw-labs.cloud"
|
||||||
service: gitea
|
service: gitea
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# App of Apps — Root
|
||||||
|
#
|
||||||
|
# This is applied ONCE after ArgoCD is bootstrapped. It is the second and
|
||||||
|
# final manual kubectl apply:
|
||||||
|
#
|
||||||
|
# kubectl apply -n argocd -f cluster/argocd/apps-of-apps.yaml
|
||||||
|
#
|
||||||
|
# From this point forward, all changes are made via git. ArgoCD self-manages.
|
||||||
|
#
|
||||||
|
# This Application watches cluster/platform/ and cluster/applications/ and
|
||||||
|
# creates child Applications for everything it finds there.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: apps-of-apps
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
# Wave 0 — this is the root, everything else comes after
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://mad-tea-party.wdw-labs.cloud/rblundon/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: fastpass
|
||||||
|
directory:
|
||||||
|
recurse: true
|
||||||
|
include: "*/application.yaml"
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: argocd
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
|
ignoreDifferences:
|
||||||
|
- group: argoproj.io
|
||||||
|
kind: Application
|
||||||
|
jsonPointers:
|
||||||
|
- /spec/syncPolicy/automated
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ExternalSecret — Authentik OIDC credentials for ArgoCD
|
||||||
|
#
|
||||||
|
# Targets argocd-secret directly — ArgoCD reads oidc.authentik.* keys
|
||||||
|
# from its own secret. ESO merges these fields into the existing secret
|
||||||
|
# without overwriting admin.password etc. (creationPolicy: Merge)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: argocd-oidc-secret
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: onepassword-connect
|
||||||
|
refreshInterval: "1h"
|
||||||
|
target:
|
||||||
|
name: argocd-secret
|
||||||
|
creationPolicy: Merge
|
||||||
|
data:
|
||||||
|
- secretKey: oidc.authentik.clientID
|
||||||
|
remoteRef:
|
||||||
|
key: argocd-oidc
|
||||||
|
property: client-id
|
||||||
|
- secretKey: oidc.authentik.clientSecret
|
||||||
|
remoteRef:
|
||||||
|
key: argocd-oidc
|
||||||
|
property: client-secret
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ArgoCD Install — v3.4.2
|
||||||
|
#
|
||||||
|
# This is NOT applied by ArgoCD itself (chicken-and-egg).
|
||||||
|
# It is the ONE manual bootstrap step:
|
||||||
|
#
|
||||||
|
# kubectl create namespace argocd
|
||||||
|
# kubectl apply -n argocd --server-side --force-conflicts -f install.yaml
|
||||||
|
#
|
||||||
|
# After this, everything else is driven by apps-of-apps.yaml.
|
||||||
|
#
|
||||||
|
# To upgrade ArgoCD: update the version below and re-apply.
|
||||||
|
# --server-side --force-conflicts is required due to CRD size limits.
|
||||||
|
#
|
||||||
|
# Source: https://github.com/argoproj/argo-cd/releases/tag/v3.4.2
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# kubectl apply -n argocd --server-side --force-conflicts -f \
|
||||||
|
# https://raw.githubusercontent.com/argoproj/argo-cd/v3.4.2/manifests/install.yaml
|
||||||
|
#
|
||||||
|
# We reference the upstream manifest directly rather than vendoring it.
|
||||||
|
# Pin upgrades by changing the version tag above and re-running the command.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: argocd
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# ─── ArgoCD Helm values — fastpass cluster ─────────────────────────────────────
|
||||||
|
# Chart: argo/argo-cd
|
||||||
|
# Version: 7.8.23
|
||||||
|
#
|
||||||
|
# Install:
|
||||||
|
# helm repo add argo https://argoproj.github.io/argo-helm
|
||||||
|
# helm repo update
|
||||||
|
# helm upgrade --install argocd argo/argo-cd \
|
||||||
|
# --namespace argocd \
|
||||||
|
# --create-namespace \
|
||||||
|
# --version 7.8.23 \
|
||||||
|
# --values cluster/argocd/values.yaml \
|
||||||
|
# --wait
|
||||||
|
#
|
||||||
|
# Then apply the root app-of-apps (second and final manual step):
|
||||||
|
# kubectl apply -n argocd -f cluster/argocd/apps-of-apps.yaml
|
||||||
|
# ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# ─── Global ─────────────────────────────────────────────────────────────────────
|
||||||
|
global:
|
||||||
|
domain: carousel-of-progress.wdw-labs.cloud
|
||||||
|
|
||||||
|
# ─── ArgoCD server ──────────────────────────────────────────────────────────────
|
||||||
|
server:
|
||||||
|
# Run insecure — TLS is terminated at ingress-nginx, not at ArgoCD itself.
|
||||||
|
# Without this, ArgoCD redirects to HTTPS internally and breaks behind ingress.
|
||||||
|
extraArgs:
|
||||||
|
- --insecure
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
ingressClassName: nginx
|
||||||
|
hostname: carousel-of-progress.wdw-labs.cloud
|
||||||
|
tls: true
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
nginx.ingress.kubernetes.io/ssl-passthrough: "false"
|
||||||
|
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||||
|
|
||||||
|
# ─── ConfigMap — core ArgoCD settings ───────────────────────────────────────────
|
||||||
|
configs:
|
||||||
|
params:
|
||||||
|
server.insecure: "true"
|
||||||
|
server.session.duration: "720h"
|
||||||
|
|
||||||
|
cm:
|
||||||
|
url: https://carousel-of-progress.wdw-labs.cloud
|
||||||
|
|
||||||
|
# Authentik native OIDC SSO
|
||||||
|
# Client secret pulled from 1Password via ESO → argocd-oidc-secret
|
||||||
|
oidc.config: |
|
||||||
|
name: Authentik
|
||||||
|
issuer: https://authentik.local.mk-labs.cloud/application/o/argocd/
|
||||||
|
clientID: $oidc.authentik.clientID
|
||||||
|
clientSecret: $oidc.authentik.clientSecret
|
||||||
|
requestedScopes:
|
||||||
|
- openid
|
||||||
|
- profile
|
||||||
|
- email
|
||||||
|
- groups
|
||||||
|
groupsClaim: groups
|
||||||
|
|
||||||
|
# Resource health checks — ignore progressing Cilium CRDs
|
||||||
|
resource.customizations.health.cilium.io_CiliumLoadBalancerIPPool: |
|
||||||
|
hs = {}
|
||||||
|
hs.status = "Healthy"
|
||||||
|
hs.message = "IP pool ready"
|
||||||
|
return hs
|
||||||
|
|
||||||
|
# Ignore ESO-injected default fields on ExternalSecret resources.
|
||||||
|
resource.customizations.ignoreDifferences.external-secrets.io_ExternalSecret: |
|
||||||
|
jqPathExpressions:
|
||||||
|
- '.spec.data[]?.remoteRef?.conversionStrategy'
|
||||||
|
- '.spec.data[]?.remoteRef?.decodingStrategy'
|
||||||
|
- '.spec.data[]?.remoteRef?.metadataPolicy'
|
||||||
|
- '.spec.dataFrom[]?.extract?.conversionStrategy'
|
||||||
|
- '.spec.dataFrom[]?.extract?.decodingStrategy'
|
||||||
|
- '.spec.dataFrom[]?.extract?.metadataPolicy'
|
||||||
|
|
||||||
|
# RBAC — argocd-admins group from Authentik gets full access
|
||||||
|
rbac:
|
||||||
|
policy.default: role:readonly
|
||||||
|
policy.csv: |
|
||||||
|
g, argocd-admins, role:admin
|
||||||
|
g, argocd-viewers, role:readonly
|
||||||
|
|
||||||
|
# Repository credentials — Gitea SSH
|
||||||
|
repositories:
|
||||||
|
homelab-repo:
|
||||||
|
url: git@gitea.mk-labs.cloud:rblundon/homelab.git
|
||||||
|
name: homelab
|
||||||
|
type: git
|
||||||
|
insecure: "false"
|
||||||
|
|
||||||
|
# ─── Redis ──────────────────────────────────────────────────────────────────────
|
||||||
|
redis:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# ─── Notifications ──────────────────────────────────────────────────────────────
|
||||||
|
# Disabled for now — wire into n8n via webhook later
|
||||||
|
notifications:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# ─── ApplicationSet controller ──────────────────────────────────────────────────
|
||||||
|
applicationSet:
|
||||||
|
enabled: true
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# ─── Dex ────────────────────────────────────────────────────────────────────────
|
||||||
|
# Disabled — using Authentik as the native OIDC provider
|
||||||
|
dex:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Disable default admin user
|
||||||
|
admin:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Enable Prometheus metrics for ArgoCD
|
||||||
|
controller:
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
repoServer:
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
variable "proxmox_api_url" {
|
variable "proxmox_api_url" {
|
||||||
type = string
|
type = string
|
||||||
description = "Proxmox API endpoint URL (e.g., https://fantasyland.mk-labs.net:8006)"
|
description = "Proxmox API endpoint URL (e.g., https://fantasyland.local.mk-labs.cloud:8006)"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "proxmox_api_token" {
|
variable "proxmox_api_token" {
|
||||||
|
|||||||
Reference in new issue
Block a user