Add shared nginx-proxy role; deploy Hermes in Docker; rename Paperclip
nginx-proxy (new role): - Shared nginx + certbot under /opt/nginx, replacing the copies that lived inside the Paperclip compose project - Apps publish via tasks_from: add_vhost (cert marker -> issue cert -> write vhost -> nginx -t -> reload); a vhost that fails nginx -t is rolled back - Certs issued one per domain via Cloudflare DNS challenge; domains registered as marker files in cert-domains/; optional LE staging - Stock nginx.conf plus conf.d (directory mounts, not single-file mounts); catch-all server drops unknown Host/SNI - Containers renamed to nginx and certbot hermes_docker (new role): - Official Hermes image, host networking, gateway and dashboard bound to loopback and published through nginx-proxy - Dashboard login via basic-auth provider, secrets from the vault - Main model set from hermes_model (fantasmic endpoint, key from vault); config.yaml overrides are merged from a 0600 staged file that is removed afterwards, so the key never appears in a command line - Data dir owner-only (HERMES_HOME_MODE=0700) paperclip-docker: - Remove nginx, certbot and docker-compose download tasks; deploy with community.docker.docker_compose_v2 (idempotent, remove_orphans) - Stop passing the Cloudflare token and certbot email to the Paperclip container via env_file - Bind Postgres to 127.0.0.1 (host networking exposed it on all interfaces); stop chowning pgdata, which the postgres image owns - Handler recreates the paperclip service so .env changes apply - Domain m-v.wdw-labs.cloud -> cranium-command.wdw-labs.cloud Playbooks: - Add day1_deploy_nginx_proxy.yml and day1_deploy_hermes_docker.yml - deploy_paperclip_stack.yml: run nginx-proxy before paperclip-docker, CLI-based container checks (no Docker SDK dependency) - Remove deploy-hermes.yml Host vars: add nginx_proxy_certbot_email and nginx_proxy_certbot_staging (staging stays on until the switch to real certs).
This commit is contained in:
1 parent
5b48536c32
commit
881e2e24a8
37 files changed
+1683
-755
No files matched your search
+394
-377
@@ -1,378 +1,395 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
64333032613165306433376335663162366661653234633037376532636563613831366365363533
|
63373230343939633131333966383630303363656234366536633839376631323762656431323263
|
||||||
3566386139613261313236313831396138663733613462390a383931656539636132373135343031
|
3938623536303533306430373064316238633132323063380a646236643461373162316336366135
|
||||||
62396234326534363466396365333265353030393064383036373662623730616634333236363437
|
33343834383962313838646461333935303834613162613633613034623563653333373431343562
|
||||||
6338623363663661610a306332393733633837636330653938643230613937353535303033643033
|
3565613135316434360a393239643065376566656332333562393737316432356633303032653131
|
||||||
35616632353337626561623064366263326339386335343833366437386230383163386666303939
|
34346263653264623566333561363339346232373432373733346137376638643261663739623138
|
||||||
63626530383463326261353034613862373861643664356439306233303430366264626231373536
|
38346634306362343761306530636138343530663563636135336431346135653464633564306564
|
||||||
36303862356364626164646432356563353335386162623539646632386638393234643638353536
|
61663732353231313637333161616466613663363037313930646334346162626332643332623461
|
||||||
37656438343634356530626336653532343762666436383461646132353561353437343963323662
|
35333638313261343435323638646565653236636231346230383136356538653865356563643661
|
||||||
64353435643761396336353338653066363566663632383466393362643861336534646632383064
|
34313066306634396133376165323761333835323365663031653864373538353535643965366631
|
||||||
62303430653732303666663264333439633462656431636361386637376237343831353662613538
|
62363831616664323735333236323734663361386365646636373030363639646666343764376638
|
||||||
38396138386631656431363761616234306664326265633432396638333938646164306334376636
|
64623633383833653339636639666162303038313564653831303362306338623831303737343539
|
||||||
66623764613361336338646262636434633038353134373166306433323361306563323834356330
|
39376630383937666531636530383137393264636464383331623563373764313232653839373938
|
||||||
39313030663764313137626535366561663365346536663034346163323932386232393364383939
|
64373865646665646363636339613536616437373234626630636261653038663936643631383733
|
||||||
37613964323462313565663062346131323762323163626338346638346331616638386633636138
|
36666137616631323338316338396364303965366639336562383133343131346162396362393030
|
||||||
36313938663939663734323433643261363130623362663838666564653663643263343139646361
|
36363436373462643665343561336363653764393733303834643935616662633238343136646337
|
||||||
34303031646432323361366165616463646637663664356565636230326531653233333365383339
|
36656233636362653863643335316462636138383665333932663164366362386432333933633664
|
||||||
33653735316132613236383131323032353934626265633338663131393638396335376236353836
|
33346638323831663266636334333530356562383463316163303639363233353031626366346231
|
||||||
37366534336635366661373731313433353561363835613631383033656163373063306139323137
|
32393865336265613562386166646437346630666432393237616565373563633131656562343266
|
||||||
37653766376230393434346137396537613733336134363031383332313930373839313235393663
|
39373265393235646462333663656263633862323036373334323831633762373831333536323836
|
||||||
65326562383966313737626466663461326532636163363331306265633038313539303434316532
|
64653035366163396138653866363765633865653637313336303064656131633531303331363432
|
||||||
65333065636531303261343164303862333031386361626431326363363261313635333430383530
|
31613532336336393232613437666235633964376535636135656662353730353031353662613463
|
||||||
37616632643566373831656532373633343332653431353465333066613063396562393961363962
|
65313163316137353831636633313962313631373733343839303036656338323738396364373565
|
||||||
39636232333532313836613930346334656563656532323562633563643766306139333034343436
|
64353264396465616435333138353531383934363564646238633935623035653830366163373063
|
||||||
66623832373936396236333063666339303934303262326166616339613663346166666531306135
|
36643733383536346665336339396135336333336235393961663565393166613563343235313365
|
||||||
33323163316131373231663935663331356362626364613965613235343139353563363234393537
|
34363763353939613730643137333562353130616565343536396630666265303961363638323165
|
||||||
32396131646234393465623832333436623731363537313235633334326530393338313933613839
|
65356138363864646136336665333261373333333234336237616138346332313138613233616331
|
||||||
31636364343961633564386561643263383031396632633436323736356233643261653637626165
|
31346132376237313863336464313662396164303733636162626130393236393633663630633733
|
||||||
63613764653931626338613033376436363565666632643130303136326464326237643564616631
|
35343866356230363262326436313738386334353565363265633261303031333662666564616261
|
||||||
38313766386362616364346236663138376439336530313132383235393532663666646663636166
|
38623639623431376232376566306362363734316238653630353564623961326331653464653264
|
||||||
32616461663935363432353536303435326338373566666637626231393335323864656331663637
|
38653437333830346461636630363538376137313030326132313665353065396464616135626265
|
||||||
34633835663338323763663835363466386237353833363061623639366537373465363464396239
|
31613433313866333931636535373063353865356435366132383538316265316635326233646439
|
||||||
31306134666266386237343363613431623864333463353330623238613636373062653833323064
|
33346439643262613562363861313364396537366231373464396164666635396435333132393363
|
||||||
36346562643662313533363134363964623565616162333331643433643863656232613935333938
|
62326630303135643264653935316330396664653063303934613335613133333931623262626138
|
||||||
61306635636633333230623265356535656431373032363237343262623438393235653734313835
|
63376438356539363533363134343535386165656364306233353364353364613665643436386163
|
||||||
35626637306463613437396235386338373763323634656665363864613361633365333738663034
|
37396164663630633233343130333036323730633532336136613862376134356534373237343739
|
||||||
37656563326131326166386232323639373365363865353763613364646635303362393564396163
|
34636162633436323465643165396162376364376365616236303361333330396238386461626362
|
||||||
64616161313864383634346437396635613431353838376663656165633438343430306230323964
|
31363931663834363464343866613862663037383135646130653939323566616334313461353730
|
||||||
30373230613839626264643562633033643236623664336234333535626363646131336330323838
|
61373935643962633138383832656634346563666637613434633130663665393637353230336633
|
||||||
32656266313066396130313865616531336461333361323436346166303432323030666366363735
|
37643534386136623731316665663630303634363132663636393862386666633239363937386163
|
||||||
38663530343833396261646230333939666435346331356437366135336437393438353930333338
|
61313361663862343438613835356539373462663230356166633231613064626638653532383537
|
||||||
38343935623466616562363431653663643465613763313230336237316265326165346238323834
|
66663331306639363932323530343164343666323237383631313033323461373432653063373665
|
||||||
62613430313965326664386636323562613262323838386438356439326531313938613031393863
|
65353531653565373165666666396236653038386435653666376661323164383537653936373432
|
||||||
38323936666335646461373936653065363139323431626662333361343039376534363433323465
|
32643234663161316564636333333265643661633634616235346132356138363966653430326634
|
||||||
61663763623266636635346666623063666537396535386135666236653336666633333130383334
|
62343237383936656336343139373135313830613466373837356534373666303233336564323466
|
||||||
36303439613565306663346164646162343764636438336138316261376130383331653266393039
|
37313266316461353062366536323237383165396434663861653032383033623964643965366266
|
||||||
64393764366437643264373361373730663262396664333964393062663433653735623637336433
|
31633238363865623237626131633334313139393437383163313762303264366433656562393461
|
||||||
36623533356361653465383737633238613234346464363565663934613632336638626238376664
|
65333039663437366131366137316230366261613131343866396131626464336165306431373931
|
||||||
36373238336432363061633730356265636639343239653436396163363061613237373333666466
|
36343134393238323165343631373032356332663337366464666530653665313864373463373031
|
||||||
34666435366634316462623662393131393138613961393030363831353832396436613966633731
|
65393164613637653435366164613336386138643463373433636637383433323434623539666138
|
||||||
37383164366661323739313361656139373662663631643835303831363938313362326337353966
|
65333335343033356237373439303636313639623231316638393533333838636364336235643437
|
||||||
37383139333532666631633035346137353432346532383866393938323936383834336231373830
|
61353234633938333263373866636161363166623733356533393665303632643635653131306137
|
||||||
64376261653132336131323562383734653366643137613433666262666337366362333335633465
|
65353531656465373830336163633564353665306566636232323430653363343930636131633735
|
||||||
31323733363332313561666438613930373862643332323931663438653666336433303034636666
|
35343335613531323032346263323561353636613166626533326663633133363465306431323039
|
||||||
36636232386462373666356461616434643861663739303032383638393739633239376664396163
|
30326561363764663435666262636664326438373536663464383232313636373466306661643537
|
||||||
36643130663963616433383235316539336133656562666134616438656532306438663739633333
|
30643232373636666462313730396232653865366235313237373362333135393435636632623638
|
||||||
39646165313566386330376166366534323236373131616332623763623131323466663365646565
|
61353735386430376133373333323464386463303534316333333438656635336636343130393337
|
||||||
30646435643737396665333066313035653361363462666639383538343833336363663264326130
|
33313137626239373731396438636434326133326332353965346138303934326231393066346439
|
||||||
66613563336266316363353131613331386533316665303034393230323937396531373336373430
|
35373535626134353037316362306539396234613736643132623234393962376439643134316163
|
||||||
66306632656163306532653131363061306362646539353837663634323662356536363366373138
|
65373331336364393337393339343433663136396162633263383535663239613132663862306363
|
||||||
38333664663938393232353235343163376634373738313661613861316432666539653938613666
|
63646662396635653862646137613964616662396630656366326563636135613337653661636639
|
||||||
35343161366638666439333633613632623562306164663934333737336362373366393335396563
|
36353561373035386565326666633366323334626663643366346261316365353863383037303836
|
||||||
61643839663738323837613736316661383332633965333464653066303065653764393034396234
|
35356562323966323739396335313736663763313463663465396430633066343930363961376561
|
||||||
31326466393135643765616165333235396466306366343934396439303337633535653033393761
|
61343962353239336434636634323535303534323739326239633965303065343936623433393462
|
||||||
64336435343634633761373733623835393136623864323861396534336634633532636162376263
|
37646135653239373165333435343531343166613864323238343664363137613964396130316439
|
||||||
66363439313032646462336664356636373563353137646334623939363231333639363838623432
|
63363963643330633363313631356535613761646461313632333366626237333732316537653136
|
||||||
61616230636265303765313436623766656635633264316438373834343737363836616466393633
|
30633532623132393764666338306262393562366266346134356134663661333030353333333465
|
||||||
37633232653266616163393531303034353932376464643265353832356466636534396239336138
|
36613333353932396439613030393038626333356230636638666663613634333365376561376439
|
||||||
61333933366662333732366437353164363761643761303032333235306365653464383639316637
|
39303264636335346630303032346233363238323032386362323433373439666635313861396263
|
||||||
66353231336139373435323264663863306135353961613437613736343364383463333031393030
|
62356239663734626665353061323131623032646365316338373235383139633334636466653430
|
||||||
37613336313933343630646562303866363130383831313835386231373462326465623466396137
|
66633662633430376331373233393532666663636636386432396263366238343663613136303263
|
||||||
33346238303433306235393538663532613662383764643264633038653934646236363338356234
|
64393431623535626161666165316235656233616337636663363762343665653236666636323063
|
||||||
34343131383465306131316538326439656338353531326539626366323161313132666337376632
|
36373364353566356438663764383662623331303063363866386134303930656638346463336238
|
||||||
30313262373261633161303737663162646130373164383030656437386363663534666639663663
|
33356630356536373062643533626563376331386338636638616234656535353038323065333930
|
||||||
35356331383831386662386535393236636639646562383534393633363836636631396364336634
|
34613764343832313063323934356462623065303465373739626533336465613836343738396463
|
||||||
35393734343338636161373864643666613530303832326561336138393536646565663230653936
|
32316561343236353365353163323938656364663634613235633165343461393232663335643865
|
||||||
63373866646533346365393261363637343839623933356466366535363034363338353130376565
|
34346130336539663339633266333166643937313761636364346563363263656661353639306639
|
||||||
32623530643032626261663864303362626461336233306164306536383565626139633164383834
|
36336334313732333362326432323832626235623331323734323539333631373535363964343037
|
||||||
62646335653234366563636262643833616165663062316363393135346361623332663164616138
|
34326532353636346566636139333464616466623234346336363530396136636139303830613165
|
||||||
64323831616265356332393535323666376233326333383139353837333762393437316265616366
|
38653539383039363233316437373463623130303038633233373565343632613239653630326434
|
||||||
39313637393236306331313364333864333530323436303539346235333430653762666633643563
|
30393564373462343361376636313037313961383065353864636532643337353162393661626630
|
||||||
63376264326432353639366165303537386535366236663537303932376533326561346533363535
|
37333363313730373132353330323533303635653763616263663964333763666538643864303231
|
||||||
30396632626137303838333263313062313261366634653134626536636538623037646238313135
|
37333662313837353131363962383735366531333730393534396263396130343336643138646234
|
||||||
66343338616337346434663735353138636132663535656534623432303661303130346230663930
|
33333434623966653664316663653337643266303234623837613962356635393865373331666237
|
||||||
66626339646463643562303432313461636632313464663833353663393166323031653364613563
|
64633430386534663162303363333861613338373835643837656664333364373361636338633761
|
||||||
36376439633731646332666539353465616230383433653765653835303566636664646162356464
|
64333034623063393166313336303431383261666564336132326334366639326363393832353865
|
||||||
62363165623639626232376231666263353838386164336464393832353265363336666531653037
|
39633736326238663634316663346239363935396432313336663037396536363561323538666364
|
||||||
34623738363037343461633035373135313630316639636231353835313534626635613566393965
|
63396230333066626463626331653438373365656435333761343565343064326361343537306238
|
||||||
37323762636164383931373163316532633065386535303538616663663536663766646363356262
|
66316663663963643266653132656439376665306261626665393061333962373133323264623330
|
||||||
34303036666631636365396637653561656237303135396136616464356561633738376339333834
|
62343033646463383637636463613633353465633738636465346135643738663535356238656433
|
||||||
36366365353832343633333435303565386566393366363831643461316134356138666131663938
|
34376339613566393764663264373034393065636536653837646466616563656362636231633064
|
||||||
38336139626562303837366235333038353739393863353161383635353339373032626238386435
|
36393736626462303039623932613462636635616233336337363135326564393439663262343363
|
||||||
64363536306266306165333135373732336434383639613865333130343532616261376363323536
|
64326630613563623164366337303430323233326237646634336335393664323231373237373137
|
||||||
38346563623161306165383962316436383631343763623336306435346636626664353434646530
|
39633962366331373531343065323563306662646530653535636438626538393537333033343462
|
||||||
32626361343635343230373834373262306164313263396366626164653235663362613765376536
|
33303332313135653135616464383539333261333730373362666163363335633830376131343161
|
||||||
62613531393866656530613165343565323839393861646337636234633964306130356263626533
|
62353161383664346337303464646463326464316131393266366464613333653266326161663539
|
||||||
61383932333866663436653638643931626262386631386438333764663134656230356630396132
|
32663037366433393864666266653137373331616339396634626262376462373533393761326337
|
||||||
64303332346366643130383664643438386664376138626136313163663961616439313165333866
|
66333666376563353839333432336563326462323461313635343231313964386333656636353266
|
||||||
63336464653531366135636434313034646230616365313063613162633435346235656565636661
|
35323062373131383661346431333665336237313036393837343933333631626236646438346136
|
||||||
34343033346261623966633262653762323037373339303962323735646366326131346334373736
|
63613231393266323739356566663264316536383632353134393561336266306361626532663966
|
||||||
35626564623330653161396232323265643966343531633732383236316666343938343730316361
|
38666666303632346163333066383934303765323839663839326531356363643465343363303165
|
||||||
32376536633331306631333865376662323966326139333130383162386564346162643833316365
|
39393366366237343565653831303335363162613461623063356433353565643233343134376435
|
||||||
35373861383033333030656261393635386535643131643237343363346133306231343531373265
|
35663739383135313130646663383662353561323930323366313962386333343433316236636462
|
||||||
32663833356234373038383634653438333864656330656531383632623439613763663935383436
|
63396563653161323565336433616265623462393761653935663438373235626535343066346331
|
||||||
62363034623263613164306463343161663937613865363134323362313165363335616364393833
|
30636366366434306563663036316436663465313330386231633736303562313239353736393733
|
||||||
62616139666337343632616565636332343064316666306366643931313639376133393735333161
|
35343330636539386132303464383134623063643563313435386563343261316635376135363832
|
||||||
31663538353662656536623038623164656630343265636466616366646334623037393636393161
|
33633737333930306439656563646366623461636366653366663933396163306138393765666337
|
||||||
30383432633433313737646565386337353063623761633931346535393662306534303732666332
|
35396332653563633330363037383734353965623763653731306532626238633736313361386466
|
||||||
65323731396262303361353163636565303531643933353535323239353136313165343763336533
|
38326362613161633465343437326337656363666463326164336137356435623365363034666335
|
||||||
39626631663238326663653530653965366662326537353038643665383361323030333532333161
|
63303938343631313439666538633731323732363530356634383131373766376536306665303132
|
||||||
61393665616661376631616533346361663266363938363964333963383633363466326166393732
|
30386137373137653733633866346439633530616438343638663665323132643335623563393633
|
||||||
35323762303137373566653433396265616431623032643734386136633137643732313930343536
|
65623236363333626361316533643134333864616364376664646264383436623264633462366161
|
||||||
37666237386665363037326236623837626331333434623731366439373963316563643564303864
|
33653131626335326437663537393935303937633932306636333264376137353262313237326538
|
||||||
36626635623162326164633233346363343333626262393531616461356333313733613165616439
|
31366166646536303931373464633365636666306137306666396231613465326235343162383331
|
||||||
35623338336335356331633034623535643762356537313861303930383130316366363061383233
|
33656665326439396231306538373232613535396233323361396164633233616234623339346433
|
||||||
37663136333133656564663962356535613333373932646239633235623239393539353137306138
|
61653034333437376434323034393637396563336530323961663862383334383563343033343863
|
||||||
63333063393066616536656630613434653264313562393464323436663435313733366631623739
|
66376537386330636139343831303235663962336332383365333439646136363632383838343839
|
||||||
63313236366136393365626634386438623062663338396539343438373162666132313034333362
|
33386265383336316231663665366638383138653765323666303832326537376139303835323933
|
||||||
65633363643966333436303636376162316363643933316131336233636562363163643062356237
|
66393638383335336463633235633731326139323266353636636239313335323430363066633430
|
||||||
35373061313139356230653230326366613665393036623634353563313530633036396461373132
|
37663535663837316338316562353835353231333063616635353733633939643535666236343137
|
||||||
39323431353239363037356264366330393066653631656163346262616334653137653232643066
|
31303765616435643765623837653436613733663135393630326264353464356366303866623564
|
||||||
61646132363331643531386463346234343038316565633536393036363564343332623238373037
|
32666533656466643063663165306266643164616562326432663564613733386631396664306263
|
||||||
33386436313437313931343438623766663863343030633465356266373661313737663965656331
|
33353433633461623061376232643963326463663165613065393063666437653236313836383361
|
||||||
65656562626236353866663038373535333262343031626663313439383137666131623833646363
|
39303063323065376239623736616566656532666533363630373961653662386538323130323331
|
||||||
30316333363939633761383933666531306337303764343061313732316436626430313866633632
|
62656433303166333464383065636334316138373362323838383132303237363632376131313036
|
||||||
36626136396362373866613436663639353532393361366230386231363530316566386261626261
|
35643231313338656562303034626563373130313136643463396236633337323137306435386262
|
||||||
30626463313139336532346166303838313333393666656432306632316636323736653639366433
|
31326362356133313263646230636132306536666562306364366337666163303334653065616134
|
||||||
34646432666266363366366561646462616365306335666437623639306539376663373138313061
|
32303437663730643836356461333436643239333262363733353338353831323566323438303162
|
||||||
63306532653666336463313731633966366630613665353533383932663730643033653465336265
|
34366138383730393433333662626564313538643838633630393465396565313339613864396536
|
||||||
62636139653631393335323238353537616664333962633632613233393634373263626333656663
|
61366534366530376361393930343236636630386261343839663031386532356533383433383738
|
||||||
30643935663432633238646335336436313738323464656535643237313866386335643932653935
|
37313133393130373664623737323631616138636665653431383435376338323439613737326535
|
||||||
32653434326365623635356134656131303663396462633962613337653837633066323865326632
|
34323462303639653162313431313737623239393037303431373039653136386539653138376464
|
||||||
30626163313731326166313035396238343239653233646461353034653562343838663433646131
|
33613461643132666230653932363739613930303333323739343033623936636536656139316435
|
||||||
34653837646431623661663364633732303934383537636133316230356136656631356166353566
|
33623964613833383030333937613037333331643230653633633135303561393337663435393035
|
||||||
35303233386535366231306535323662303761666536646664396138373265643533373161616237
|
37373135663461623663623634623930663937366162626134383862336336636633336138656334
|
||||||
39363263363065313832393237333831353836666335366365353939326363316530343665636264
|
31323565396531323633393534326538616338313030303230363765313637656232626665313532
|
||||||
35373666356262306332323361333939636438323932373566373035333337393036386532633463
|
33333662363062313530636661646536383531643335366561366537303263613534336537663764
|
||||||
34326135323165613263626632303664363333333262333466663030383364656666623466343038
|
37376238646464383466346130323232373736616636333939356466353830623861653664363235
|
||||||
35306539636536353964633365623362323662613936386463643033666234613562386366386533
|
33366561623531343462303462636537613939646465323965366638653936643363353164613030
|
||||||
65323238396532383965353663333565303134346432306135323331636636393036613031383362
|
38646164613330313035396466653931336237326464646532636133326234646133303161386632
|
||||||
61616665616264326135656338656334353064346665346464316637343731303839363165313133
|
37656438666663383430363834323966383633333438336138626166353339323466386566343461
|
||||||
63363366376234646631306665636237386133643863306330333238353366646665306262336462
|
62346566326638323730343839363730333736333465613933303536626132366561313231383434
|
||||||
61663162383165306236393238643161383963336164333134376333653462356263316366386432
|
31643732356530646634386230336566663461623462643036316433343334623035353036383234
|
||||||
30613163616336303234663835613938623162303764306335636264623230623863306637393537
|
62636133666431303163646133633966383030666664363630343730333634663463663034363264
|
||||||
34646166353538386330616235306131646531323930363664333239323533363263653562656533
|
63316265653034363239353434666263656463323136663132663263373130303536636266383533
|
||||||
65643162613539386238363564663638636132303638303436323137643164346531356632626634
|
66353234306430306161656164313131376163646332646435336664613764356333316666323832
|
||||||
30363635346435393531323165316364383261333335623262336530623231363238643964393232
|
39373932396530613430373130646464346463376330333566623935373433393265386663376237
|
||||||
30363836326461323730363534376437636161353061393030636334313430383134326563616539
|
37613935653933643435363932623139343738366236346434666638323437383562626133353231
|
||||||
30363362316237653933373339393035636634373434636535383334393263623430643535663862
|
66373166363138353239633134356338333032613338326633356139353232326232653963353864
|
||||||
34306266323630646464663266383861663533306563366539313361623532306437303865333863
|
33346236623333626635316239346236313765373863343830363434333738633532343730303462
|
||||||
39613631306437373132313930363434646339303763356366663132623565303563363239623662
|
63613930376566316463383936333933396566626436383062323834636362396361383738633336
|
||||||
38356165376362383735306439366630653962366465396265646130303735313836626332326161
|
63333564623363623738393835316264313630383631333638333066613963343761383338306566
|
||||||
39303739383837636366613963326632633764393564623162343734663131633165636530633836
|
32623336383933646265653538336566323430393735373063636438396539643365366134623864
|
||||||
39383430376666363231643538343638336336373833303534363163353630393032616666636530
|
66616263616663633039303861316235633934313334613333336163386364306632626232613834
|
||||||
30373361373761346436316132323236643236396137653030656431333863373438386566363639
|
65376265323437346635313330396439643536373564333231313634323837613066373964316334
|
||||||
35636230666138633335336266626530373762656439666133363262323932323665623434643732
|
64643634363138613465363037386534346164323134333138666261623234386336653664313562
|
||||||
64343138633539636434373363356635396238333962353433653632316230313537393336396636
|
34303331663939623833646662343063303865346266323430353232373339356336383338363933
|
||||||
35616239353838303165306232303066656131643532656433313766333066393162613635633734
|
32363739323330333937346334643031306166363132326363626261306436343065303339623963
|
||||||
30336263363439373761313961366234323432323131303539333237313764313631646462366230
|
33626636643865636461383261366533646139333138396232376339623066313635333963343532
|
||||||
31313763386262653434323734306463626364643966353064643534633031623962393533326436
|
37306463643263333632396334373739323665316338383763643830653965396539366432373164
|
||||||
66616631333833363339623664626365313465666234363739636264616538336263366237373832
|
37373335313137383664396563323831666535313635313737613365626639623864376163383436
|
||||||
34613431326433336330623637326335346330613962336263313032366234353938303238663236
|
36643061386661383939666630393133636134316535663934333766313837653332303531623437
|
||||||
36663462383566363933666532653735666566353534353330356161383166323830356336366363
|
39363161313936393361343265393132393636376538636338646230376564393330356534663263
|
||||||
63356330383538396133393162623736386135633537643037643238393166323431313239636137
|
30646334343961366561613661623036303535313562303666376635643431323237313132373931
|
||||||
33306563383838633938363130633539363731653436643131646137393661663836343830343662
|
32316333333237636330376166326434343438316362333464343232313232343130376533353235
|
||||||
64633365633862396362656565623439383139626433306331316665666534383063633932376337
|
64363761643136653563353232653239646238366330313939613436363465353061303033663464
|
||||||
33313839616166663335346331613737666135613531363464376231353835613831613233373932
|
35383039626166363937633232613836616164663832663938613237393666383336366231313438
|
||||||
35373731323334623836366435616231346630313862393861613162376161333138643931353164
|
34613930663536336263646637666361366235633066613236383463383464616432616563383831
|
||||||
35383334623966356632373665663234363830333162396431353364653431383533633664343031
|
39396265303364353363316132313264393735663034616561363062626530383463393761363931
|
||||||
66646664666565333763333266313164613362333532616130636561326164366634383137653531
|
38656261616635643836313264396130336538643865643636316462336663643066393835663339
|
||||||
38636434326561383336386134656366393163623930666430326164393430366265376639356562
|
32313263616138666461636431316337386530356139666139333236613964323734386566636666
|
||||||
36646338313137383436336665383238393034646364656462333430376538366265613736663439
|
64616661303562633063343464363766363132636135633463633235356161626330386364646336
|
||||||
39636631633339643130656433303637383161323331353939323662356337626231323937613636
|
30643165356237353130623534616239336632353237353263393837656664646537633766646531
|
||||||
66303935666235336466653131316563356536333336326132626438316338376137376337373963
|
36633433626336386334323039303039623062306637633539656339333233303262633033313639
|
||||||
35333665383661333161333563613131343863323337366633383334336332383066666238396565
|
62623062656431613238383737353239326633386237653438613166386333653735326661333838
|
||||||
38353332343761376165363363363763313431343031636561396534656262613761363665616163
|
65666239643435313237323032356261376563333265386133383237353034613537623131363038
|
||||||
36653766343539363031303238313030663637666161303036353466306536303032306639663562
|
36363066346135346364336234303539336638333962656165306264343438646166313962643935
|
||||||
36393331316434356666353762316234626364376333333966333933306636393132326464386465
|
39623764643034663965383865353939306136663532353932333664396165643830333030373730
|
||||||
32393764393064656262623935326438373039643537313562646437346232353265333733653132
|
30613462346536656661656534383864633664303735393035623964366632306233343165663038
|
||||||
37636138333237386265616234333361613835393433363631623534633533363634656165383931
|
64386364643630653737666666376565643639616639393462373233333765343961323631336436
|
||||||
61666533313562663831353639663734626630323361656237383662666637353861613862306164
|
32333634613136663734663763353162356337326537653766633664353163363634396131373166
|
||||||
65376565376336386163316666656564643333383731633562633834623464323731363562653033
|
66343933343862323032643537333464303836623939316130623961323037623839663639393966
|
||||||
33643861636533613634343938363131656636656265633365633731653538326638343165313865
|
32623439333836383234313135306135363434656366653964336135383765383163643962663465
|
||||||
38353738653830383737353433333830636339386339623435343330656530653766643732353131
|
36653130626362636561646661376139356565356563373435626537663930643837376530326662
|
||||||
32303539356533373731383564616163643637323834333133363431666538393338393235636333
|
61356336656235396333663362326563643039346231633261323930653162323938343562626235
|
||||||
36303062303138373636396335616335623862366364336365616633373966363563616439303738
|
36663662366534383932396630326338626333666437656264343764316339306132353265613063
|
||||||
31636231343463623162613734363332323330343466633561333032663435643764616164366366
|
62393339653630623965636331623331633733653530653164643863363637616131343036633432
|
||||||
36323336613136386662346164326331323034386462383862616166336361623937333638316264
|
33633161396331333264386431663565636334376533363735666466623830383164356366666136
|
||||||
65643765353734383934343435636331346561636135346434623730386566323231343262393065
|
63353061623564386630616134613866623532663237333432613962366537643834306364316632
|
||||||
30653364346633626165323434333532646664663333376362336165663431653434383865323938
|
34613465343532653261303664326362663938613638656532613638303566333635363263653935
|
||||||
30643932656633633631303136323135323562306139643838393464333563363934323233343136
|
62363363393539326638333965643130383139313935376436363831653837303831326634656639
|
||||||
33613163323135313536376132653464333730386462623131666662316139326463666433383637
|
31343166613030616166393638643333343264626466303363313938323966623832333330336436
|
||||||
66313133643464636130616333363331663165376438333636366364346539346334623534303362
|
35633538636437313761383530613862343033613334643465393164386431303138323239313436
|
||||||
30616165373836393038373430336539373732323631663666616232396666653364313138313836
|
38653161643261326438383132306663333235336362353439306566306563643664323335376265
|
||||||
33306365366564313165646230326135633039323063333231316232316132623939386563396439
|
32653365383863323165343366666639393538363434353061623939633262316131643465316462
|
||||||
30636661306531373139373061633031343165333339363035316338376165643364313862306132
|
30383239613439343333636663363366616263393866636466343835333335633030633062323039
|
||||||
32356530343166326631306234356336623464303233613064393030646362643061363338393532
|
62656564613065303336323064616230356564313235393030316239336162633831363336623261
|
||||||
34346233393739653732663336396230633066363663653064633132323235353030333539623235
|
31633163363937346534316136306532636464353463326166383239383736363962363465643162
|
||||||
38623835303931616231316639396431633634656161326431643766306566396138653364346665
|
33383539396364303062326666636437373761646462663338666436656362346231666136653232
|
||||||
64333666363739643233326566663764383362623263613538613134303461353563306435393064
|
39353538313663373066623935623935616461646266616231343338623462616663373837616162
|
||||||
65323836373434313864373331656132363839393232303963346562623630646466316635633164
|
36663436363532346565323363383263376231356138646563333433323561343533333566303431
|
||||||
64653830393361386631393336653066653032366263663337613039623935633938373232323137
|
36633432633039396363666634333862613835303838323733656332653031346637316233313963
|
||||||
38663535653963376633623133343933333739363233373734323637376433633234663439313665
|
65326662313564643564616535383264323137333136646662396462373134316238323537323538
|
||||||
36643038666536303539626436636535623762623230336130356231646665393965393930643037
|
39613734633631346131653336333562333831333030333639656231303261643034353430613933
|
||||||
61636335643531383837653336323631333961353937623334633833653431353566336366343632
|
34636164376165386431636334313831323733313266323666656365333062353063333839363937
|
||||||
36346439366535613532363630656462356435663032323931343930313235613137376465333863
|
65386331643732653430313138343639366437613466316438383762376164383031636232386162
|
||||||
30336466366135613363633136383461363336336231656531373931313134633438633439656231
|
39353064353466373664653437626461316563613161383461626331393363653332643062626331
|
||||||
64356333323737633965633633613939333064383765636465626265326338366532363562376237
|
31656334623061346337303963323630613065326364343834316361373364623566316630643036
|
||||||
64663937633433326664313331356537353365663761373032363161323334383766356339343365
|
30323334636137653439656235396266396434663737396466366363646664393162303563626632
|
||||||
37643662363266616535636365343261363537313765633031613263643561393663303339316433
|
35666437353665343065396536353731353532323664363430303934343933623566363031366435
|
||||||
66383863323633643864383134386630333763633138353562363863653666366435316364643564
|
65326434656465366364383932353131353938326535366662633234653038363164323563633833
|
||||||
35636230336437326561396536663235643130343063666165373530323732663761656131326239
|
66326530343339646161353766376565336366363364363231613031663966396632373539383130
|
||||||
32633435336233333231633133633165333961346437343833633863373839316461663661373565
|
63616333333736333563343232656162373861666433393462623232393535633235653237666233
|
||||||
39313033383562363063313464336631356630643461633065366439376636653339303362336363
|
38373831303862323735623461393365303063353235326166613163323162613164613162373761
|
||||||
66353732623761343666666230646437636230333733303961666663366163313665353132333462
|
31663031653835336231303432633735653538303062303030323964616439393538356138636539
|
||||||
38383661363431323330336564333166396331643664383937666232343962343635366432643031
|
61656136613835373965383837616366643931633933633733626136346536646630393537626633
|
||||||
65313666383962656166636433323233353634313533643232333037303566646163393865656130
|
64616563363034616532303936343136616662363965353739666233313936613039613235333836
|
||||||
33316630343535386663356665616132356137326666343134353934303336643635353463623635
|
63623031323430383039353939333934346638366239346364383366383966393364393165316633
|
||||||
31623936353433626237643136313131656666623236643362636131373636346164366562393565
|
32343566303766633464303164323339313130373261306564393737623465393631363231343138
|
||||||
34323065626565303035643463386262373164383431383865663536633362663137663437336565
|
30386132636232623765666462653638383330373438323834326563666330346262623366636463
|
||||||
35616562353363653230633534393235336363363038356139366438316664353535333761363930
|
39626633613436316164353861303333326564393864393662383263343662616633386565396137
|
||||||
66643266623862366461303735336465393039643866383764633937353835336137613865386466
|
64393337333634646434326264373335383762356630383633313539323737643736363262346464
|
||||||
63613833393636346664613737323436333666343535666436323835386561373336636135346663
|
35313230323132316464616332646333306239613762346439346234653437376164316335656561
|
||||||
35376335306266356564656234623534636561346235663137376532633439336231363563383234
|
38396264303936626338313232323636336430643761363432626266626233633438303535656136
|
||||||
36386333353133623538636464613033386238363532643837626434346235353630396636343434
|
62323232393438343661396536396261643466346339613866353037303732316538656633626130
|
||||||
61643764623133333861373636646531613633336334636261613562363766343138366563646564
|
35323164653337316636613163316130373137653134393633613936623530306430663435383530
|
||||||
37323933653639343962393430623265613033396436383432353436353338383434373137373536
|
65653461323833656466623938356335386235643236353662653864643564303262363937623363
|
||||||
66666162373166333937643735336562373637346130353262376134393233323337383765623632
|
30373430343933616238323434373039356439316531623562653862303735383233346134363338
|
||||||
64313339323866333239323763303931393766366537323833653162656139353230376361333539
|
32653433623162366361303565613537336462646338616337656437616363333936633433356434
|
||||||
37636530396136383030383235383664393566313363613461376262383865396466376264393561
|
62336664343063303035313233613765336539666437623635396366306434363231333633626561
|
||||||
34316336323264633664353132363061303538313934383439346666653438393532376563383861
|
32623731663465653237326232346263653163616132623035313632306630663239626339643530
|
||||||
62663932393431386237373237623063336237653665326664386433303538363735336565633938
|
36366438326261353364353433323630303631306336363564623237386130323663616334633063
|
||||||
34613535313364666539363133376235383765643339316131323936323064363831353236643038
|
33643665306138393434396135656534306639373133323866353261323262343532356439376435
|
||||||
36383431616333656639666365626665303631393133356563333335626535323534346539393431
|
38316239353865366239656432393830353563396565663838323432363333323533363232643666
|
||||||
30363537353362343064313364636438383939376533613765333930653264353364356234626234
|
35376634313338616436303131383838396166303864333165316262373538313731323563316536
|
||||||
32636330623737653832346362386537373032306238363466633232366364326139376234616636
|
65626230656339653538343838313631336131393533626265373731326536613861336335316665
|
||||||
34396465383338366536363337313635613531343431623038336531343634616635313664333230
|
37373238353837633838633136393164663937626262613035646263383639383365313865613365
|
||||||
61383561353664303931306631333164613637393632343337393562663864363962363966633332
|
37313537393332623535303038666432383432613363633462316466363436353231333830653137
|
||||||
62363331653835353731663835323432333434373464663063306239323765323265316231663636
|
66346633666132313136323965613865633031346134666335356237353261363937613637656333
|
||||||
38656137666664323163363639386366323736623863333131306539636436643361613137323738
|
65653537303965383133623161343332663962373462326232346334313064373530646331643838
|
||||||
66636664326661376634303136366139373235666661306130383330613662313133393632663235
|
63333538636561336335323665366439313936623136626465303630373962316130363461633565
|
||||||
64653363396431616338653938343531613530613364643861343465646132636630373366323034
|
30653430633733376336323537633036383537336665353735376466373762393761336535653836
|
||||||
31323063393437393934616634646465306438313530656465323532356537316131626131313863
|
33323935613065336535323034343535323531633537386337326665343935323136636464386636
|
||||||
32653536303066353764616235346434663063366234646266323164623962623861313865656133
|
37316561626664386634346435613331613363383461626333653738323130633633366462333465
|
||||||
33366633636433393732346465616438303730333939333162356335383732626231353062613731
|
36383237616363306631623066616534333866383138396435373839366565653738663039333266
|
||||||
66396436663639333262386235633936316431393333623861653836386164636438346565366531
|
66626466663834363766313165396132396334666266303665616666316236306433376536313765
|
||||||
35393735353235623736613936383637663931333930373036396337383335633165613464336432
|
65656530656539383833646430393762663066396430663838303566326465633231653039613134
|
||||||
62356532646439666361306534386239623634636532343434316336306663303339393537373834
|
62613030663037363062393161363034633831346631656262623330333032613030303138636161
|
||||||
63666436613034343061303166326332643061346633363136656535613931623936313339393263
|
62366366613038303766373530333039376266303137366365306535393238323565623633633962
|
||||||
30316133316431393661643336336463643932653034313637633762646338383965323032316434
|
61653634366633393630373834326331386230313136393966303964373964623561336431623433
|
||||||
32366236373634316639303339363032663135383065376239613762336238313634386535666366
|
30626333363338643039626661653437336536636331396132383834613264343965666134653733
|
||||||
38303063353062396663396130636131656333326630623339353836613432393663613261316633
|
30303030383832336463616164633866376332653735306436343136376131653231313733363065
|
||||||
36376137633036653538323337663932333831323836356336376135326635346166336534373766
|
61396639323539363763386236373433393032326266313534663566363064643765306134356562
|
||||||
36646630346363326237323135363832306566303462306365613863393735323862306537346436
|
35346237626439326464333330663166666466383037303135663336613139653330663039353538
|
||||||
35623730323465356630303536373061666337326565356630646665613430636436333663333564
|
63316536656162656238363235626637313835643062626163373839333462353239633634666161
|
||||||
65646435663238396436373161366631376137393533623131666561353037663135383165666231
|
35363430373464336635656361316266333132396564333331306261323531306561356537653837
|
||||||
33663563363639346336343666386639323833326333313137663562623236643932633030306564
|
33653365643864626639393864393665363739333239326639666132613334643364663431353961
|
||||||
62643836616533303665376566363034363334646261363565363139353031366362386433323030
|
34393363353234616166323536386365316462386339333334663036313938633364626236613633
|
||||||
61663635653961366665666666393564366531633863393632393734383137376463363933393862
|
34666232613061316661663132343032623435663435366539663532653262313564613938626565
|
||||||
64316335616162623436613365393135363664313266623237323830303632373139653763353539
|
63656430343165666435336461323966393565373931373837626464313261303263396161616565
|
||||||
34313936366666303733666566363565363933316434333231653732663335363961386634373263
|
65393366303739623061616633353664326362376666346166613462626536336363323164613835
|
||||||
31623862326462363364326334353038366538626237633138303362343532616232613837663733
|
33326133666166333036326162366233653664383961363239343865313231326334383837393039
|
||||||
65393339643831323734646439356566393033373332386333633766373539616436623464373963
|
32633634383430333963633661613563326566306433653233313332393235666536633734323762
|
||||||
64646535613436633733326335323664386262626437633862643664376131663965376237303133
|
33326135363231303166326632323435363233336132353666336235666136363531353865646566
|
||||||
38383730373738613034643637633934636536343936373932656365613339616534373263333965
|
30303330656665396634303330313463306566653863663335626337343562373430326633653961
|
||||||
37633466393734323431356239373038306131626463326636623063323930326339346239316235
|
30646366633330333431356566616336323732326435356336653164323238373863396465626235
|
||||||
66643262303936306430306465366437346333386364663266346433616136343863383236343764
|
65386338636132383632373764633139396265376566323037353538313835366232356366393233
|
||||||
65383331393739333131383763323762303766356433313334346137643963396466343466373637
|
32313236633831326136346232623630373039393862613435316563343966353663666364383733
|
||||||
66336165356564366133623664373165666235306163666666633562623437343865306665313134
|
65336639656438303835393135343431316130326438373237616666366163613863306264376438
|
||||||
33323133643066663134356337333763346162626437363763366331373865666539656462343139
|
37323339333731666265323138376664613065353533363638386139346131613065353663306334
|
||||||
33333066646363663962303936353734663930333261373331353766633335633033653038333935
|
66353932616139376330366364323564396664373539396366653239383538343563353832633039
|
||||||
31303631343663363837383962326137323432656230326439336662616233643766313366613366
|
31633330343235343138373365363161303966316639623961306433383037343735623932376430
|
||||||
30663737363466333362303338653238313739316433343336366435333366393064616462363063
|
64393932326431323938306536663239303130366265323965653164326532326265313761323337
|
||||||
62623466326133343130313836396233313236343461623634623936393562373261616237343966
|
35626232663834393066646263376563333635643337356639373036306538383536626363396533
|
||||||
64623233653835613762363334363736303332656363326263323562626430656564616563346639
|
63626361333861643339626634356430323537343865643535643238336366393835623130383337
|
||||||
63616562653964656262623134626233316234323065316464626565643561623863646464633663
|
63353139306338353961353732343461323964623532326161636162303038623661633436616134
|
||||||
32363763316130393736636662353663316466663965353233636532653364633736633033356264
|
62333966613432356430613961343837326661623130356232623633363865303039653832643638
|
||||||
63333264653162313438313531626335343938353361336563656639376365333932303563333930
|
31643035613561316663383934343262653661636333323963306330373133303431373433353934
|
||||||
31316237343836386137363635373231326563626438663764383562666336626233643934336438
|
34376333373166626561616532356266316666646536323963326662363335653131626661666436
|
||||||
36326365313033653432633430396265313834336633366161376537363235633732623036306130
|
66636562656431663537623339616137383161373862323362323363373634353861353035303935
|
||||||
38303331343230306437386466643566326561616562623038363034633139313634623564313530
|
33373963346164363038336164343733336265623638396239373263326435363662343232366664
|
||||||
64323033643965316439613938666166326638353166303163313163373635613435333036306536
|
62383039646463646263373463343531333938393730383531653163653930313236373337333638
|
||||||
36363263326666323261633831626562343537646139303031336261666539396161383235623230
|
33393632663439643364353734623530386239663430383263646266323833633632323238623762
|
||||||
31656263313130653637386332396432303534643036333761653965643632643164373637316438
|
63383464323230333039323838366237376334663163653463313033353664316565313263323861
|
||||||
39353565356166653037373237643731653735333465313831393435616139663262616135383631
|
39356430623364376530356233623762643163356234353764343461356238353337333634353939
|
||||||
37656234313838353832343035303732313663363034316536656263626265373632646438373732
|
66356137633464326432363433383335313938306530326635356333353732376661616465373661
|
||||||
36666462626537663963623361313166376365643963383339353165616337313830343136386561
|
30666462643339656664356431336661643164393933646531613266656134393163323534343166
|
||||||
62666637333765373663646534383466663830313366316133633364376537336264393062323830
|
33373736653333336132663038663561313563363562316363626535383834356564383535343962
|
||||||
65396262303937366130333232323432626561353761666662393737353765663533646633343431
|
63303734373735393162326336343338336238613038626231653936383264626238613535646164
|
||||||
33353030323365663936343433353866306235316637666262313663376430366534613364623238
|
65353366383134363239663738363433636238633038363735633638363837613635616337656534
|
||||||
66383262313731396330623564343132393962356664376565626466613038653932313534306430
|
38623438396430336563643266643434336130663337383835383939343461373962633731623339
|
||||||
62396564623664356633613030343439316235646665653034383464363736646433643463656230
|
65663636326534653533613436373635373864616165393362663033323361343735633863353837
|
||||||
34393231386464343463333238663436376435343766343162303337313763623836663533393031
|
66343037303437346461386531346363393965633165353062623866626663383838613861653332
|
||||||
30333735303531383231633839383037613834363265373436613439303266656636316139633165
|
62343766663237626266643166376239343334363536393166366138616363663766663136346235
|
||||||
32393036343861353064663163333164336539383165313431386538636439333166333266383763
|
34623362373364316230386465336264353666643934313165393632313337393164393165633661
|
||||||
38633539333766326136383962633536353432656663396638363439313137653037643135323438
|
30333638633432306164646163633561373564653436656330343439653062646630376536653362
|
||||||
37323739666366353165346235383863633765346330303263656365353962396565323966653764
|
39356362386639353865383730396261383364613964383665363766626639666630396237623733
|
||||||
39366461383563383762376637383130333738343564326231636536353331653365376463333335
|
33323939376633613036353066333534396163633737363864623634646366323032393635343434
|
||||||
35336538396335313135376138613736313264663362393333383832373766326661343765643831
|
38613263366331656363373239633764353230306338316635373965373837386136303237633334
|
||||||
38373438623530313962343065656339316131656363643564353238356639666566313264366463
|
33386237366166313766626133313466323635336437313363393061643533393866353336613961
|
||||||
39373237363539313530666461653966303633383834316463353136613265346436323133623563
|
31343561633562656431313131326464613439636666313537336533333935323338623538336230
|
||||||
39336665613764346335623133363935323434303138643033643737396535653261643261663330
|
39396534663065643232363339396234346335623531613437666136323531636639623739303434
|
||||||
31353835353232356431633930653538316331623231363364313732343061333332646439353731
|
34653063623139373065363438653461333239376164363462343737366330363934336665306338
|
||||||
34316133353564643534663464616239313836613133353938663164656661343763613433343366
|
38613863326362646631636536633561623034613939306434306431646261343563346664333132
|
||||||
63383265303533383530396138363230646539643833633439366165343630636138643439393165
|
62623431303537313164633333373030623630313733313138366538326361346534373634363663
|
||||||
66383633666433636130616364643731626461316331303563343865336566353134643466303638
|
65643536616637623734373863663466363066323465633862333538613935666337376530386339
|
||||||
31666130656666663434306136323236613162386137353233336363316566323364623135653130
|
32356531326661656461623434366666386134363336363766653462643031626238653063343864
|
||||||
34336431626139303634346439623362616464343963323335343030623835333964613863623766
|
30393232303534343365656161356162383735653532636538616265333062373036326164306634
|
||||||
65396565343438366632346162663463353230306535643461373333613833613430343937626563
|
30333035663434386462326230303231366430623261353439383330373566363238303666653637
|
||||||
65343631353035353731353766633630343936336161343066393862663536613630653261616435
|
61666562346631393762383565333030343263613139353565613238393337613338666364656538
|
||||||
61643566306633653433623538313264333565303431343938386136616230356238363164613833
|
66336131356333303835366539386239376332393636363339323763646138333737616433663230
|
||||||
32653738313465653435363138663061303965643262373135616663316166353961313135376363
|
34386439356236656330656234613963336536633766316232626632626338656363383638313964
|
||||||
63353037643266636330386230636530636633356231313636303233633665363961303163646234
|
33633465383833366230623563653332616233666335396530313764336662313436363764636434
|
||||||
32323664316465386339653730333332333631613631323634386139386230393465373063343036
|
33326566396538333961363836383566366231613839386338363037313934306566613235303930
|
||||||
37393566636135353034366138643434613463653936386162316137343230353765316433626362
|
65656130343733343963343930616136306632656539663665613334613931383031643661646334
|
||||||
64363362373465393433336361663934663965383031346162656234323630313338636331326435
|
63326530623639653732326463313966363231393733323936616565323462373034336334303166
|
||||||
32656266393035616130636438393535626263653638643432633665323565616661363339343531
|
34313965366231643261643161356361616431623931366133386332633735356266316530326537
|
||||||
62643365633532633262343966316530353966633263393432353061383134386432356665366236
|
36323539346330363866353764663636386435323063386535316139366634313331613366393237
|
||||||
36616565373166303562396464356136353938363535663638663965376662336362653263326565
|
33653965663737363262396135326632616536373331396431306664373962633862633666616237
|
||||||
30303538356530333237323935363930626664386264373937646336376532656532383234643639
|
35353439393863313132323533623431333564343565613633306461373738313066373836643461
|
||||||
39666438313064366231336235373666616231386366623638666266386238343065363034303663
|
38343731653433643937636337636436353237663765313735646431303834353662326431303163
|
||||||
32633830663662643136636536343536363566666333646331656530636635626534363964353263
|
37613762396639353066633636613836396135376563643064623039346434393138383166646264
|
||||||
39353239383539363131313331626637626661306563643930356230353137653961363566663031
|
62656461633366633431363831656366373934333838336463616264646636653630396334643132
|
||||||
39643431626164613964626231346638636132323462343933656236333964663962656534313236
|
63306238373230613734356536346439353738303132366265346561326534303239303039343832
|
||||||
61636238386335633230353565613062363834633634336331393239373932633738383131323066
|
63636361653837383766353136313465313164653534663862353966333339633864616239613937
|
||||||
61653038663631383539616438386530336462613063363137643533303837346264656630353933
|
61363062653833316630303536313765323232313633356363376436643230623034323264313065
|
||||||
62313336333137343039353166393966323838333665653834636138346630353434643138373366
|
66643061336536353965633038393231323965376637653131383264323463393238636365623061
|
||||||
30386339386439383339623934353462336166323961666432373136373863386165366364656334
|
33316432336134633933343566373561333339333764316633393762653238393630343462373433
|
||||||
33666363633763643538383030383333616465303465333630383739376635613538376564303136
|
31643863323430626432383536326437613639653565373665326133666666326237343039363163
|
||||||
35393564353739626631363932306131386535313063303638656638353338666335646131393131
|
33616162383764613039326438393962353563343166373330343862633366356232363765666336
|
||||||
63643139643935356661613735373461633239356463666331343531643264386664663531336561
|
36376634393034356130326634373961363730303531666666353532386433373932656332333136
|
||||||
61633132323637396434316535303638663336313162353738363931623461376136373438376666
|
37663364386262363565346566663161396661616664353035386537323230656235633866613432
|
||||||
63646661653063616664616635383838333038356438313066383866343966343635303565633836
|
61633161666364623033626438396534313931353432663435353036376433356665646266306138
|
||||||
36353538653438363931386261633535613066623462303262646136343461316465373961393433
|
36306262303934653231303631363364336632353739326338616638616335303332396664313862
|
||||||
33653636383062386366353837336262393465333638663633316331656632653539626363393030
|
34613363626438313337316632313965336331383362643266626336353264346164343036616265
|
||||||
61346663326233353864383063626461383966666331663438333739393065633938656236616161
|
65373462363732353534623239306230346532333366636530356531656232366239643335646164
|
||||||
62636439663363353736343431333166393161633466313230393261376133373838336333616338
|
30353266663833646464646165316664616564313734323131386236353535336339336635653462
|
||||||
32373039313135343864643661353263316335336537323436303437623637336336656664636236
|
38303430366538356163373331373633336634343238393638623434316663666336316163383365
|
||||||
63626437343864313338313336316332613464633564653837333363373632616564663161663237
|
65646462653765633961303438626665306139646462316630663635336630376363343337393034
|
||||||
31313466343163353936343631316637356339303563363261646366323333333237356139613932
|
35363836386365633161383463323062613663373431336139656361616333366534666666653735
|
||||||
64346533383138633336363134663032333565363064353864376630366235666433363532356137
|
38653931343961313565626233633433636632333835643936333739626365633938636463316637
|
||||||
63383965613636306534653836653164616638626164376166643136373566363531346437363364
|
34343965393235646564393762353430396133656136646565323632313035623736353730663761
|
||||||
33363630353535626339663234303764373565616466636263343933376532316432393030636462
|
61653539613837323862383136333734646134383865666465633938376661616137346564653262
|
||||||
36396437666331376661376261626232366337366665326537393166343630356462363766616264
|
34346365356663626634333032383763663434656464373764353061326265646330646238333137
|
||||||
35313661633162363137666434643466353961633536623762383764656336666436646162343662
|
64323331623838346432633964393433383239633731366365643234643832376161376430653837
|
||||||
37663032613935363261653165356632353730626232373934343061393633653564613133383838
|
31346138366466393839346231353937626431323065383837343062636664626266333734373430
|
||||||
66623461393163623430303139613136336161636438366337373263306564653662656639616365
|
37623461363062343563313930316131666165633463616438623263383764653137316461643738
|
||||||
38356664323437663264363431653562643136316362653235376431663836303833643737353361
|
64333431663439383163343438386637653537303835393930326230363166306136636663393161
|
||||||
33643662303336636231616233393166393638353133306263333561336261303336343639353330
|
32353136393663363639346338323933383032383031346237336437613061626638373330363639
|
||||||
35636132383734356235343237316230373766393563656663343131663561373630636438633634
|
31636361353233653034613366366339636564353639313038643832613261383133393134303136
|
||||||
36373835363437386462373466386531663231363738616564313839333864326463373036343364
|
66326638623238303761396262663862373161323665396266383738343330366431343562313530
|
||||||
34633632633832353363363162333536616332646630633433653663313332313531393363356132
|
31333730393634623466356532386138663939623230323531373566356431613031363236396234
|
||||||
62663637333765633665616466643130363234626563636563643866343033316330623862303039
|
65306164336537643636626461333234303266306433646566363361623234313735653930343932
|
||||||
39326561626331383935393662393436336239633330386261616462646631633964353862643361
|
63346339633864656538323631363231343930323631353231646534646339323130323237373835
|
||||||
63613831323831336461313936356233666532333535393963353633366431343330626233616636
|
30313834613334303539326338623735396537316162363861626566326662636638373931343134
|
||||||
64396464353563623037653163616535346437313433653332313862303363353638366539613531
|
61643530363465653934366639393364636161363633336636383136333363303936333333303261
|
||||||
66616366386265376231376436366566333137646633323730303737653834616662666538663037
|
34323231396365376232653733386464333364623634353361323635373935366266323130666633
|
||||||
64373139376332356436653733386631366165316563393536353564626633646365366562626633
|
36313530356236636534623661343335306330303961336138306335613836653938303966616231
|
||||||
36373835623937386366356136613061306630626661653764653665313462373632666236323664
|
37646234306530346234393439323435623164306538396237343138313131386239613834323664
|
||||||
33323435333062333831653434383331306333376432623330643062666130333461336630393561
|
31356663616630363536613037633666636163333034663132643865626434633863653463333739
|
||||||
39383462626632316437343166396231386138616637306563343765626533653531636665393264
|
66656536643731323839376239333665353935343563363436323266343863346466363663613336
|
||||||
38633966333633316339643265376361643366336334336633393430316432393165363239373332
|
62373064633264643932376265653162336661313133326438303931646135666161383165626262
|
||||||
38326263343637663661663963303464636561623332333438306337343430643162626366643936
|
65613132613737626462613038383234643838653462646364373166323064383366333933643933
|
||||||
66363166626665363031306161626263666531313733373833633433643130613039336539653466
|
65653734343638303235336561366131336630353761373866396437323864386235646665656334
|
||||||
32323436323439366265656236623163366432306433323839343061376464633330376538626334
|
63666264636238633938653535376431616539376465303964396334323435636564613562643462
|
||||||
32626436363463643534376634626562396437373037653962356262653939316562393233383735
|
33396239383132626366333466393135313834383038383361383631616336613166663062656634
|
||||||
38643832386238316235366439646132336136363131326665653465306265353966623761366632
|
65616538303165616637356464353039333765643736313761393237643933643736653436366462
|
||||||
63373332313766343366663137653131336339663032616435366362633638373033663762316437
|
63363732363864336664363637643663383732386537396231303565633036303534383464336533
|
||||||
66353732356166376435316135646635636361626361653338343534383562383836343763363630
|
63613334336138353632613764663962346263323536343532643563666332626531653464333931
|
||||||
61383331623534346334393065636136306339353433326436633965376363313030303661333935
|
38333734303230343533646238323865396434346232656664376465383938343139396534303838
|
||||||
38363431363430323461353339643130393164326161356662343766616631326539346131666635
|
65333361346234663133383837646632393939636365383966386364616433636666313466633138
|
||||||
35613462643731376336646338636464333935666666323532633730333162373865363333393638
|
38393835323263393536336439646237613033306665336133373836323539303033383461666333
|
||||||
35316534643330613962656665303363353434313664663932666466616630653636316666646332
|
38616631656430643333313634343061623934633166623262656133666135646162343363366539
|
||||||
38376431373363303931336437323432363432393539363461373135633839363334393232343334
|
35386434343931393432313061386632613632633733646437666265653130363935666239393735
|
||||||
64353431363838313564613839623866653964383762313632366232393131323466656431343035
|
35666633303132636134626365376633386531313036653536616462373164303734303836303936
|
||||||
66663635616131373665323166386364323637663061653734393039656232303434613865313538
|
66333338613861323266633339383539303238626230613663306438386333373831316135303939
|
||||||
35336430333739356632303837323133376664643433303361396336636130636666373639626230
|
39316264616663323761323130313930373834666161663836333735306338633232313637313361
|
||||||
34373761643564306663643037623531373163336131303733653739356362323539
|
66666338626338343434653761363735613166356632396663643332616631353433306566306662
|
||||||
|
63363538346634376338313538376333376537316532386531633537396638663732666665353462
|
||||||
|
61353339356461323532313630663135626532306132336161373235663033633033656461336235
|
||||||
|
62613434623631353336376230656232363730643637646131626430393962616537306262386565
|
||||||
|
31303839323263343439623935313666303330353539623130653838623161323130373933383039
|
||||||
|
38336631343761633861383338666365343562613835313265633165313832613665613230303533
|
||||||
|
37646432623461313236373137363536613565353164333935346337376635336264623965346337
|
||||||
|
61646462653032333362353738666238323861323835306664303735326162356338613162353763
|
||||||
|
38333035323661313661346136663437613931386432376165643830613066643061623934336130
|
||||||
|
64396431646533646130636333363866396138323131383037373937383230353662646634376132
|
||||||
|
30643530336665663865623165653135363961373838666134346463343239353733326262396630
|
||||||
|
34373562646634373566373238353836326562313162663764386239396364333162326630626630
|
||||||
|
31373962643765663364333766373031646263626236613461343533396164663962633235343136
|
||||||
|
62373936646332303161326265383165353362643234653831613539613231623563353566613633
|
||||||
|
64316461616236313137353136336365313833643333376665306232636537333734343633613461
|
||||||
|
66373066393639623037323535333661656664383265623130656563383537633131336562306237
|
||||||
|
64366135643537653231396238313836353439303465343732323533356437353935396139376462
|
||||||
|
3839656237373031313431326364373839613265303330383236
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
# Paperclip Docker deployment
|
# Paperclip Docker deployment
|
||||||
paperclip_domain: muppet-vision.wdw-labs.cloud
|
paperclip_domain: cranium-command.wdw-labs.cloud
|
||||||
paperclip_base_path: /opt/paperclip
|
paperclip_base_path: /opt/paperclip
|
||||||
paperclip_port: 3100
|
paperclip_port: 3100
|
||||||
certbot_email: ryan.blundon@protonmail.com
|
certbot_email: ryan.blundon@protonmail.com
|
||||||
@@ -8,6 +8,12 @@ certbot_email: ryan.blundon@protonmail.com
|
|||||||
# vLLM inference endpoint (local)
|
# vLLM inference endpoint (local)
|
||||||
vllm_endpoint: "http://127.0.0.1:8000"
|
vllm_endpoint: "http://127.0.0.1:8000"
|
||||||
|
|
||||||
|
hermes_model:
|
||||||
|
provider: custom
|
||||||
|
default: Gemma-4-26B-A4B-Hermes-VLM-MLX # from http://fantasmic.wdw-labs.cloud:8000/v1/models
|
||||||
|
base_url: http://fantasmic.wdw-labs.cloud:8000/v1
|
||||||
|
api_key: "{{ vault_fantasmic_api_key }}"
|
||||||
|
|
||||||
# # DNS entries (if using your existing dns-manager role)
|
# # DNS entries (if using your existing dns-manager role)
|
||||||
# dns_entries:
|
# dns_entries:
|
||||||
# - name: illuminations
|
# - name: illuminations
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# FILE: ansible/host_vars/astro_orbiter/vars.yml
|
# FILE: ansible/host_vars/illuminations/vars.yml
|
||||||
# HOST: astro-orbiter (10.1.71.130)
|
# HOST: illuminations (10.1.71.130)
|
||||||
# ROLE: llama.cpp LLM inference host — Ryzen 7 5800XT / RTX 3090 (ATX rebuild,
|
# ROLE: llama.cpp LLM inference host — Ryzen 7 5800XT / RTX 3090 (ATX rebuild,
|
||||||
# 2026-08-04). Superseded the prior AMD RX 5700 / Ollama config below;
|
# 2026-08-04). Superseded the prior AMD RX 5700 / Ollama config below;
|
||||||
# drive was transplanted into new hardware, not reinstalled.
|
# drive was transplanted into new hardware, not reinstalled.
|
||||||
@@ -234,7 +234,11 @@ vllm_service_enabled: true
|
|||||||
vllm_service_state: started
|
vllm_service_state: started
|
||||||
|
|
||||||
# Paperclip Docker deployment
|
# Paperclip Docker deployment
|
||||||
paperclip_domain: muppet-vision.wdw-labs.cloud
|
nginx_proxy_certbot_email: ryan.blundon@protonmail.com
|
||||||
paperclip_base_path: /opt/paperclip
|
nginx_proxy_certbot_staging: true # remove after first successful real issuance
|
||||||
paperclip_port: 3100
|
|
||||||
certbot_email: ryan.blundon@protonmail.com
|
hermes_model:
|
||||||
|
provider: custom
|
||||||
|
default: Gemma-4-26B-A4B-Hermes-VLM-MLX # from http://fantasmic.wdw-labs.cloud:8000/v1/models
|
||||||
|
base_url: http://fantasmic.wdw-labs.cloud:8000/v1
|
||||||
|
api_key: "{{ vault_fantasmic_api_key }}"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
# Run after day1_deploy_nginx_proxy.yml (the role publishes its vhost through it).
|
||||||
|
- name: Deploy Hermes Agent (Docker)
|
||||||
|
hosts: illuminations
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- hermes_docker
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
- name: Deploy shared nginx reverse proxy and certbot
|
||||||
|
hosts: illuminations
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- nginx-proxy
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy Paperclip stack with nginx and certbot
|
- name: Deploy Paperclip stack (behind the shared nginx-proxy)
|
||||||
hosts: illuminations
|
hosts: illuminations
|
||||||
become: true
|
become: true
|
||||||
vars_files:
|
vars_files:
|
||||||
@@ -8,77 +8,75 @@
|
|||||||
|
|
||||||
roles:
|
roles:
|
||||||
- docker-host
|
- docker-host
|
||||||
|
- nginx-proxy # must come before paperclip-docker (it publishes its vhost through it)
|
||||||
- paperclip-docker
|
- paperclip-docker
|
||||||
|
|
||||||
post_tasks:
|
post_tasks:
|
||||||
- name: Verify Paperclip stack is running
|
# CLI-based on purpose: community.docker.docker_container_info needs the
|
||||||
community.docker.docker_container_info:
|
# Docker SDK for Python, which the paperclip-docker role no longer installs.
|
||||||
name: "{{ item }}"
|
- name: Verify stack containers are running
|
||||||
|
ansible.builtin.command: "docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ item }}"
|
||||||
register: container_status
|
register: container_status
|
||||||
loop:
|
loop:
|
||||||
|
- paperclip-db
|
||||||
- paperclip
|
- paperclip
|
||||||
- paperclip-nginx
|
- nginx
|
||||||
- paperclip-certbot
|
- certbot
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Fail if any container is not running
|
|
||||||
fail:
|
|
||||||
msg: "Container {{ item.item }} is not running or does not exist"
|
|
||||||
when: not item.exists or item.container.State.Running == false
|
|
||||||
loop: "{{ container_status.results }}"
|
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
failed_when: container_status.rc != 0 or container_status.stdout | trim != 'true'
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
|
||||||
- name: Verify Docker is available
|
- name: Verify Docker is available
|
||||||
command: docker --version
|
ansible.builtin.command: docker --version
|
||||||
register: docker_version
|
register: docker_version
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: "Docker version: {{ docker_version.stdout }}"
|
msg: "Docker version: {{ docker_version.stdout }}"
|
||||||
|
|
||||||
- name: Verify docker-compose plugin is available
|
- name: Verify docker-compose plugin is available
|
||||||
command: docker compose version
|
ansible.builtin.command: docker compose version
|
||||||
register: compose_version
|
register: compose_version
|
||||||
changed_when: false
|
changed_when: false
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Show docker-compose version
|
- name: Show docker-compose version
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: "Docker Compose version: {{ compose_version.stdout | default('docker-compose plugin not found') }}"
|
msg: "Docker Compose version: {{ compose_version.stdout | default('docker-compose plugin not found') }}"
|
||||||
|
|
||||||
- name: Display deployment summary
|
- name: Display deployment summary
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
msg: |
|
||||||
Paperclip stack deployed successfully!
|
Paperclip stack deployed successfully!
|
||||||
|
|
||||||
Access points:
|
Access points:
|
||||||
- HTTPS: https://{{ paperclip_domain }}
|
- HTTPS: https://{{ paperclip_domain }}
|
||||||
- HTTP (redirects to HTTPS): http://{{ paperclip_domain }}
|
- HTTP (redirects to HTTPS): http://{{ paperclip_domain }}
|
||||||
|
|
||||||
Services:
|
Services:
|
||||||
- Paperclip: {{ paperclip_host }}:{{ paperclip_port }}
|
- Paperclip: {{ paperclip_host }}:{{ paperclip_port }}
|
||||||
- nginx (reverse proxy): 0.0.0.0:443 / 0.0.0.0:80
|
- nginx (shared reverse proxy): 0.0.0.0:443 / 0.0.0.0:80, config in {{ nginx_proxy_conf_dir }}
|
||||||
- certbot: Running certificate renewal checks
|
- certbot: Running certificate renewal checks
|
||||||
|
|
||||||
Data location:
|
Data location:
|
||||||
- {{ paperclip_base_path }}/data
|
- {{ paperclip_base_path }}/data
|
||||||
|
|
||||||
Certificates:
|
Certificates:
|
||||||
- {{ paperclip_certs_dir }}/live/{{ paperclip_domain }}/
|
- {{ nginx_proxy_certs_dir }}/live/{{ paperclip_domain }}/
|
||||||
|
|
||||||
vLLM endpoint:
|
vLLM endpoint:
|
||||||
- {{ vllm_endpoint }}
|
- {{ vllm_endpoint }}
|
||||||
|
|
||||||
OpenCode configuration:
|
OpenCode configuration:
|
||||||
- Config: {{ paperclip_base_path }}/.config/opencode/opencode.jsonc
|
- Config: {{ paperclip_base_path }}/.config/opencode/opencode.jsonc
|
||||||
- Model: Gemma-4-26B (AWQ)
|
- Model: Gemma-4-26B (AWQ)
|
||||||
- Endpoint: {{ vllm_endpoint }}/v1
|
- Endpoint: {{ vllm_endpoint }}/v1
|
||||||
|
|
||||||
Next steps:
|
Next steps:
|
||||||
1. Verify DNS resolution for {{ paperclip_domain }}
|
1. Verify DNS resolution for {{ paperclip_domain }}
|
||||||
2. Visit https://{{ paperclip_domain }} to access Paperclip
|
2. Visit https://{{ paperclip_domain }} to access Paperclip
|
||||||
3. OpenCode should auto-initialize with vLLM at {{ vllm_endpoint }}
|
3. OpenCode should auto-initialize with vLLM at {{ vllm_endpoint }}
|
||||||
4. Set up Hermes Gateway on Mac Studio
|
4. Run day1_deploy_hermes_docker.yml (publishes its dashboard through the same nginx)
|
||||||
5. Configure agents and skills
|
5. Configure agents and skills
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
hermes_image: nousresearch/hermes-agent:latest
|
||||||
|
# missing = only pull if absent (no surprise upgrades on redeploy).
|
||||||
|
# Set to "always" when you actually want to upgrade the image.
|
||||||
|
hermes_pull_policy: missing
|
||||||
|
|
||||||
|
hermes_container_name: hermes
|
||||||
|
hermes_project_dir: /opt/hermes
|
||||||
|
hermes_data_dir: "{{ hermes_project_dir }}/data"
|
||||||
|
# The container's hermes user. The image's start-up hook chowns the data
|
||||||
|
# volume to it; the role only needs the UID to stage files it can read.
|
||||||
|
hermes_uid: 10000
|
||||||
|
# config.yaml, .env and sessions live here and can hold API keys, so keep it
|
||||||
|
# owner-only. Passed to the container as HERMES_HOME_MODE (see compose).
|
||||||
|
hermes_data_dir_mode: "0700"
|
||||||
|
hermes_secrets_env_file: "{{ hermes_project_dir }}/hermes-secrets.env"
|
||||||
|
|
||||||
|
# Same network namespace as Paperclip and nginx
|
||||||
|
hermes_network_mode: host
|
||||||
|
hermes_memory_limit: 8G
|
||||||
|
hermes_cpu_limit: "4.0"
|
||||||
|
# Playwright/Chromium needs shared memory (upstream troubleshooting note)
|
||||||
|
hermes_shm_size: 1g
|
||||||
|
|
||||||
|
# --- Gateway / API server (loopback only; Paperclip reaches it locally) ---
|
||||||
|
hermes_gateway_port: 8642
|
||||||
|
hermes_api_server_enabled: true
|
||||||
|
hermes_api_server_host: 127.0.0.1
|
||||||
|
# Leave empty to run keyless on loopback. If you set one, Paperclip (or any
|
||||||
|
# other client) must send it as a bearer token.
|
||||||
|
# e.g. hermes_api_server_key: "{{ vault_hermes_api_server_key }}"
|
||||||
|
hermes_api_server_key: ""
|
||||||
|
|
||||||
|
# --- Dashboard (bound to loopback, published through nginx) ---
|
||||||
|
hermes_dashboard_enabled: true
|
||||||
|
hermes_dashboard_host: 127.0.0.1
|
||||||
|
hermes_dashboard_port: 9119
|
||||||
|
hermes_domain: haunted-mansion.wdw-labs.cloud
|
||||||
|
# Publish the dashboard through the nginx-proxy role (cert + vhost + reload).
|
||||||
|
# Requires day1_deploy_nginx_proxy.yml to have run first.
|
||||||
|
hermes_publish_vhost: true
|
||||||
|
hermes_public_url: "https://{{ hermes_domain }}"
|
||||||
|
hermes_dashboard_username: admin
|
||||||
|
|
||||||
|
# Vault variables this role requires (see README block at bottom of file)
|
||||||
|
hermes_required_vault_vars:
|
||||||
|
- vault_hermes_dashboard_password_hash
|
||||||
|
- vault_hermes_dashboard_secret
|
||||||
|
|
||||||
|
# --- Preflight ---
|
||||||
|
hermes_preflight_ports:
|
||||||
|
- "{{ hermes_gateway_port }}"
|
||||||
|
- "{{ hermes_dashboard_port }}"
|
||||||
|
hermes_wait_timeout: 120
|
||||||
|
|
||||||
|
# --- config.yaml overrides (deep-merged, only the keys listed here) ---
|
||||||
|
# nginx runs with host networking, so it reaches the dashboard from loopback.
|
||||||
|
# Model settings are NOT managed by default (your wizard-written config stays
|
||||||
|
# untouched). To manage them, add a model block, for example:
|
||||||
|
# model:
|
||||||
|
# provider: custom
|
||||||
|
# model: <served-model-name>
|
||||||
|
# base_url: http://10.1.71.130:8000/v1
|
||||||
|
# api_key: "{{ vault_vllm_api_key }}"
|
||||||
|
hermes_config_overrides:
|
||||||
|
dashboard:
|
||||||
|
trusted_proxies:
|
||||||
|
- "127.0.0.1"
|
||||||
|
|
||||||
|
# Main model (written to config.yaml under model:). Leave empty to manage it
|
||||||
|
# by hand with `hermes model` or the dashboard. Example:
|
||||||
|
# hermes_model:
|
||||||
|
# provider: custom
|
||||||
|
# default: <served model name from the endpoint's /v1/models>
|
||||||
|
# base_url: http://fantasmic.wdw-labs.cloud:8000/v1
|
||||||
|
# api_key: "{{ vault_fantasmic_api_key }}"
|
||||||
|
# # context_length: 65536 # only if Hermes complains about the context window
|
||||||
|
# The api_key lands in config.yaml in plain text (owner-only, see hermes_data_dir_mode).
|
||||||
|
hermes_model: {}
|
||||||
|
|
||||||
|
# What the merge task actually applies: base overrides + the model block.
|
||||||
|
hermes_effective_overrides: >-
|
||||||
|
{{ hermes_config_overrides
|
||||||
|
| combine({'model': hermes_model} if (hermes_model | length > 0) else {}, recursive=True) }}
|
||||||
|
|
||||||
|
# Required vault vars:
|
||||||
|
# vault_hermes_dashboard_password_hash - output of hash_password() (see below)
|
||||||
|
# vault_hermes_dashboard_secret - openssl rand -base64 32
|
||||||
|
# Generate the hash with:
|
||||||
|
# docker exec -it -w /opt/hermes hermes /opt/hermes/.venv/bin/python -c \
|
||||||
|
# "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""Deep-merge overrides (a JSON file, path in argv[1]) into config.yaml.
|
||||||
|
|
||||||
|
Run inside the hermes container as the hermes user. Only keys present in the
|
||||||
|
overrides are touched. Prints CHANGED when the file was rewritten.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
PATH = "/opt/data/config.yaml"
|
||||||
|
with open(sys.argv[1]) as fh:
|
||||||
|
overrides = json.load(fh)
|
||||||
|
|
||||||
|
if os.path.exists(PATH):
|
||||||
|
with open(PATH) as fh:
|
||||||
|
current = yaml.safe_load(fh) or {}
|
||||||
|
else:
|
||||||
|
current = {}
|
||||||
|
|
||||||
|
|
||||||
|
def merge(dst, src):
|
||||||
|
changed = False
|
||||||
|
for key, val in src.items():
|
||||||
|
if isinstance(val, dict) and isinstance(dst.get(key), dict):
|
||||||
|
changed |= merge(dst[key], val)
|
||||||
|
elif dst.get(key) != val:
|
||||||
|
dst[key] = val
|
||||||
|
changed = True
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
if merge(current, overrides):
|
||||||
|
with open(PATH, "w") as fh:
|
||||||
|
yaml.safe_dump(current, fh, sort_keys=False)
|
||||||
|
print("CHANGED")
|
||||||
|
else:
|
||||||
|
print("UNCHANGED")
|
||||||
|
sys.exit(0)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
# Config.yaml is read at start, so a plain restart is enough.
|
||||||
|
# Compose/env changes are handled by docker compose itself (it recreates).
|
||||||
|
- name: Restart hermes
|
||||||
|
ansible.builtin.command: "docker restart {{ hermes_container_name }}"
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
- name: Preflight | Required vault variables are defined
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: "{{ item }} is defined and ({{ item }} | length) > 0"
|
||||||
|
fail_msg: "{{ item }} is not defined. Add it to your ansible vault."
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ hermes_required_vault_vars }}"
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Preflight | Confirm docker CLI is available
|
||||||
|
ansible.builtin.command: docker --version
|
||||||
|
changed_when: false
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Preflight | Get existing container state
|
||||||
|
ansible.builtin.command: "docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ hermes_container_name }}"
|
||||||
|
register: hermes_existing
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
# Only enforce free ports when our container is not already running,
|
||||||
|
# so redeploys stay idempotent.
|
||||||
|
- name: Preflight | Ensure required ports are free
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
timeout: 3
|
||||||
|
loop: "{{ hermes_preflight_ports }}"
|
||||||
|
when: hermes_existing.stdout | default('') | trim != 'true'
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Create project directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ hermes_project_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
# No owner here: the image's start-up hook chowns the volume to the hermes user.
|
||||||
|
- name: Create data directory (owner-only)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ hermes_data_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: "{{ hermes_data_dir_mode }}"
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
# Compose reads this as root on the host; the container never sees the file.
|
||||||
|
- name: Template secrets env file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: hermes-secrets.env.j2
|
||||||
|
dest: "{{ hermes_secrets_env_file }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Template docker compose file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: docker-compose.yml.j2
|
||||||
|
dest: "{{ hermes_project_dir }}/docker-compose.yml"
|
||||||
|
mode: "0644"
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Deploy hermes
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ hermes_project_dir }}"
|
||||||
|
pull: "{{ hermes_pull_policy }}"
|
||||||
|
state: present
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Verify gateway health
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://127.0.0.1:{{ hermes_gateway_port }}/health"
|
||||||
|
status_code: 200
|
||||||
|
register: hermes_health
|
||||||
|
until: hermes_health.status == 200
|
||||||
|
retries: "{{ (hermes_wait_timeout | int) // 5 }}"
|
||||||
|
delay: 5
|
||||||
|
when: hermes_api_server_enabled | bool
|
||||||
|
tags: [hermes, verify]
|
||||||
|
|
||||||
|
- name: Wait for dashboard port
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
host: "{{ hermes_dashboard_host }}"
|
||||||
|
port: "{{ hermes_dashboard_port }}"
|
||||||
|
timeout: "{{ hermes_wait_timeout }}"
|
||||||
|
when: hermes_dashboard_enabled | bool
|
||||||
|
tags: [hermes, verify]
|
||||||
|
|
||||||
|
# Overrides can contain secrets (the model api_key), so they are staged in a
|
||||||
|
# 0600 file owned by the container user instead of travelling in a command
|
||||||
|
# line (visible in `ps`), and removed again afterwards. The merge script runs
|
||||||
|
# inside the container with the image's own venv (PyYAML is available there),
|
||||||
|
# as the hermes user, so config.yaml stays owned by it.
|
||||||
|
- name: Apply config.yaml overrides
|
||||||
|
when: hermes_effective_overrides | length > 0
|
||||||
|
tags: [hermes, config]
|
||||||
|
block:
|
||||||
|
- name: Stage overrides file
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ hermes_effective_overrides | to_json }}"
|
||||||
|
dest: "{{ hermes_data_dir }}/.ansible-overrides.json"
|
||||||
|
owner: "{{ hermes_uid }}"
|
||||||
|
group: "{{ hermes_uid }}"
|
||||||
|
mode: "0600"
|
||||||
|
changed_when: false # transient file; only the merge result should count as a change
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Merge into config.yaml
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -i
|
||||||
|
- --user
|
||||||
|
- hermes
|
||||||
|
- "{{ hermes_container_name }}"
|
||||||
|
- /opt/hermes/.venv/bin/python
|
||||||
|
- "-"
|
||||||
|
- /opt/data/.ansible-overrides.json
|
||||||
|
stdin: "{{ lookup('ansible.builtin.file', 'merge_config.py') }}"
|
||||||
|
register: hermes_config_merge
|
||||||
|
changed_when: "'CHANGED' in hermes_config_merge.stdout and 'UNCHANGED' not in hermes_config_merge.stdout"
|
||||||
|
notify: Restart hermes
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Remove staged overrides file
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ hermes_data_dir }}/.ansible-overrides.json"
|
||||||
|
state: absent
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Publish dashboard through nginx-proxy
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: nginx-proxy
|
||||||
|
tasks_from: add_vhost
|
||||||
|
vars:
|
||||||
|
vhost_name: hermes
|
||||||
|
vhost_domain: "{{ hermes_domain }}"
|
||||||
|
vhost_upstream_port: "{{ hermes_dashboard_port }}"
|
||||||
|
vhost_proxy_timeout: 3600s # long-lived dashboard WebSockets
|
||||||
|
when:
|
||||||
|
- hermes_publish_vhost | bool
|
||||||
|
- hermes_dashboard_enabled | bool
|
||||||
|
tags: [hermes, nginx]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Managed by Ansible (hermes_docker role)
|
||||||
|
services:
|
||||||
|
hermes:
|
||||||
|
image: {{ hermes_image }}
|
||||||
|
container_name: {{ hermes_container_name }}
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: {{ hermes_network_mode }}
|
||||||
|
command: gateway run
|
||||||
|
shm_size: "{{ hermes_shm_size }}"
|
||||||
|
env_file:
|
||||||
|
- {{ hermes_secrets_env_file }}
|
||||||
|
environment:
|
||||||
|
HERMES_HOME_MODE: "{{ hermes_data_dir_mode }}"
|
||||||
|
{% if hermes_dashboard_enabled | bool %}
|
||||||
|
HERMES_DASHBOARD: "1"
|
||||||
|
HERMES_DASHBOARD_HOST: "{{ hermes_dashboard_host }}"
|
||||||
|
HERMES_DASHBOARD_PORT: "{{ hermes_dashboard_port }}"
|
||||||
|
HERMES_DASHBOARD_PUBLIC_URL: "{{ hermes_public_url }}"
|
||||||
|
{% endif %}
|
||||||
|
{% if hermes_api_server_enabled | bool %}
|
||||||
|
API_SERVER_ENABLED: "true"
|
||||||
|
API_SERVER_HOST: "{{ hermes_api_server_host }}"
|
||||||
|
API_SERVER_PORT: "{{ hermes_gateway_port }}"
|
||||||
|
{% endif %}
|
||||||
|
volumes:
|
||||||
|
- {{ hermes_data_dir }}:/opt/data
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: {{ hermes_memory_limit }}
|
||||||
|
cpus: "{{ hermes_cpu_limit }}"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Managed by Ansible (hermes_docker role) - do not edit
|
||||||
|
# Single quotes keep compose from interpolating '$' inside hashes/secrets.
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_USERNAME={{ hermes_dashboard_username }}
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH='{{ vault_hermes_dashboard_password_hash }}'
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_SECRET='{{ vault_hermes_dashboard_secret }}'
|
||||||
|
{% if hermes_api_server_key | length > 0 %}
|
||||||
|
API_SERVER_KEY='{{ hermes_api_server_key }}'
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
hermes_image: nousresearch/hermes-agent:latest
|
||||||
|
# missing = only pull if absent (no surprise upgrades on redeploy).
|
||||||
|
# Set to "always" when you actually want to upgrade the image.
|
||||||
|
hermes_pull_policy: missing
|
||||||
|
|
||||||
|
hermes_container_name: hermes
|
||||||
|
hermes_project_dir: /opt/hermes
|
||||||
|
hermes_data_dir: "{{ hermes_project_dir }}/data"
|
||||||
|
hermes_secrets_env_file: "{{ hermes_project_dir }}/hermes-secrets.env"
|
||||||
|
|
||||||
|
# Same network namespace as Paperclip and nginx
|
||||||
|
hermes_network_mode: host
|
||||||
|
hermes_memory_limit: 8G
|
||||||
|
hermes_cpu_limit: "4.0"
|
||||||
|
# Playwright/Chromium needs shared memory (upstream troubleshooting note)
|
||||||
|
hermes_shm_size: 1g
|
||||||
|
|
||||||
|
# --- Gateway / API server (loopback only; Paperclip reaches it locally) ---
|
||||||
|
hermes_gateway_port: 8642
|
||||||
|
hermes_api_server_enabled: true
|
||||||
|
hermes_api_server_host: 127.0.0.1
|
||||||
|
# Leave empty to run keyless on loopback. If you set one, Paperclip (or any
|
||||||
|
# other client) must send it as a bearer token.
|
||||||
|
# e.g. hermes_api_server_key: "{{ vault_hermes_api_server_key }}"
|
||||||
|
hermes_api_server_key: "{{ vault_hermes_api_server_key }}"
|
||||||
|
|
||||||
|
# --- Dashboard (bound to loopback, published through nginx) ---
|
||||||
|
hermes_dashboard_enabled: true
|
||||||
|
hermes_dashboard_host: 127.0.0.1
|
||||||
|
hermes_dashboard_port: 9119
|
||||||
|
hermes_domain: haunted-mansion.wdw-labs.cloud
|
||||||
|
hermes_public_url: "https://{{ hermes_domain }}"
|
||||||
|
hermes_dashboard_username: admin
|
||||||
|
|
||||||
|
# Vault variables this role requires (see README block at bottom of file)
|
||||||
|
hermes_required_vault_vars:
|
||||||
|
- vault_hermes_dashboard_password_hash
|
||||||
|
- vault_hermes_dashboard_secret
|
||||||
|
|
||||||
|
# --- Preflight ---
|
||||||
|
hermes_preflight_ports:
|
||||||
|
- "{{ hermes_gateway_port }}"
|
||||||
|
- "{{ hermes_dashboard_port }}"
|
||||||
|
hermes_wait_timeout: 120
|
||||||
|
|
||||||
|
# --- config.yaml overrides (deep-merged, only the keys listed here) ---
|
||||||
|
# nginx runs with host networking, so it reaches the dashboard from loopback.
|
||||||
|
# Model settings are NOT managed by default (your wizard-written config stays
|
||||||
|
# untouched). To manage them, add a model block, for example:
|
||||||
|
# model:
|
||||||
|
# provider: custom
|
||||||
|
# model: <served-model-name>
|
||||||
|
# base_url: http://10.1.71.130:8000/v1
|
||||||
|
# api_key: "{{ vault_vllm_api_key }}"
|
||||||
|
hermes_config_overrides:
|
||||||
|
dashboard:
|
||||||
|
trusted_proxies:
|
||||||
|
- "127.0.0.1"
|
||||||
|
|
||||||
|
# Required vault vars:
|
||||||
|
# vault_hermes_dashboard_password_hash - output of hash_password() (see below)
|
||||||
|
# vault_hermes_dashboard_secret - openssl rand -base64 32
|
||||||
|
# Generate the hash with:
|
||||||
|
# docker exec -it -w /opt/hermes hermes /opt/hermes/.venv/bin/python -c \
|
||||||
|
# "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Deep-merge overrides (JSON in $HERMES_CONFIG_OVERRIDES) into config.yaml.
|
||||||
|
|
||||||
|
Run inside the hermes container as the hermes user. Only keys present in the
|
||||||
|
overrides are touched. Prints CHANGED when the file was rewritten.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
PATH = "/opt/data/config.yaml"
|
||||||
|
overrides = json.loads(os.environ["HERMES_CONFIG_OVERRIDES"])
|
||||||
|
|
||||||
|
if os.path.exists(PATH):
|
||||||
|
with open(PATH) as fh:
|
||||||
|
current = yaml.safe_load(fh) or {}
|
||||||
|
else:
|
||||||
|
current = {}
|
||||||
|
|
||||||
|
|
||||||
|
def merge(dst, src):
|
||||||
|
changed = False
|
||||||
|
for key, val in src.items():
|
||||||
|
if isinstance(val, dict) and isinstance(dst.get(key), dict):
|
||||||
|
changed |= merge(dst[key], val)
|
||||||
|
elif dst.get(key) != val:
|
||||||
|
dst[key] = val
|
||||||
|
changed = True
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
if merge(current, overrides):
|
||||||
|
with open(PATH, "w") as fh:
|
||||||
|
yaml.safe_dump(current, fh, sort_keys=False)
|
||||||
|
print("CHANGED")
|
||||||
|
else:
|
||||||
|
print("UNCHANGED")
|
||||||
|
sys.exit(0)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
# Config.yaml is read at start, so a plain restart is enough.
|
||||||
|
# Compose/env changes are handled by docker compose itself (it recreates).
|
||||||
|
- name: Restart hermes
|
||||||
|
ansible.builtin.command: "docker restart {{ hermes_container_name }}"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# ---- Hermes dashboard: paste inside the http { } block of nginx.conf.j2 ----
|
||||||
|
# Redirect HTTP to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name {{ hermes_domain }};
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name {{ hermes_domain }};
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/live/{{ hermes_domain }}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/live/{{ hermes_domain }}/privkey.pem;
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# No auth_basic here: the dashboard has its own login (basic auth provider).
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:{{ hermes_dashboard_port }};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $server_name;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
- name: Preflight | Required vault variables are defined
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: "{{ item }} is defined and ({{ item }} | length) > 0"
|
||||||
|
fail_msg: "{{ item }} is not defined. Add it to your ansible vault."
|
||||||
|
quiet: true
|
||||||
|
loop: "{{ hermes_required_vault_vars }}"
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Preflight | Confirm docker CLI is available
|
||||||
|
ansible.builtin.command: docker --version
|
||||||
|
changed_when: false
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Preflight | Get existing container state
|
||||||
|
community.docker.docker_container_info:
|
||||||
|
name: "{{ hermes_container_name }}"
|
||||||
|
register: hermes_existing
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
# Only enforce free ports when our container is not already running,
|
||||||
|
# so redeploys stay idempotent.
|
||||||
|
- name: Preflight | Ensure required ports are free
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: "{{ item }}"
|
||||||
|
state: stopped
|
||||||
|
timeout: 3
|
||||||
|
loop: "{{ hermes_preflight_ports }}"
|
||||||
|
when: not (hermes_existing.container.State.Running | default(false))
|
||||||
|
tags: [hermes, preflight]
|
||||||
|
|
||||||
|
- name: Create project and data directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- "{{ hermes_project_dir }}"
|
||||||
|
- "{{ hermes_data_dir }}"
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
# Compose reads this as root on the host; the container never sees the file.
|
||||||
|
- name: Template secrets env file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: hermes-secrets.env.j2
|
||||||
|
dest: "{{ hermes_secrets_env_file }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Template docker compose file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: docker-compose.yml.j2
|
||||||
|
dest: "{{ hermes_project_dir }}/docker-compose.yml"
|
||||||
|
mode: "0644"
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Deploy hermes
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ hermes_project_dir }}"
|
||||||
|
pull: "{{ hermes_pull_policy }}"
|
||||||
|
state: present
|
||||||
|
tags: [hermes]
|
||||||
|
|
||||||
|
- name: Verify gateway health
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://127.0.0.1:{{ hermes_gateway_port }}/health"
|
||||||
|
status_code: 200
|
||||||
|
register: hermes_health
|
||||||
|
until: hermes_health.status == 200
|
||||||
|
retries: "{{ (hermes_wait_timeout | int) // 5 }}"
|
||||||
|
delay: 5
|
||||||
|
when: hermes_api_server_enabled | bool
|
||||||
|
tags: [hermes, verify]
|
||||||
|
|
||||||
|
- name: Wait for dashboard port
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
host: "{{ hermes_dashboard_host }}"
|
||||||
|
port: "{{ hermes_dashboard_port }}"
|
||||||
|
timeout: "{{ hermes_wait_timeout }}"
|
||||||
|
when: hermes_dashboard_enabled | bool
|
||||||
|
tags: [hermes, verify]
|
||||||
|
|
||||||
|
# Runs inside the container with the image's own venv (PyYAML is available
|
||||||
|
# there) as the hermes user, so config.yaml stays owned by UID 10000.
|
||||||
|
- name: Merge config.yaml overrides
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- docker
|
||||||
|
- exec
|
||||||
|
- -i
|
||||||
|
- --user
|
||||||
|
- hermes
|
||||||
|
- -e
|
||||||
|
- "HERMES_CONFIG_OVERRIDES={{ hermes_config_overrides | to_json }}"
|
||||||
|
- "{{ hermes_container_name }}"
|
||||||
|
- /opt/hermes/.venv/bin/python
|
||||||
|
- "-"
|
||||||
|
stdin: "{{ lookup('ansible.builtin.file', 'merge_config.py') }}"
|
||||||
|
register: hermes_config_merge
|
||||||
|
changed_when: "'CHANGED' in hermes_config_merge.stdout and 'UNCHANGED' not in hermes_config_merge.stdout"
|
||||||
|
no_log: true
|
||||||
|
when: hermes_config_overrides | length > 0
|
||||||
|
notify: Restart hermes
|
||||||
|
tags: [hermes, config]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Managed by Ansible (hermes_docker role)
|
||||||
|
services:
|
||||||
|
hermes:
|
||||||
|
image: {{ hermes_image }}
|
||||||
|
container_name: {{ hermes_container_name }}
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: {{ hermes_network_mode }}
|
||||||
|
command: gateway run
|
||||||
|
shm_size: "{{ hermes_shm_size }}"
|
||||||
|
env_file:
|
||||||
|
- {{ hermes_secrets_env_file }}
|
||||||
|
environment:
|
||||||
|
{% if hermes_dashboard_enabled | bool %}
|
||||||
|
HERMES_DASHBOARD: "1"
|
||||||
|
HERMES_DASHBOARD_HOST: "{{ hermes_dashboard_host }}"
|
||||||
|
HERMES_DASHBOARD_PORT: "{{ hermes_dashboard_port }}"
|
||||||
|
HERMES_DASHBOARD_PUBLIC_URL: "{{ hermes_public_url }}"
|
||||||
|
{% endif %}
|
||||||
|
{% if hermes_api_server_enabled | bool %}
|
||||||
|
API_SERVER_ENABLED: "true"
|
||||||
|
API_SERVER_HOST: "{{ hermes_api_server_host }}"
|
||||||
|
API_SERVER_PORT: "{{ hermes_gateway_port }}"
|
||||||
|
{% endif %}
|
||||||
|
volumes:
|
||||||
|
- {{ hermes_data_dir }}:/opt/data
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: {{ hermes_memory_limit }}
|
||||||
|
cpus: "{{ hermes_cpu_limit }}"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Managed by Ansible (hermes_docker role) - do not edit
|
||||||
|
# Single quotes keep compose from interpolating '$' inside hashes/secrets.
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_USERNAME={{ hermes_dashboard_username }}
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH='{{ vault_hermes_dashboard_password_hash }}'
|
||||||
|
HERMES_DASHBOARD_BASIC_AUTH_SECRET='{{ vault_hermes_dashboard_secret }}'
|
||||||
|
{% if hermes_api_server_key | length > 0 %}
|
||||||
|
API_SERVER_KEY='{{ hermes_api_server_key }}'
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# nginx-proxy
|
||||||
|
|
||||||
|
Shared nginx + certbot (Cloudflare DNS challenge) under /opt/nginx. Apps publish
|
||||||
|
themselves with `tasks_from: add_vhost`; see the header of tasks/add_vhost.yml.
|
||||||
|
|
||||||
|
Layout on the host:
|
||||||
|
|
||||||
|
/opt/nginx/
|
||||||
|
docker-compose.yml .env (0600)
|
||||||
|
conf.d/ 00-global.conf + one <app>.conf per vhost -> /etc/nginx/conf.d
|
||||||
|
certs/ certbot state (live/, archive/, renewal/) -> /etc/letsencrypt, /etc/nginx/certs
|
||||||
|
cert-domains/ one empty file per domain (filename = domain)
|
||||||
|
scripts/ certbot-init.sh
|
||||||
|
|
||||||
|
Required variables: `nginx_proxy_certbot_email`, and `vault_cloudflare_api_token`
|
||||||
|
(or `nginx_proxy_cloudflare_api_token`).
|
||||||
|
|
||||||
|
Prerequisite (manual): a DNS record for each app domain pointing at this host.
|
||||||
|
|
||||||
|
Staging: set `nginx_proxy_certbot_staging: true` to test the DNS challenge without
|
||||||
|
using Let's Encrypt rate limits. Staging certs are skipped-as-existing afterwards, so
|
||||||
|
to move a domain to a real cert: `docker exec certbot certbot delete --cert-name <domain>`
|
||||||
|
and re-run its play.
|
||||||
|
|
||||||
|
Retire a domain: delete its cert-domains/ marker and conf.d/<app>.conf, reload nginx,
|
||||||
|
then `docker exec certbot certbot delete --cert-name <domain>`.
|
||||||
|
|
||||||
|
Pin nginx older than 1.25.1? Set `nginx_proxy_http2: false` (the template uses `http2 on;`).
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
nginx_proxy_base_path: /opt/nginx
|
||||||
|
nginx_proxy_certs_dir: "{{ nginx_proxy_base_path }}/certs"
|
||||||
|
nginx_proxy_conf_dir: "{{ nginx_proxy_base_path }}/conf.d"
|
||||||
|
nginx_proxy_domains_dir: "{{ nginx_proxy_base_path }}/cert-domains"
|
||||||
|
nginx_proxy_scripts_dir: "{{ nginx_proxy_base_path }}/scripts"
|
||||||
|
|
||||||
|
nginx_proxy_nginx_container: nginx
|
||||||
|
nginx_proxy_certbot_container: certbot
|
||||||
|
nginx_proxy_nginx_image: nginx:latest
|
||||||
|
nginx_proxy_certbot_image: certbot/dns-cloudflare:latest
|
||||||
|
|
||||||
|
# Required: set these in group_vars/host_vars (token comes from the vault)
|
||||||
|
nginx_proxy_certbot_email: ""
|
||||||
|
nginx_proxy_cloudflare_api_token: "{{ vault_cloudflare_api_token }}"
|
||||||
|
|
||||||
|
# true = Let's Encrypt staging (untrusted certs, no rate-limit risk). Use while iterating.
|
||||||
|
nginx_proxy_certbot_staging: false
|
||||||
|
|
||||||
|
# nginx reloads this often so renewed certs get picked up
|
||||||
|
nginx_proxy_reload_interval: 6h
|
||||||
|
nginx_proxy_client_max_body_size: 100M
|
||||||
|
|
||||||
|
# "http2 on;" needs nginx >= 1.25.1 (fine for nginx:latest). Set false if you pin older.
|
||||||
|
nginx_proxy_http2: true
|
||||||
|
|
||||||
|
nginx_proxy_pull_policy: missing
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Issues certificates (one per domain) via the Cloudflare DNS challenge.
|
||||||
|
#
|
||||||
|
# certbot-init.sh -> every domain with a marker file in $DOMAINS_DIR
|
||||||
|
# certbot-init.sh a.example.com -> only the domains given as arguments
|
||||||
|
#
|
||||||
|
# Idempotent: domains that already have a certificate are skipped.
|
||||||
|
# Renewal is NOT handled here; `certbot renew` covers every existing lineage.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DOMAINS_DIR="${DOMAINS_DIR:-/etc/cert-domains.d}"
|
||||||
|
EMAIL="${CERTBOT_EMAIL:-}"
|
||||||
|
TOKEN="${CLOUDFLARE_API_TOKEN:-}"
|
||||||
|
|
||||||
|
STAGING_ARG=""
|
||||||
|
if [ "${CERTBOT_STAGING:-0}" = "1" ]; then
|
||||||
|
STAGING_ARG="--server https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
echo "NOTE: using the Let's Encrypt STAGING server (certificates will not be trusted)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
DOMAINS="$*"
|
||||||
|
elif [ -d "$DOMAINS_DIR" ]; then
|
||||||
|
DOMAINS=$(ls -1 "$DOMAINS_DIR" | grep -E '^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}$' || true)
|
||||||
|
else
|
||||||
|
DOMAINS=""
|
||||||
|
fi
|
||||||
|
DOMAINS=$(printf '%s\n' "$DOMAINS" | tr ', \t' '\n\n\n' | grep -v '^$' | sort -u || true)
|
||||||
|
|
||||||
|
if [ -z "$DOMAINS" ]; then
|
||||||
|
echo "No domains registered in $DOMAINS_DIR; nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$EMAIL" ]; then
|
||||||
|
echo "ERROR: CERTBOT_EMAIL not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo "ERROR: CLOUDFLARE_API_TOKEN not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Temporary credentials file, shared by all requests and always cleaned up
|
||||||
|
CREDS_FILE=$(mktemp /tmp/cloudflare.XXXXXX)
|
||||||
|
trap 'rm -f "$CREDS_FILE"' EXIT
|
||||||
|
chmod 600 "$CREDS_FILE"
|
||||||
|
printf 'dns_cloudflare_api_token = %s\n' "$TOKEN" > "$CREDS_FILE"
|
||||||
|
|
||||||
|
FAILED=""
|
||||||
|
ISSUED=0
|
||||||
|
SKIPPED=0
|
||||||
|
|
||||||
|
for DOMAIN in $DOMAINS; do
|
||||||
|
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
|
||||||
|
|
||||||
|
if [ -f "$CERT_PATH" ]; then
|
||||||
|
echo "Certificate for $DOMAIN already exists at $CERT_PATH"
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Requesting certificate for $DOMAIN via Cloudflare DNS challenge..."
|
||||||
|
|
||||||
|
# --cert-name pins the lineage directory to the domain name so the
|
||||||
|
# live/<domain>/ path nginx expects never gets a -0001 suffix.
|
||||||
|
# STAGING_ARG is intentionally unquoted (it expands to two words).
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if certbot certonly \
|
||||||
|
--dns-cloudflare \
|
||||||
|
--dns-cloudflare-credentials "$CREDS_FILE" \
|
||||||
|
--agree-tos \
|
||||||
|
--no-eff-email \
|
||||||
|
--email "$EMAIL" \
|
||||||
|
--non-interactive \
|
||||||
|
--cert-name "$DOMAIN" \
|
||||||
|
$STAGING_ARG \
|
||||||
|
-d "$DOMAIN" && [ -f "$CERT_PATH" ]; then
|
||||||
|
echo "Certificate obtained and verified for $DOMAIN at $CERT_PATH"
|
||||||
|
ISSUED=$((ISSUED + 1))
|
||||||
|
else
|
||||||
|
echo "ERROR: Failed to obtain certificate for $DOMAIN"
|
||||||
|
FAILED="$FAILED $DOMAIN"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Summary: $ISSUED issued, $SKIPPED already present"
|
||||||
|
|
||||||
|
if [ -n "$FAILED" ]; then
|
||||||
|
echo "ERROR: certificate request failed for:$FAILED"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
- name: Reload nginx
|
||||||
|
ansible.builtin.shell: >
|
||||||
|
docker exec {{ nginx_proxy_nginx_container }} nginx -t &&
|
||||||
|
docker exec {{ nginx_proxy_nginx_container }} nginx -s reload
|
||||||
|
changed_when: true
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
# Publish an app through nginx-proxy. Usage:
|
||||||
|
#
|
||||||
|
# - ansible.builtin.include_role:
|
||||||
|
# name: nginx-proxy
|
||||||
|
# tasks_from: add_vhost
|
||||||
|
# vars:
|
||||||
|
# vhost_name: paperclip # required, becomes conf.d/<name>.conf
|
||||||
|
# vhost_domain: "{{ paperclip_domain }}" # required
|
||||||
|
# vhost_upstream_port: 3100 # required
|
||||||
|
# vhost_upstream_host: 127.0.0.1 # optional
|
||||||
|
# vhost_proxy_timeout: 60s # optional (use 3600s for WebSockets)
|
||||||
|
# vhost_health_path: /health # optional, proxied without access log
|
||||||
|
#
|
||||||
|
# Order: marker -> certificate -> vhost file -> nginx -t -> reload.
|
||||||
|
# The vhost is only written once the certificate exists, and a vhost that
|
||||||
|
# fails nginx -t is rolled back, so one bad app cannot take the others down.
|
||||||
|
- name: Validate vhost parameters
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- vhost_name is defined
|
||||||
|
- vhost_domain is defined
|
||||||
|
- vhost_upstream_port is defined
|
||||||
|
fail_msg: "add_vhost needs vhost_name, vhost_domain and vhost_upstream_port"
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: Check that the nginx-proxy containers are running
|
||||||
|
ansible.builtin.command: "docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ item }}"
|
||||||
|
register: nginx_proxy_running
|
||||||
|
failed_when: nginx_proxy_running.rc != 0 or nginx_proxy_running.stdout | trim != 'true'
|
||||||
|
changed_when: false
|
||||||
|
loop:
|
||||||
|
- "{{ nginx_proxy_nginx_container }}"
|
||||||
|
- "{{ nginx_proxy_certbot_container }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
|
||||||
|
- name: "Register cert marker for {{ vhost_domain }}"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_proxy_domains_dir }}/{{ vhost_domain }}"
|
||||||
|
state: touch
|
||||||
|
modification_time: preserve
|
||||||
|
access_time: preserve
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: "Check for existing certificate for {{ vhost_domain }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ nginx_proxy_certs_dir }}/live/{{ vhost_domain }}/fullchain.pem"
|
||||||
|
register: nginx_proxy_cert_before
|
||||||
|
|
||||||
|
# Targeted run: only this domain, so another app's failed issuance
|
||||||
|
# cannot fail this play.
|
||||||
|
- name: "Issue certificate for {{ vhost_domain }}"
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
docker exec {{ nginx_proxy_certbot_container }}
|
||||||
|
/scripts/certbot-init.sh {{ vhost_domain }}
|
||||||
|
register: nginx_proxy_cert_issue
|
||||||
|
changed_when: "'Certificate obtained' in nginx_proxy_cert_issue.stdout"
|
||||||
|
when: not nginx_proxy_cert_before.stat.exists
|
||||||
|
|
||||||
|
- name: "Confirm certificate exists for {{ vhost_domain }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ nginx_proxy_certs_dir }}/live/{{ vhost_domain }}/fullchain.pem"
|
||||||
|
register: nginx_proxy_cert_after
|
||||||
|
failed_when: not nginx_proxy_cert_after.stat.exists
|
||||||
|
|
||||||
|
- name: "Write {{ vhost_name }} vhost, test, and reload"
|
||||||
|
block:
|
||||||
|
- name: "Template conf.d/{{ vhost_name }}.conf"
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: vhost.conf.j2
|
||||||
|
dest: "{{ nginx_proxy_conf_dir }}/{{ vhost_name }}.conf"
|
||||||
|
mode: "0644"
|
||||||
|
backup: true
|
||||||
|
register: nginx_proxy_vhost_file
|
||||||
|
|
||||||
|
- name: Test nginx config
|
||||||
|
ansible.builtin.command: "docker exec {{ nginx_proxy_nginx_container }} nginx -t"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Reload nginx
|
||||||
|
ansible.builtin.command: "docker exec {{ nginx_proxy_nginx_container }} nginx -s reload"
|
||||||
|
when: nginx_proxy_vhost_file.changed
|
||||||
|
|
||||||
|
- name: Remove superseded backup
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_proxy_vhost_file.backup_file }}"
|
||||||
|
state: absent
|
||||||
|
when: nginx_proxy_vhost_file.backup_file is defined
|
||||||
|
|
||||||
|
rescue:
|
||||||
|
- name: Restore previous vhost
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ nginx_proxy_vhost_file.backup_file }}"
|
||||||
|
dest: "{{ nginx_proxy_conf_dir }}/{{ vhost_name }}.conf"
|
||||||
|
remote_src: true
|
||||||
|
mode: "0644"
|
||||||
|
when: nginx_proxy_vhost_file.backup_file | default('') | length > 0
|
||||||
|
|
||||||
|
- name: Remove backup after restoring it
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_proxy_vhost_file.backup_file }}"
|
||||||
|
state: absent
|
||||||
|
when: nginx_proxy_vhost_file.backup_file | default('') | length > 0
|
||||||
|
|
||||||
|
- name: Remove the new vhost (there was no previous one)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ nginx_proxy_conf_dir }}/{{ vhost_name }}.conf"
|
||||||
|
state: absent
|
||||||
|
when:
|
||||||
|
- nginx_proxy_vhost_file is defined
|
||||||
|
- nginx_proxy_vhost_file.backup_file | default('') | length == 0
|
||||||
|
|
||||||
|
- name: Fail with context
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
nginx rejected the {{ vhost_name }} vhost; it was rolled back and
|
||||||
|
the running config is unchanged.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
- name: Assert required variables
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- nginx_proxy_certbot_email | length > 0
|
||||||
|
- nginx_proxy_cloudflare_api_token | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Set nginx_proxy_certbot_email, and make sure vault_cloudflare_api_token
|
||||||
|
(or nginx_proxy_cloudflare_api_token) is defined.
|
||||||
|
quiet: true
|
||||||
|
tags: [nginx-proxy, preflight]
|
||||||
|
|
||||||
|
- name: Create directories
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- "{{ nginx_proxy_base_path }}"
|
||||||
|
- "{{ nginx_proxy_certs_dir }}"
|
||||||
|
- "{{ nginx_proxy_conf_dir }}"
|
||||||
|
- "{{ nginx_proxy_domains_dir }}"
|
||||||
|
- "{{ nginx_proxy_scripts_dir }}"
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Install certbot script
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: certbot-init.sh
|
||||||
|
dest: "{{ nginx_proxy_scripts_dir }}/certbot-init.sh"
|
||||||
|
mode: "0755"
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Template global nginx settings
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: 00-global.conf.j2
|
||||||
|
dest: "{{ nginx_proxy_conf_dir }}/00-global.conf"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload nginx
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Template compose env file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: env.j2
|
||||||
|
dest: "{{ nginx_proxy_base_path }}/.env"
|
||||||
|
mode: "0600"
|
||||||
|
no_log: true
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Template compose file
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: docker-compose.yml.j2
|
||||||
|
dest: "{{ nginx_proxy_base_path }}/docker-compose.yml"
|
||||||
|
mode: "0644"
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Deploy nginx and certbot
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ nginx_proxy_base_path }}"
|
||||||
|
pull: "{{ nginx_proxy_pull_policy }}"
|
||||||
|
state: present
|
||||||
|
tags: [nginx-proxy]
|
||||||
|
|
||||||
|
- name: Verify nginx config is valid and the container is serving
|
||||||
|
ansible.builtin.command: "docker exec {{ nginx_proxy_nginx_container }} nginx -t"
|
||||||
|
register: nginx_proxy_test
|
||||||
|
until: nginx_proxy_test.rc == 0
|
||||||
|
retries: 6
|
||||||
|
delay: 5
|
||||||
|
changed_when: false
|
||||||
|
tags: [nginx-proxy, verify]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Managed by Ansible (nginx-proxy role)
|
||||||
|
# Included inside the stock nginx.conf http { } block, so these are http-level.
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size {{ nginx_proxy_client_max_body_size }};
|
||||||
|
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
|
||||||
|
# Catch-all: unknown Host headers get nothing, and unknown SNI is rejected
|
||||||
|
# before any certificate is presented.
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
location = /nginx-health {
|
||||||
|
return 200 "ok\n";
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl default_server;
|
||||||
|
server_name _;
|
||||||
|
ssl_reject_handshake on;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Managed by Ansible (nginx-proxy role)
|
||||||
|
services:
|
||||||
|
certbot:
|
||||||
|
image: {{ nginx_proxy_certbot_image }}
|
||||||
|
container_name: {{ nginx_proxy_certbot_container }}
|
||||||
|
volumes:
|
||||||
|
- {{ nginx_proxy_certs_dir }}:/etc/letsencrypt
|
||||||
|
- {{ nginx_proxy_scripts_dir }}:/scripts:ro
|
||||||
|
- {{ nginx_proxy_domains_dir }}:/etc/cert-domains.d:ro
|
||||||
|
environment:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
|
||||||
|
CERTBOT_EMAIL: ${CERTBOT_EMAIL}
|
||||||
|
CERTBOT_STAGING: "{{ '1' if nginx_proxy_certbot_staging | bool else '0' }}"
|
||||||
|
entrypoint: /bin/sh
|
||||||
|
command:
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
while true; do
|
||||||
|
/scripts/certbot-init.sh || echo "issuance failed; will retry next cycle"
|
||||||
|
certbot renew --quiet
|
||||||
|
sleep 12h
|
||||||
|
done
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: {{ nginx_proxy_nginx_image }}
|
||||||
|
container_name: {{ nginx_proxy_nginx_container }}
|
||||||
|
network_mode: host
|
||||||
|
volumes:
|
||||||
|
# Directory mounts (not single files) so Ansible's atomic file
|
||||||
|
# replacement is visible inside the container.
|
||||||
|
- {{ nginx_proxy_conf_dir }}:/etc/nginx/conf.d:ro
|
||||||
|
- {{ nginx_proxy_certs_dir }}:/etc/nginx/certs:ro
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
( while :; do sleep {{ nginx_proxy_reload_interval }}; nginx -s reload; done ) &
|
||||||
|
exec nginx -g 'daemon off;'
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost/nginx-health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Managed by Ansible (nginx-proxy role)
|
||||||
|
CLOUDFLARE_API_TOKEN={{ nginx_proxy_cloudflare_api_token }}
|
||||||
|
CERTBOT_EMAIL={{ nginx_proxy_certbot_email }}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Managed by Ansible (nginx-proxy role) - vhost: {{ vhost_name }}
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name {{ vhost_domain }};
|
||||||
|
return 301 https://{{ vhost_domain }}$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
{% if nginx_proxy_http2 | bool %}
|
||||||
|
http2 on;
|
||||||
|
{% endif %}
|
||||||
|
server_name {{ vhost_domain }};
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/live/{{ vhost_domain }}/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/live/{{ vhost_domain }}/privkey.pem;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://{{ vhost_upstream_host | default('127.0.0.1') }}:{{ vhost_upstream_port }};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout {{ vhost_proxy_timeout | default('60s') }};
|
||||||
|
proxy_read_timeout {{ vhost_proxy_timeout | default('60s') }};
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
}
|
||||||
|
{% if vhost_health_path | default('') | length > 0 %}
|
||||||
|
|
||||||
|
location {{ vhost_health_path }} {
|
||||||
|
proxy_pass http://{{ vhost_upstream_host | default('127.0.0.1') }}:{{ vhost_upstream_port }}{{ vhost_health_path }};
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
@@ -2,31 +2,30 @@
|
|||||||
# Paperclip deployment paths and configuration
|
# Paperclip deployment paths and configuration
|
||||||
paperclip_base_path: /opt/paperclip
|
paperclip_base_path: /opt/paperclip
|
||||||
paperclip_data_dir: "{{ paperclip_base_path }}/data"
|
paperclip_data_dir: "{{ paperclip_base_path }}/data"
|
||||||
paperclip_certs_dir: "{{ paperclip_base_path }}/certs"
|
|
||||||
paperclip_port: 3100
|
paperclip_port: 3100
|
||||||
paperclip_domain: muppet-vision.wdw-labs.cloud
|
paperclip_domain: cranium-command.wdw-labs.cloud
|
||||||
paperclip_host: muppet-vision.wdw-labs.cloud
|
paperclip_host: cranium-command.wdw-labs.cloud
|
||||||
|
# paperclip_listen_host: 127.0.0.1 # Bootstrap local deployment
|
||||||
|
paperclip_listen_host: 0.0.0.0 # Switch to this host after CEO is functional on local model
|
||||||
|
|
||||||
# Paperclip allowed hostnames (space or comma-separated)
|
# Paperclip allowed hostnames (space or comma-separated)
|
||||||
# Includes: domain name, localhost (for internal/local access), and client IP
|
# Includes: domain name, localhost (for internal/local access), and client IP
|
||||||
paperclip_allowed_hostnames: "{{ paperclip_domain }} localhost 192.168.10.50"
|
paperclip_allowed_hostnames: "{{ paperclip_domain }} localhost 192.168.10.50"
|
||||||
|
|
||||||
# Deployment mode: authenticated (requires login) or local_trusted (no auth, trusted network)
|
# Deployment mode: authenticated (requires login) or local_trusted (no auth, trusted network)
|
||||||
paperclip_deployment_mode: "authenticated"
|
# paperclip_deployment_mode: "local_trusted" # Bootstrap local deployment
|
||||||
|
paperclip_deployment_mode: "authenticated" # Switch to this model after CEO is functional on local model
|
||||||
|
|
||||||
# Reverse proxy configuration
|
# TLS / reverse proxy are handled by the nginx-proxy role (/opt/nginx).
|
||||||
nginx_port: 443
|
# Set nginx_proxy_certbot_email in host_vars; run day1_deploy_nginx_proxy.yml first.
|
||||||
nginx_http_port: 80
|
|
||||||
|
|
||||||
# HTTPS/SSL configuration
|
# Inference endpoints (vLLM or remote OpenAI-compatible)
|
||||||
certbot_email: ryan.blundon@protonmail.com
|
illuminations_endpoint: "http://illuminations:8000"
|
||||||
certbot_provider: cloudflare
|
|
||||||
|
|
||||||
# Inference endpoint (vLLM or remote OpenAI-compatible)
|
|
||||||
vllm_endpoint: "http://127.0.0.1:8000"
|
|
||||||
fantasmic_endpoint: "http://fantasmic.wdw-labs.cloud:8000"
|
fantasmic_endpoint: "http://fantasmic.wdw-labs.cloud:8000"
|
||||||
wishes_endpoint: "http://fantasmic.wdw-labs.cloud:8000"
|
wishes_endpoint: "http://wishes.wdw-labs.cloud:8000"
|
||||||
|
|
||||||
# NOTE: Authentication secrets (BETTER_AUTH_SECRET, PAPERCLIP_AGENT_JWT_SECRET)
|
# NOTE: Secrets come from the ansible vault and are written to .env (mode 0600):
|
||||||
# are auto-generated during deployment and stored in .env (mode 0600)
|
# vault_better_auth_secret, vault_paperclip_agent_jwt_secret,
|
||||||
# No need to specify them here — they're created with: openssl rand -hex 24
|
# vault_illuminations_api_key, vault_fantasmic_api_key
|
||||||
|
# Everything in .env is passed into the Paperclip container (env_file), so only
|
||||||
|
# put values here that Paperclip itself should be able to read.
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
---
|
---
|
||||||
|
# Recreate (not just restart) so .env / env_file changes are actually applied.
|
||||||
|
# Skipped when the deploy task already created or recreated containers this run.
|
||||||
- name: Restart paperclip stack
|
- name: Restart paperclip stack
|
||||||
shell: |
|
community.docker.docker_compose_v2:
|
||||||
cd {{ paperclip_base_path }}
|
project_src: "{{ paperclip_base_path }}"
|
||||||
/usr/local/bin/docker-compose restart
|
services:
|
||||||
become: yes
|
- paperclip
|
||||||
|
state: present
|
||||||
- name: Reload nginx
|
recreate: always
|
||||||
shell: |
|
become: true
|
||||||
docker exec paperclip-nginx nginx -s reload
|
when: not (paperclip_deploy.changed | default(false))
|
||||||
become: yes
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Reload certbot
|
|
||||||
shell: |
|
|
||||||
docker restart paperclip-certbot
|
|
||||||
become: yes
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
@@ -1,27 +1,6 @@
|
|||||||
---
|
---
|
||||||
- name: Install Docker SDK for Python
|
|
||||||
apt:
|
|
||||||
name: python3-docker
|
|
||||||
state: present
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Download latest docker-compose from GitHub
|
|
||||||
shell: |
|
|
||||||
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
||||||
chmod +x /usr/local/bin/docker-compose
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Verify docker-compose installation
|
|
||||||
shell: /usr/local/bin/docker-compose --version
|
|
||||||
register: compose_version
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Show docker-compose version
|
|
||||||
debug:
|
|
||||||
msg: "{{ compose_version.stdout }}"
|
|
||||||
|
|
||||||
- name: Create paperclip directories
|
- name: Create paperclip directories
|
||||||
file:
|
ansible.builtin.file:
|
||||||
path: "{{ item }}"
|
path: "{{ item }}"
|
||||||
state: directory
|
state: directory
|
||||||
mode: '0755'
|
mode: '0755'
|
||||||
@@ -29,97 +8,77 @@
|
|||||||
group: root
|
group: root
|
||||||
loop:
|
loop:
|
||||||
- "{{ paperclip_base_path }}"
|
- "{{ paperclip_base_path }}"
|
||||||
- "{{ paperclip_data_dir }}"
|
# The postgres image chowns its own data dir on start; do not chown it here.
|
||||||
- "{{ paperclip_certs_dir }}"
|
|
||||||
- "{{ paperclip_base_path }}/pgdata"
|
- "{{ paperclip_base_path }}/pgdata"
|
||||||
become: true
|
become: true
|
||||||
|
tags: [paperclip, files]
|
||||||
|
|
||||||
- name: Create OpenCode config directory
|
# Container runs as UID 1000. Deliberately NOT recursive: recurse + mode would
|
||||||
file:
|
# chmod every file the app writes under these paths on each run. A fresh
|
||||||
path: "{{ paperclip_base_path }}/.config/opencode"
|
# deploy has no root-owned content to repair; if you ever need to, run a
|
||||||
|
# one-off `chown -R 1000:1000 <dir>` by hand.
|
||||||
|
- name: Create container-owned directories (UID 1000)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
state: directory
|
state: directory
|
||||||
mode: '0755'
|
mode: '0755'
|
||||||
owner: 1000
|
owner: 1000
|
||||||
group: 1000
|
group: 1000
|
||||||
|
loop:
|
||||||
|
- "{{ paperclip_data_dir }}"
|
||||||
|
- "{{ paperclip_base_path }}/.config/opencode"
|
||||||
|
- "{{ paperclip_base_path }}/.local/share/opencode"
|
||||||
become: true
|
become: true
|
||||||
|
tags: [paperclip, files]
|
||||||
|
|
||||||
- name: Create OpenCode .local/share directory
|
# - name: Deploy OpenCode auth.json with vLLM credentials
|
||||||
file:
|
# template:
|
||||||
path: "{{ paperclip_base_path }}/.local/share/opencode"
|
# src: opencode-auth.json.j2
|
||||||
state: directory
|
# dest: "{{ paperclip_base_path }}/.local/share/opencode/auth.json"
|
||||||
mode: '0755'
|
# mode: '0600'
|
||||||
owner: 1000
|
# owner: 1000
|
||||||
group: 1000
|
# group: 1000
|
||||||
become: yes
|
# become: true
|
||||||
|
# no_log: true
|
||||||
- name: Deploy OpenCode auth.json with vLLM credentials
|
|
||||||
template:
|
|
||||||
src: opencode-auth.json.j2
|
|
||||||
dest: "{{ paperclip_base_path }}/.local/share/opencode/auth.json"
|
|
||||||
mode: '0600'
|
|
||||||
owner: 1000
|
|
||||||
group: 1000
|
|
||||||
become: true
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: Fix directory ownership for container user (UID 1000)
|
|
||||||
shell: |
|
|
||||||
chown -R 1000:1000 {{ paperclip_data_dir }}
|
|
||||||
chown -R 1000:1000 {{ paperclip_base_path }}/pgdata
|
|
||||||
chown -R 1000:1000 {{ paperclip_base_path }}/.config
|
|
||||||
become: true
|
|
||||||
|
|
||||||
|
# This file is both the compose interpolation source and the container's
|
||||||
|
# env_file, so everything here is visible inside the Paperclip container.
|
||||||
|
# Infrastructure secrets (Cloudflare token, certbot email) live in
|
||||||
|
# /opt/nginx/.env instead.
|
||||||
- name: Write secrets and configuration to .env
|
- name: Write secrets and configuration to .env
|
||||||
copy:
|
ansible.builtin.copy:
|
||||||
content: |
|
content: |
|
||||||
# Authentication secrets (from vault)
|
# Authentication secrets (from vault)
|
||||||
BETTER_AUTH_SECRET={{ vault_better_auth_secret }}
|
BETTER_AUTH_SECRET={{ vault_better_auth_secret }}
|
||||||
PAPERCLIP_AGENT_JWT_SECRET={{ vault_paperclip_agent_jwt_secret }}
|
PAPERCLIP_AGENT_JWT_SECRET={{ vault_paperclip_agent_jwt_secret }}
|
||||||
|
|
||||||
# Infrastructure configuration
|
# Inference endpoints
|
||||||
CLOUDFLARE_API_TOKEN={{ vault_cloudflare_api_token }}
|
ILLUMINATIONS_ENDPOINT={{ illuminations_endpoint }}
|
||||||
CERTBOT_EMAIL={{ certbot_email }}
|
ILLUMINATIONS_API_KEY={{ vault_illuminations_api_key }}
|
||||||
PAPERCLIP_DOMAIN={{ paperclip_domain }}
|
FANTASMIC_ENDPOINT={{ fantasmic_endpoint }}
|
||||||
VLLM_ENDPOINT={{ vllm_endpoint }}
|
FANTASMIC_API_KEY={{ vault_fantasmic_api_key }}
|
||||||
VLLM_API_KEY={{ vault_vllm_api_key }}
|
|
||||||
dest: "{{ paperclip_base_path }}/.env"
|
dest: "{{ paperclip_base_path }}/.env"
|
||||||
mode: '0600'
|
mode: '0600'
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
become: true
|
become: true
|
||||||
no_log: true
|
no_log: true
|
||||||
|
notify: Restart paperclip stack
|
||||||
|
tags: [paperclip, files]
|
||||||
|
|
||||||
|
# No notify: the deploy task below recreates whatever the compose file changed.
|
||||||
- name: Template docker-compose.yml
|
- name: Template docker-compose.yml
|
||||||
template:
|
ansible.builtin.template:
|
||||||
src: docker-compose.yml.j2
|
src: docker-compose.yml.j2
|
||||||
dest: "{{ paperclip_base_path }}/docker-compose.yml"
|
dest: "{{ paperclip_base_path }}/docker-compose.yml"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
become: true
|
become: true
|
||||||
notify: Restart paperclip stack
|
tags: [paperclip, files]
|
||||||
|
|
||||||
- name: Template nginx config
|
|
||||||
template:
|
|
||||||
src: nginx.conf.j2
|
|
||||||
dest: "{{ paperclip_base_path }}/nginx.conf"
|
|
||||||
mode: '0644'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
become: true
|
|
||||||
notify: Reload nginx
|
|
||||||
|
|
||||||
- name: Template certbot init script
|
|
||||||
template:
|
|
||||||
src: certbot-init.sh.j2
|
|
||||||
dest: "{{ paperclip_base_path }}/certbot-init.sh"
|
|
||||||
mode: '0755'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Template OpenCode config
|
- name: Template OpenCode config
|
||||||
template:
|
ansible.builtin.template:
|
||||||
src: opencode.jsonc.j2
|
src: opencode.jsonc.j2
|
||||||
dest: "{{ paperclip_base_path }}/.config/opencode/opencode.jsonc"
|
dest: "{{ paperclip_base_path }}/.config/opencode/opencode.jsonc"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
@@ -127,43 +86,71 @@
|
|||||||
group: 1000
|
group: 1000
|
||||||
become: true
|
become: true
|
||||||
notify: Restart paperclip stack
|
notify: Restart paperclip stack
|
||||||
|
tags: [paperclip, files]
|
||||||
|
|
||||||
- name: Stop and remove old containers
|
# Idempotent: only changed services are recreated. remove_orphans also cleans
|
||||||
shell: |
|
# up the old paperclip-nginx / paperclip-certbot containers if they still exist.
|
||||||
cd {{ paperclip_base_path }}
|
- name: Deploy paperclip stack
|
||||||
/usr/local/bin/docker-compose down --remove-orphans 2>/dev/null || true
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ paperclip_base_path }}"
|
||||||
|
pull: missing
|
||||||
|
remove_orphans: true
|
||||||
|
state: present
|
||||||
|
register: paperclip_deploy
|
||||||
become: true
|
become: true
|
||||||
ignore_errors: true
|
tags: [paperclip, deploy]
|
||||||
|
|
||||||
- name: Deploy docker compose stack
|
|
||||||
shell: |
|
|
||||||
cd {{ paperclip_base_path }}
|
|
||||||
/usr/local/bin/docker-compose up -d
|
|
||||||
become: true
|
|
||||||
register: compose_result
|
|
||||||
|
|
||||||
- name: Wait for Paperclip to be ready
|
- name: Wait for Paperclip to be ready
|
||||||
uri:
|
ansible.builtin.uri:
|
||||||
url: "http://localhost:{{ paperclip_port }}/health"
|
url: "http://localhost:{{ paperclip_port }}/health"
|
||||||
status_code: 200
|
status_code: 200
|
||||||
register: result
|
register: paperclip_health
|
||||||
until: result.status == 200
|
until: paperclip_health.status == 200
|
||||||
retries: 30
|
retries: 30
|
||||||
delay: 5
|
delay: 5
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
tags: [paperclip, deploy, verify]
|
||||||
|
|
||||||
- name: Wait for nginx to be ready
|
- name: Warn if Paperclip never became healthy
|
||||||
uri:
|
ansible.builtin.debug:
|
||||||
url: "https://{{ paperclip_domain }}/"
|
msg: "WARNING: Paperclip did not report healthy on port {{ paperclip_port }}. Check: docker logs paperclip"
|
||||||
validate_certs: no
|
when: paperclip_health.status | default(0) != 200
|
||||||
status_code: [200, 301, 302]
|
tags: [paperclip, verify]
|
||||||
register: result
|
|
||||||
until: result.status in [200, 301, 302]
|
# Requires day1_deploy_nginx_proxy.yml to have run first.
|
||||||
retries: 30
|
- name: Publish Paperclip through nginx-proxy
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: nginx-proxy
|
||||||
|
tasks_from: add_vhost
|
||||||
|
apply:
|
||||||
|
become: true
|
||||||
|
tags: [paperclip, nginx]
|
||||||
|
vars:
|
||||||
|
vhost_name: paperclip
|
||||||
|
vhost_domain: "{{ paperclip_domain }}"
|
||||||
|
vhost_upstream_port: "{{ paperclip_port }}"
|
||||||
|
vhost_health_path: /health
|
||||||
|
tags: [paperclip, nginx]
|
||||||
|
|
||||||
|
# --resolve keeps this independent of DNS: it tests nginx, the cert and the
|
||||||
|
# upstream. DNS is a separate (manual) prerequisite.
|
||||||
|
- name: Verify HTTPS through nginx
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
curl -sk -o /dev/null -w '%{http_code}'
|
||||||
|
--resolve {{ paperclip_domain }}:443:127.0.0.1
|
||||||
|
https://{{ paperclip_domain }}/
|
||||||
|
register: paperclip_https
|
||||||
|
until: paperclip_https.stdout in ['200', '301', '302']
|
||||||
|
retries: 6
|
||||||
delay: 5
|
delay: 5
|
||||||
ignore_errors: true
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
tags: [paperclip, verify]
|
||||||
|
|
||||||
- name: Verify Paperclip is accessible via https
|
- name: Report HTTPS result
|
||||||
debug:
|
ansible.builtin.debug:
|
||||||
msg: "Paperclip is accessible at https://{{ paperclip_domain }}"
|
msg: >-
|
||||||
when: result.status is defined and result.status in [200, 301, 302]
|
{{ 'Paperclip is serving at https://' ~ paperclip_domain
|
||||||
|
if paperclip_https.stdout in ['200', '301', '302']
|
||||||
|
else 'WARNING: https://' ~ paperclip_domain ~ ' returned ' ~ (paperclip_https.stdout | default('no response')) ~ ' (502 usually means Paperclip is not up yet)' }}
|
||||||
|
tags: [paperclip, verify]
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DOMAIN="${PAPERCLIP_DOMAIN}"
|
|
||||||
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
|
|
||||||
EMAIL="${CERTBOT_EMAIL}"
|
|
||||||
TOKEN="${CLOUDFLARE_API_TOKEN}"
|
|
||||||
|
|
||||||
# Verify environment variables
|
|
||||||
if [ -z "$DOMAIN" ]; then
|
|
||||||
echo "ERROR: PAPERCLIP_DOMAIN not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$EMAIL" ]; then
|
|
||||||
echo "ERROR: CERTBOT_EMAIL not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$TOKEN" ]; then
|
|
||||||
echo "ERROR: CLOUDFLARE_API_TOKEN not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if certificate already exists
|
|
||||||
if [ -f "$CERT_PATH" ]; then
|
|
||||||
echo "Certificate for $DOMAIN already exists at $CERT_PATH"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Requesting certificate for $DOMAIN via Cloudflare DNS challenge..."
|
|
||||||
|
|
||||||
# Create a temporary credentials file (more reliable than env var with certbot)
|
|
||||||
CREDS_FILE="/tmp/cloudflare.ini"
|
|
||||||
cat > "$CREDS_FILE" << EOF
|
|
||||||
dns_cloudflare_api_token = ${TOKEN}
|
|
||||||
EOF
|
|
||||||
chmod 600 "$CREDS_FILE"
|
|
||||||
|
|
||||||
# Request certificate with Cloudflare DNS plugin
|
|
||||||
if certbot certonly \
|
|
||||||
--dns-cloudflare \
|
|
||||||
--dns-cloudflare-credentials "$CREDS_FILE" \
|
|
||||||
--agree-tos \
|
|
||||||
--no-eff-email \
|
|
||||||
--email "$EMAIL" \
|
|
||||||
--non-interactive \
|
|
||||||
-d "$DOMAIN"; then
|
|
||||||
echo "Certificate obtained successfully for $DOMAIN"
|
|
||||||
else
|
|
||||||
echo "ERROR: Failed to obtain certificate for $DOMAIN"
|
|
||||||
rm -f "$CREDS_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up credentials file
|
|
||||||
rm -f "$CREDS_FILE"
|
|
||||||
|
|
||||||
# Verify certificate
|
|
||||||
if [ -f "$CERT_PATH" ]; then
|
|
||||||
echo "Certificate verified at $CERT_PATH"
|
|
||||||
else
|
|
||||||
echo "ERROR: Certificate not found at $CERT_PATH after successful certbot run"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -3,6 +3,8 @@ services:
|
|||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: paperclip-db
|
container_name: paperclip-db
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
# network_mode: host would otherwise expose Postgres on every interface
|
||||||
|
command: ["postgres", "-c", "listen_addresses=127.0.0.1"]
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: paperclip
|
POSTGRES_USER: paperclip
|
||||||
POSTGRES_PASSWORD: paperclip
|
POSTGRES_PASSWORD: paperclip
|
||||||
@@ -23,12 +25,12 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- {{ paperclip_base_path }}/.env
|
- {{ paperclip_base_path }}/.env
|
||||||
environment:
|
environment:
|
||||||
HOST: "127.0.0.1"
|
HOST: {{ paperclip_listen_host }}
|
||||||
PAPERCLIP_HOME: /paperclip
|
PAPERCLIP_HOME: /paperclip
|
||||||
PAPERCLIP_PORT: {{ paperclip_port }}
|
PAPERCLIP_PORT: {{ paperclip_port }}
|
||||||
PAPERCLIP_PUBLIC_URL: "https://{{ paperclip_domain }}"
|
PAPERCLIP_PUBLIC_URL: "https://{{ paperclip_domain }}"
|
||||||
PAPERCLIP_DEPLOYMENT_MODE: "{{ paperclip_deployment_mode }}"
|
PAPERCLIP_DEPLOYMENT_MODE: "{{ paperclip_deployment_mode }}"
|
||||||
PAPERCLIP_DEPLOYMENT_EXPOSURE: "private"
|
# PAPERCLIP_DEPLOYMENT_EXPOSURE: "private"
|
||||||
PAPERCLIP_ALLOWED_HOSTNAMES: "{{ paperclip_allowed_hostnames }}"
|
PAPERCLIP_ALLOWED_HOSTNAMES: "{{ paperclip_allowed_hostnames }}"
|
||||||
DATABASE_URL: "postgresql://paperclip:paperclip@localhost:5432/paperclip"
|
DATABASE_URL: "postgresql://paperclip:paperclip@localhost:5432/paperclip"
|
||||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||||
@@ -50,43 +52,3 @@ services:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
|
||||||
certbot:
|
|
||||||
image: certbot/dns-cloudflare:latest
|
|
||||||
container_name: paperclip-certbot
|
|
||||||
volumes:
|
|
||||||
- {{ paperclip_certs_dir }}:/etc/letsencrypt
|
|
||||||
- {{ paperclip_base_path }}/certbot-init.sh:/certbot-init.sh:ro
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
environment:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
|
|
||||||
CERTBOT_EMAIL: ${CERTBOT_EMAIL}
|
|
||||||
PAPERCLIP_DOMAIN: ${PAPERCLIP_DOMAIN}
|
|
||||||
entrypoint: /bin/sh
|
|
||||||
command:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
/certbot-init.sh && while true; do certbot renew --quiet; sleep 12h; done
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
nginx:
|
|
||||||
image: nginx:latest
|
|
||||||
container_name: paperclip-nginx
|
|
||||||
network_mode: host
|
|
||||||
volumes:
|
|
||||||
- {{ paperclip_base_path }}/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
- {{ paperclip_certs_dir }}:/etc/nginx/certs:ro
|
|
||||||
depends_on:
|
|
||||||
- certbot
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:80/"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 30s
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
paperclip_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
user nginx;
|
|
||||||
worker_processes auto;
|
|
||||||
error_log /var/log/nginx/error.log warn;
|
|
||||||
pid /var/run/nginx.pid;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
client_max_body_size 100M;
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name {{ paperclip_domain }};
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTPS server
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name {{ paperclip_domain }};
|
|
||||||
|
|
||||||
# SSL certificates from certbot
|
|
||||||
ssl_certificate /etc/nginx/certs/live/{{ paperclip_domain }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/nginx/certs/live/{{ paperclip_domain }}/privkey.pem;
|
|
||||||
|
|
||||||
# SSL configuration
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_session_timeout 10m;
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:{{ paperclip_port }};
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $server_name;
|
|
||||||
proxy_set_header X-Forwarded-Port $server_port;
|
|
||||||
|
|
||||||
# WebSocket support
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
|
|
||||||
# Timeouts
|
|
||||||
proxy_connect_timeout 60s;
|
|
||||||
proxy_send_timeout 60s;
|
|
||||||
proxy_read_timeout 60s;
|
|
||||||
|
|
||||||
# Buffering
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_request_buffering off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Health check endpoint (no auth)
|
|
||||||
location /health {
|
|
||||||
proxy_pass http://127.0.0.1:{{ paperclip_port }}/health;
|
|
||||||
access_log off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"vllm": {
|
"vllm": {
|
||||||
"type": "api",
|
"type": "api",
|
||||||
"key": "{{ vault_vllm_api_key }}"
|
"key": "{{ vault_vllm_api_key }}"
|
||||||
}
|
},
|
||||||
"fantasmic": {
|
"fantasmic": {
|
||||||
"type": "api",
|
"type": "api",
|
||||||
"key": "{{ vault_fantasmic_api_key }}"
|
"key": "{{ vault_fantasmic_api_key }}"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"provider": {
|
"provider": {
|
||||||
"vllm": {
|
"illuminations": {
|
||||||
"npm": "@ai-sdk/openai-compatible",
|
"npm": "@ai-sdk/openai-compatible",
|
||||||
"name": "vLLM (Local)",
|
"name": "illuminations",
|
||||||
"models": {
|
"models": {
|
||||||
"gemma-4-26b": {
|
"gemma-4-26b": {
|
||||||
"id": "Gemma-4-26B-A4B-it-AWQ",
|
"id": "Gemma-4-26B-A4B-it-AWQ",
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"baseURL": "{{ vllm_endpoint }}/v1",
|
"baseURL": "{{ illuminations_endpoint }}/v1",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer {{ vault_vllm_api_key }}"
|
"Authorization": "Bearer {env:ILLUMINATIONS_API_KEY}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user