init
This commit is contained in:
commit
883f31932e
169 changed files with 5676 additions and 0 deletions
35
ansible/README.md
Normal file
35
ansible/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
Install collection dependencies:
|
||||
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
Run setup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/setup.yml
|
||||
|
||||
Run backup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/backup.yml
|
||||
|
||||
Run restore:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/restore.yml
|
||||
|
||||
Manual backup:
|
||||
|
||||
ansible-playbook -i inventories/prod/hosts.ini playbooks/backup.yml
|
||||
|
||||
Automated backups run via systemd timer (bi-weekly by default).
|
||||
|
||||
Vault variables (inventories/prod/group_vars/all/vault.yml):
|
||||
|
||||
vault_kellnr_admin_pwd: "..."
|
||||
vault_pg_password: "..."
|
||||
vault_secret_key: "random-long-django-secret"
|
||||
vault_restic_password: "..."
|
||||
vault_accounts_ssh_pubkey: "ssh-ed25519 ..."
|
||||
vault_accounts_ssh_private_key: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
...
|
||||
vault_rclone_proton_username: "user@proton.me"
|
||||
vault_rclone_proton_password: "rclone-obscured-password"
|
||||
vault_rclone_proton_2fa: "TOTP-SECRET"
|
||||
27
ansible/inventories/debug/group_vars/all/secrets.yml
Normal file
27
ansible/inventories/debug/group_vars/all/secrets.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
vault_kellnr_admin_pwd: debug
|
||||
vault_pg_password: debug
|
||||
vault_secret_key: debug
|
||||
s3_bucket: backups-testing
|
||||
vault_xmpp_admin_user: tiara
|
||||
vault_xmpp_oauth_client_id: xmpp-client-id
|
||||
vault_xmpp_oauth_client_secret: xmpp-client-secret
|
||||
vault_xmpp_ropc_client_id: xmpp-ropc-client-id
|
||||
vault_xmpp_ropc_client_secret: xmpp-ropc-client-secret
|
||||
vault_comentario_oauth_client_id: comentario-client-id
|
||||
vault_comentario_oauth_client_secret: comentario-client-secret
|
||||
vault_bugzilla_oauth_client_id: bugzilla-client-id
|
||||
vault_bugzilla_oauth_client_secret: bugzilla-client-secret
|
||||
vault_bugzilla_oidc_passphrase: bugzilla-oidc-passphrase
|
||||
vault_bugzilla_db_password: debug
|
||||
vault_bugzilla_admin_pwd: debugdebug
|
||||
vault_social_google_client_id: google-client-id-placeholder
|
||||
vault_social_google_client_secret: google-client-secret-placeholder
|
||||
vault_social_microsoft_client_id: microsoft-client-id-placeholder
|
||||
vault_social_microsoft_client_secret: microsoft-client-secret-placeholder
|
||||
vault_social_apple_client_id: apple-client-id-placeholder
|
||||
vault_social_apple_client_secret: apple-client-secret-placeholder
|
||||
vault_social_facebook_client_id: facebook-client-id-placeholder
|
||||
vault_social_facebook_client_secret: facebook-client-secret-placeholder
|
||||
vault_social_twitter_client_id: twitter-client-id-placeholder
|
||||
vault_social_twitter_client_secret: twitter-client-secret-placeholder
|
||||
54
ansible/inventories/debug/group_vars/all/vault.yml
Normal file
54
ansible/inventories/debug/group_vars/all/vault.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
34646335663961323835326462363434393133316464393063356666396362626637306263626332
|
||||
3137613332313531333364643331373934363237363231640a393362313738383035373131633537
|
||||
30623432363962313538323131323836613539643533623364666631663733353134316461373930
|
||||
6364393465613665620a336261336630346630646530653135336163653136336532333130333338
|
||||
64626264353737363235353865303039656339653062633335623964643939326364393263393832
|
||||
36663030646363636130353164313063356133396635643334653561343462333662656237633766
|
||||
39646363646161303165316439646164613530636266373761396135663830383932663837383661
|
||||
62636334383861396133353839616239316366303437383934333265343063366139613564346131
|
||||
39656434653662316534386263383361333131383037313163326464353963643239343661663932
|
||||
37303065366463653761623062326439633363663030616432346538373461353830353338623230
|
||||
64306261326135363566656232313330353037613164616264656431363439326331613837376662
|
||||
36646134366463383665393136616437393339346134363638666431363933636637366632366439
|
||||
39656138366234646138353131313166656431303463316332663138303330326135666261363533
|
||||
34393833626239323036663035646239316266323434366435323931366532623639653837373261
|
||||
37633333663233646332393239613264363562343164653033303865313233393432306539646536
|
||||
39643264623862393833653362323436396661353065306233646234306235306366666139666130
|
||||
61643364613133326461383331366664363531643963376137653561376532393736393633313831
|
||||
31303861373037363766316664623234633563386338353431396663383034343061643962366535
|
||||
31653833393765663961613338336334366162656466663863306662383838386664326631336434
|
||||
65366563333533396434333861333937333466313861626661363061303939373538666130326238
|
||||
62363130633566336163653238356636623066306662326536366463613532626536633564353430
|
||||
64303165353436626266636131333864666336386535663832313564313533333163633639393038
|
||||
64633236333236616535396634653162396464363337393163623262653866666664303534376164
|
||||
35303066626665386535656537666432636132656639646431663463363630333466316534303739
|
||||
30396466356131616666626130383963333166396339623861303662333535343239643364633535
|
||||
37656466363535363163643036653464336538623334633565346163343630663463633235383831
|
||||
66306462396163313634666130666264336138643163643563663635333362623262373330643766
|
||||
33626633303062656462313862393763616163363231323036306638633536313230656261303835
|
||||
35333738396430356139346331643839386236343464323137663964336561633766353262353863
|
||||
32313635323632643065343139366436373864636465376538376365333835366232613032333230
|
||||
35376134343861373333346537623838303165366639653063303364303638626431316535656466
|
||||
38353261393262306433383035353261363265653838653661636463636638636465383665383763
|
||||
34303238323838313364373137613566663737323938306365376565353066656162656135376363
|
||||
39326366623830616330336163363738326263663832313337323164656331636334333937383737
|
||||
61653336613033363766636435353365356265373263656165326162316130333134336230353637
|
||||
36656364646336336163333338663966613934356266313865643934373566343964363062326366
|
||||
38646562373261313638383338636139333835626239346363336331663536306431636463336630
|
||||
38656236366436316234393236646234613264353264333030626362343463333863373065343336
|
||||
63663135363365316265636464663864313765663763613131613030636133643637336364316234
|
||||
62393061346337383563313866303463633362356263356238323336663262646532343136393439
|
||||
64333362383461366230663435396164393031616432323537396237383634353363393332376536
|
||||
39646631386365646266366530623762616264663164346533323239643863363036663565323438
|
||||
63663537306133363337363962646462323131353065313537626432633534653432393639626337
|
||||
66393234313835613163663030363332336361316365616238643964333363386134663232323766
|
||||
33306165626431373730373135306431373937613235333861636266666662336431356265623638
|
||||
38623065623933646237326339353932353439383932663631346131316431316234323537353062
|
||||
37353632333961386334646232393930666234373432653463613931643537623536656364626632
|
||||
61393063636333663961336562326330653461393562313064626566626436383765356264633838
|
||||
39356232626362323935626362316563363430383134636230373665613930336462383363393831
|
||||
37356261663238303337383833353930383736386434666539333939616434626532393831643866
|
||||
35386137663632383639376538386435373065333138643064383136656136373634323839636366
|
||||
31356561396137326134643562396264303561363639636637313362616438646231663162656332
|
||||
3739
|
||||
4
ansible/inventories/debug/group_vars/idp/firewall.yml
Normal file
4
ansible/inventories/debug/group_vars/idp/firewall.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "9000", proto: tcp, from: "10.0.0.0/24" }
|
||||
9
ansible/inventories/debug/group_vars/proxy/firewall.yml
Normal file
9
ansible/inventories/debug/group_vars/proxy/firewall.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "80" }
|
||||
- { port: "443" }
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "5000", proto: tcp }
|
||||
- { port: "5222", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "5269", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "53", proto: udp, from: "10.0.0.0/24" }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
wg_client_peers:
|
||||
-
|
||||
name: "Ansible controller"
|
||||
public_key: "{{ lookup('file', '/etc/wireguard/public.key') }}"
|
||||
allowed_ips: "10.0.0.3/32"
|
||||
13
ansible/inventories/debug/hosts.ini
Normal file
13
ansible/inventories/debug/hosts.ini
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[proxy]
|
||||
vm-proxy ansible_host=10.10.0.2 ansible_user=debian wg_endpoint=10.10.0.2
|
||||
|
||||
[idp]
|
||||
vm-idp ansible_host=10.10.0.3 ansible_user=debian
|
||||
|
||||
[docker_hosts:children]
|
||||
proxy
|
||||
idp
|
||||
|
||||
[wg_peers:children]
|
||||
proxy
|
||||
idp
|
||||
126
ansible/inventories/prod/group_vars/all/vault.yml
Normal file
126
ansible/inventories/prod/group_vars/all/vault.yml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
65656630393666376132613061363366363432303437313961303434323431333832636331633535
|
||||
3364623638626435326236656563646435396661353939610a366337363936633038623065323134
|
||||
63353766653532623164353764353761363633633366633538633566323465303835323831656238
|
||||
3565353965646338630a343232386563313764313465383666356136326336336230303432346538
|
||||
38316130663339386235343265616237373536613762626338376564666432366464323436353639
|
||||
36326137373232316438383837633830366163633437643565313863613137323033353430356638
|
||||
33663736623032323263353831343430313734373065393232663065643432333864323563663265
|
||||
33386465326335373635333435623764346666353765336630643764373737306637343834656639
|
||||
35653466366666383332663464626463646235303737306166373832396531623038376532656665
|
||||
65663964373565303538343732393137396361613330613336326564613633363662306232643534
|
||||
37393939323563343332613833613339313630396434663235656232643631303031393735396263
|
||||
63613863366162383637363736393430643233326533383534623539646330313737383332396165
|
||||
32393134323739363866386633646563393036373235353332333062316639653362626261376434
|
||||
31366437383465616534653465386636376163653430663764613236366134336435636631623963
|
||||
37346430393933306331636633333636666239333962323137393466356265626363376361393435
|
||||
30656432353333646337346662653637303430313739353431666237373037666534316566666564
|
||||
65663838643461316566616532343461343138323637356231393730393062363164633537363539
|
||||
36623035386636376239306633303766616636333935336464323636626437393466356139393164
|
||||
32373039326138346465383736616433343234303762343161313661363563353864663538316662
|
||||
30613432356438663561626137316636623431656432346238303735313865326230633231633636
|
||||
33666434343038656266323866663634653063353864366163653737363938336235316565306635
|
||||
35646137313936303738623630356333623261303839346632363562646263616130643734353066
|
||||
61663761326264313732396166303362313231333166346231306165346630636331666262373631
|
||||
61636137356430633566323732303233656635643639353539626238613761386532336136663535
|
||||
32356561613635633230303733303836336635396338666134303464376437316163633238373365
|
||||
34363230383933613136343761326563633034306539623866376661336539303535336565393962
|
||||
62613138623334376134373566623034313365616238353864616265383533363037363463343835
|
||||
32623334663832333630326565633833373936376633343937363633363533633162646164393537
|
||||
35663765626362366234626432663064346231386265376132396335353130336432356435336337
|
||||
37636136303631366333316636313733376137343961393062613762333962616263373233633366
|
||||
30383236386462393732373464333363376638663465363634396238376564326363383432386438
|
||||
36363638383437643233393436383539333162373730633533666533343864346432623130626237
|
||||
33643436653732333532666333613038316539626536366265386332623563353133363663663530
|
||||
31643131653566323935393932353537343533343131666239663864653232633331363961663031
|
||||
37346664383031646264316132356433616631373936376435613666653139313630316665636237
|
||||
61626137653765623638393132303436366364353162333036353138376535626635343731666432
|
||||
34663032643936393732373966363534303435313234323334393964313235333263373136313962
|
||||
35386539393238326132346134346333626437353464333538623966363965663237353331363438
|
||||
34336630343930633363663062316639336630396164653236303538383537333062636535366461
|
||||
36393534336462633062316535376665303666643538303630653234396533323463323131303365
|
||||
38336636666238316332646162323164343436303264396335646466643165623937363934363734
|
||||
33366532396332393231633366353632363561663662633362343838653431356633633462393738
|
||||
62333236613831316334343837333465303535383036353933396436346235653162326266663934
|
||||
36633434633630303234333336633834623465613433333833666430353538663332396366316237
|
||||
39656638303763633535393932326132346266666635353462623132306331626266303336396362
|
||||
62346564636365353031366264656534376665366664386636633136373738323038663132643034
|
||||
39303261313333333465656666636435323338336335313166343431396238643738633534366364
|
||||
34383733383366626265313936353039613630626237636439343930393831323238333161656637
|
||||
34303763386635373237393463623838303738383434306639303161336337313831653862346535
|
||||
32363237333932343633653165663138393761656365363632393539356665613837343663396438
|
||||
62313333306262303435386330616233313230323338666537346265343562343065326362643461
|
||||
37373330386531646465393039656165333032383735643566366465366130313263653930336434
|
||||
32346234626561373236333934633130366134626239323963346536626339336139326536346238
|
||||
38396663323837393330343633313939656430343366626637396564653036303830633065396336
|
||||
61383234653130623933666262353339363737633230636239323339643032323264306636343161
|
||||
62636133656364303231666535393764633035393363313732306331663131393331366436373764
|
||||
32386134303532653934306239323362306637396263363361386638343631633761653534643061
|
||||
66613761336333643062306635313834333464343936333438636664666561393237303139366666
|
||||
30653263623738383839613564623635336236356630353031323636333631373339376366323834
|
||||
63393839656535313966643335636135336366336135376561303661633332363338363266646364
|
||||
30346536323835326165663336326337336431376337356161613566356562326334333566343963
|
||||
34316433343231343735313236313432323732616363653634323262393235333063386234376135
|
||||
62616536383339333461393462313365633834326638656433383032336639633861363466636462
|
||||
33643066363631633438363432316366313836383864663266633336373865633733353933323531
|
||||
61373531336130303834643739336237343966626239613731373034313232343764613637356564
|
||||
66366330346431663031613535633435356239376361663936646334383466373261316531653762
|
||||
63306138306334616333373139663432326366646534396331613265313465646338323063653735
|
||||
31653739616534303138363465653966613436613634616166323665613538363263346334336338
|
||||
34623235636630363332343566616336323636646235663132373633313230623639623962643466
|
||||
39356137313239653431383339646135616636616437623830353639616634306534663636386232
|
||||
39363864303733363164313130666536366664303733366132663439656333626562343063386336
|
||||
33396261663030333332333139353163623133663134363736326538653763373730326563313937
|
||||
62353434356232376630306538633731626435613639303165616535316164336637343362353465
|
||||
39633031366231633032313162336437336362626131613363353131663532646266306636396138
|
||||
62333939383065653866356633663834373265646366353261653661353465613766633638333836
|
||||
66643335396563316166393535623734303730346138386434323034643735313137313362306636
|
||||
31386565383032663937323334346462656436376633306638323562376238363339323530353230
|
||||
38353537333232306536386238653765393061646432646162626438636365323564363363316362
|
||||
64646662633036316530326437366530353234346231303865306665343061613134613338336664
|
||||
64313237643137633361643466313938623137366561376532646133366261633637623837653233
|
||||
33306263666665636136633531393866323232356562656135653464333836363830323634343338
|
||||
63633836386565336563646131333861666332336261393034326337323836393035346262663631
|
||||
64666635623362626235373433613132303037396136663630353334353938656434656337386134
|
||||
31666364626335626639396334396162326138626631353635323364383364616162353735303336
|
||||
38306330666538326130316635323562393963343635663666333038383564323832646239323062
|
||||
66356363323664663637656332363461363430393938313930316364623766303437623865633461
|
||||
31663133626235626265323661353531386232323836353737313138353330653936306339343235
|
||||
38663563653439626435373361383530646262656238306239643136373161396134336233366566
|
||||
38663863616139383738303061383431313763323336353463636361613132616363326133323339
|
||||
37363562363633393664643334386266366337353464336333336363373161663034303835396262
|
||||
61623233666137343963613161643164333336323130353538636565363735376561356561313133
|
||||
63333033336666393538396534353463393331363033353631393162653132396532623864333536
|
||||
61656231353437356532353066663261633861616530313332343263643635613034393563373961
|
||||
64663631383736613835376633633735626466636661353765303765643038623039303733636264
|
||||
66353133313464316633626661663665646264666133356133383435646563346332383865306235
|
||||
64353265393463326633646434633264303734626532663935326665363632393435333565306635
|
||||
35666464643364313039363264333261623365373330316637653038616639643637663437366663
|
||||
39643134613465376162396238376562643930323632643465393038333432663062383434626432
|
||||
34366665663761316564656436326331373165626237383839343736373063383765383032383762
|
||||
65303433353535393665323638666239336538326239363263646534653239333039643034656362
|
||||
35353062636334353264353964303938636563306631356664303965306331613134326264653730
|
||||
33613061633931663066616139646663343334323763343630636432353931313265303931343833
|
||||
63303831333032383839633263626539653139313561626432393731623932383733666464313061
|
||||
39393537393563343238306438313032353134383630386436643534306266393364303966393361
|
||||
32333538383863353031373735353932633633613538316261396164653230336463666165313965
|
||||
35653533393662393230343736303466643737353437333966653966613861646361303637323933
|
||||
34393365643235373762323631633930316538333835303230613265366265393938646432613964
|
||||
62323637326166303936303663313038393133316336653462313931663335316230306536356165
|
||||
62346432366234623232353666313131613330636436303830363261323465363164366637353238
|
||||
66656463353633343133326531333131396438663964623861323037623162303666613565663161
|
||||
66376531373739396463653461356131643131623561663633343331346237393063303932306239
|
||||
37363533323632323336373934323237656338623962323539376230616139353463336631306264
|
||||
38383233313261333466613462356234653530356535633439646432303862316236613834393765
|
||||
38373263636338343030366161316464656362653433353137323532356438643238373665346336
|
||||
63343230633163633066623936343661656565356533333433613838353937613233326464386462
|
||||
61613261333739376630363036656564363363616665313639313463323239666636396366303663
|
||||
38393466613561663436653736393431633530346263363964393961613833376235383664313331
|
||||
65363233326165643533373139663233636538653937313835316263633834326139386230643338
|
||||
66663133373739623236633338663435663838376633333534633337366135343438663365626533
|
||||
63393437613637383033363237633363633531356535366339386334363535643364356564313765
|
||||
33343039303035303739366239643333326461653736353362373133663331306362616339366236
|
||||
38636237366230313332336437376539333233626361333138663164383334336138643333363030
|
||||
63623166366163656431666233323564386236306165623162386639316131633939356136666336
|
||||
33376366353866343033373264626334303334383766623564313262303135666232313765653536
|
||||
34653430363436303237373833363765383963613865323566633436653266313036
|
||||
4
ansible/inventories/prod/group_vars/idp/firewall.yml
Normal file
4
ansible/inventories/prod/group_vars/idp/firewall.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "9000", proto: tcp, from: "10.0.0.0/24" }
|
||||
9
ansible/inventories/prod/group_vars/proxy/firewall.yml
Normal file
9
ansible/inventories/prod/group_vars/proxy/firewall.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
ufw_allow:
|
||||
- { port: "80" }
|
||||
- { port: "443" }
|
||||
- { port: "51820", proto: udp }
|
||||
- { port: "5000", proto: tcp }
|
||||
- { port: "5222", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "5269", proto: tcp, from: "10.0.0.0/24" }
|
||||
- { port: "53", proto: udp, from: "10.0.0.0/24" }
|
||||
10
ansible/inventories/prod/group_vars/wg_peers/wireguard.yml
Normal file
10
ansible/inventories/prod/group_vars/wg_peers/wireguard.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
wg_client_peers:
|
||||
-
|
||||
name: "Tiara's Pixel 8 Pro"
|
||||
public_key: "0bMudTNuiRCaOdFKvWy+N6X2czYWt8nKe7OIiFS5LEY="
|
||||
allowed_ips: "10.0.0.3/32"
|
||||
-
|
||||
name: "Tiara's Workstation"
|
||||
public_key: "8Cwcyqu3Xo0th6Lkk5arcG7MdgwEejocYrA/+RRbrgA="
|
||||
allowed_ips: "10.0.0.4/32"
|
||||
13
ansible/inventories/prod/hosts.ini
Normal file
13
ansible/inventories/prod/hosts.ini
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[proxy]
|
||||
hwsrv-953720.hostwindsdns.com ansible_user=tiara wg_endpoint=tiararodney.com
|
||||
|
||||
[idp]
|
||||
hwsrv-1317920.hostwindsdns.com ansible_user=tiara
|
||||
|
||||
[docker_hosts:children]
|
||||
proxy
|
||||
idp
|
||||
|
||||
[wg_peers:children]
|
||||
proxy
|
||||
idp
|
||||
20
ansible/playbooks/backup.yml
Normal file
20
ansible/playbooks/backup.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger backup
|
||||
systemd:
|
||||
name: restic-backup.service
|
||||
state: started
|
||||
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger backup
|
||||
systemd:
|
||||
name: restic-backup.service
|
||||
state: started
|
||||
28
ansible/playbooks/restore.yml
Normal file
28
ansible/playbooks/restore.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: restic, tasks_from: restore-restic }
|
||||
vars:
|
||||
host_id: proxy
|
||||
-
|
||||
include_role: { name: kellnr, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: devpi, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: prosody, tasks_from: restore }
|
||||
-
|
||||
include_role: { name: comentario, tasks_from: restore }
|
||||
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: restic, tasks_from: restore-restic }
|
||||
vars:
|
||||
host_id: idp
|
||||
-
|
||||
include_role: { name: authentik, tasks_from: restore-authentik }
|
||||
425
ansible/playbooks/setup.yml
Normal file
425
ansible/playbooks/setup.yml
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
---
|
||||
-
|
||||
hosts: all
|
||||
become: yes
|
||||
tags: [host]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: host }
|
||||
vars:
|
||||
ssh_pubkey_dir: "{{ playbook_dir }}/../../.ssh"
|
||||
-
|
||||
hosts: docker_hosts
|
||||
become: yes
|
||||
tags: [docker]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: docker }
|
||||
vars:
|
||||
registry_mirror_ip: "10.0.0.1"
|
||||
registry_mirrors:
|
||||
-
|
||||
upstream: docker.io
|
||||
mirror: "https://dockerhub.oci.code.tiararodney.com"
|
||||
-
|
||||
upstream: ghcr.io
|
||||
mirror: "https://ghcr.oci.code.tiararodney.com"
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
apply: { tags: [restic] }
|
||||
tags: [restic]
|
||||
vars:
|
||||
host_id: proxy
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
apply: { tags: [restic] }
|
||||
tags: [restic]
|
||||
vars:
|
||||
host_id: idp
|
||||
-
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: [letsencrypt, apache]
|
||||
tasks:
|
||||
-
|
||||
name: Create letsencrypt certificate archive
|
||||
command:
|
||||
cmd: "tar czf /tmp/letsencrypt.tar.gz --dereference -C {{ (playbook_dir + '/../../letsencrypt') | realpath }} ."
|
||||
creates: /tmp/letsencrypt.tar.gz
|
||||
-
|
||||
hosts: wg_peers
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
include_role: { name: wireguard }
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: generate-keys }
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
name: Build WireGuard peer list
|
||||
set_fact:
|
||||
wg_peers:
|
||||
-
|
||||
public_key: "{{ hostvars[groups['idp'][0]]['wg_public_key'] }}"
|
||||
allowed_ips: "10.0.0.2/32"
|
||||
when: groups['idp'][0] in hostvars and 'wg_public_key' in hostvars[groups['idp'][0]]
|
||||
-
|
||||
name: Append client peers
|
||||
set_fact:
|
||||
wg_peers: "{{ wg_peers + wg_client_peers }}"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: deploy-wireguard }
|
||||
vars:
|
||||
wg_address: "10.0.0.1/24"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
name: Display proxy WireGuard public key
|
||||
debug:
|
||||
msg: "Proxy WG public key: {{ wg_public_key }}"
|
||||
when: wg_public_key is defined
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tags: [wireguard]
|
||||
tasks:
|
||||
-
|
||||
name: Build WireGuard peer list
|
||||
set_fact:
|
||||
wg_peers:
|
||||
-
|
||||
public_key: "{{ hostvars[groups['proxy'][0]]['wg_public_key'] }}"
|
||||
allowed_ips: "10.0.0.1/32"
|
||||
endpoint: "{{ hostvars[groups['proxy'][0]]['wg_endpoint'] }}:51820"
|
||||
persistent_keepalive: true
|
||||
when: groups['proxy'][0] in hostvars and 'wg_public_key' in hostvars[groups['proxy'][0]]
|
||||
-
|
||||
name: Append client peers
|
||||
set_fact:
|
||||
wg_peers: "{{ wg_peers + wg_client_peers }}"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
include_role: { name: wireguard, tasks_from: deploy-wireguard }
|
||||
vars:
|
||||
wg_address: "10.0.0.2/24"
|
||||
when: wg_peers is defined
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
vars:
|
||||
chat_domain: chat.tiararodney.com
|
||||
authentik_url: https://accounts.tiararodney.com
|
||||
authentik_internal_url: http://10.0.0.2:9000
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: host
|
||||
tasks_from: setup-swap
|
||||
apply: { tags: [host, swap] }
|
||||
tags: [host, swap]
|
||||
-
|
||||
name: Ensure accounts.tiararodney.com resolves to localhost for mod_auth_openidc
|
||||
tags: [apache, bugzilla]
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: "accounts\\.tiararodney\\.com"
|
||||
line: "127.0.0.1 accounts.tiararodney.com"
|
||||
-
|
||||
include_role:
|
||||
name: dnsmasq
|
||||
apply: { tags: [dnsmasq] }
|
||||
tags: [dnsmasq]
|
||||
vars:
|
||||
dns_records:
|
||||
- { domain: tiararodney.com, ip: "10.0.0.1" }
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
apply: { tags: [apache] }
|
||||
tags: [apache]
|
||||
vars:
|
||||
letsencrypt_archive: /tmp/letsencrypt.tar.gz
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
apply: { tags: [docker-registry] }
|
||||
tags: [docker-registry]
|
||||
vars:
|
||||
hostname: dockerhub.oci.code.tiararodney.com
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
apply: { tags: [docker-registry] }
|
||||
tags: [docker-registry]
|
||||
vars:
|
||||
hostname: ghcr.oci.code.tiararodney.com
|
||||
install_dir: /opt/docker-registry-ghcr
|
||||
port: 5051
|
||||
remote_url: "https://ghcr.io"
|
||||
-
|
||||
include_role:
|
||||
name: restic
|
||||
tasks_from: restore-restic
|
||||
apply: { tags: [registry-restore, never] }
|
||||
tags: [registry-restore, never]
|
||||
vars:
|
||||
host_id: proxy
|
||||
restore_include: /var/backups/docker-registry
|
||||
-
|
||||
include_role:
|
||||
name: docker_registry
|
||||
tasks_from: restore-registry
|
||||
apply: { tags: [registry-restore, never] }
|
||||
tags: [registry-restore, never]
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-static-site
|
||||
apply: { tags: [blog] }
|
||||
tags: [blog]
|
||||
vars:
|
||||
name: blog
|
||||
server_name: blog.tiararodney.com
|
||||
document_root: /var/www/blog.tiararodney.com
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-static-site
|
||||
apply: { tags: [spec] }
|
||||
tags: [spec]
|
||||
vars:
|
||||
name: spec
|
||||
server_name: specs.code.tiararodney.com
|
||||
document_root: /var/www/specs.code.tiararodney.com
|
||||
directory_index: "README.html README.md README.txt"
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
-
|
||||
include_role:
|
||||
name: kellnr
|
||||
apply: { tags: [kellnr] }
|
||||
tags: [kellnr]
|
||||
vars:
|
||||
version: "6.0.0-rc.2"
|
||||
hostname: crates.code.tiararodney.com
|
||||
admin_pwd: "{{ vault_kellnr_admin_pwd }}"
|
||||
-
|
||||
include_role:
|
||||
name: devpi
|
||||
apply: { tags: [devpi] }
|
||||
tags: [devpi]
|
||||
vars:
|
||||
hostname: pypi.code.tiararodney.com
|
||||
-
|
||||
include_role:
|
||||
name: prosody
|
||||
apply: { tags: [prosody] }
|
||||
tags: [prosody]
|
||||
vars:
|
||||
version: "13.0"
|
||||
domain: "{{ chat_domain }}"
|
||||
admin_jid: "{{ vault_xmpp_admin_user }}@{{ chat_domain }}"
|
||||
bind_address: "10.0.0.1"
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
oauth_client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
oauth_userinfo_url: "{{ authentik_internal_url }}/application/o/userinfo/"
|
||||
oauth_ropc_client_id: "{{ vault_xmpp_ropc_client_id }}"
|
||||
oauth_ropc_client_secret: "{{ vault_xmpp_ropc_client_secret }}"
|
||||
oauth_token_url: "{{ authentik_internal_url }}/application/o/token/"
|
||||
session_timeout: 1800
|
||||
smtp_host: "{{ vault_prosody_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_prosody_smtp_username }}"
|
||||
smtp_password: "{{ vault_prosody_smtp_password }}"
|
||||
default_contacts:
|
||||
-
|
||||
jid: "{{ vault_xmpp_admin_user }}@{{ chat_domain }}"
|
||||
name: Tiara
|
||||
-
|
||||
include_role:
|
||||
name: conversejs
|
||||
apply: { tags: [conversejs] }
|
||||
tags: [conversejs]
|
||||
vars:
|
||||
version: "12.0.0"
|
||||
domain: "{{ chat_domain }}"
|
||||
oauth_client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
oauth_authorize_url: "{{ authentik_url }}/application/o/authorize/"
|
||||
oauth_token_url: "{{ authentik_url }}/application/o/token/"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
apply: { tags: [prosody, xmpp-upload] }
|
||||
tags: [prosody, xmpp-upload]
|
||||
vars:
|
||||
vhost_name: xmpp-upload
|
||||
server_name: "upload.{{ chat_domain }}"
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
backend_port: 5280
|
||||
-
|
||||
include_role:
|
||||
name: comentario
|
||||
apply: { tags: [comentario] }
|
||||
tags: [comentario]
|
||||
vars:
|
||||
version: "latest"
|
||||
domain: comments.tiararodney.com
|
||||
oauth_issuer_url: "{{ authentik_url }}/application/o/comentario"
|
||||
oauth_client_id: "{{ vault_comentario_oauth_client_id }}"
|
||||
oauth_client_secret: "{{ vault_comentario_oauth_client_secret }}"
|
||||
smtp_host: "{{ vault_comentario_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_comentario_smtp_username }}"
|
||||
smtp_password: "{{ vault_comentario_smtp_password }}"
|
||||
-
|
||||
include_role:
|
||||
name: bugzilla
|
||||
apply: { tags: [bugzilla] }
|
||||
tags: [bugzilla]
|
||||
vars:
|
||||
version: "5.0.4.1"
|
||||
domain: bugs.code.tiararodney.com
|
||||
db_password: "{{ vault_bugzilla_db_password }}"
|
||||
admin_email: "me@tiararodney.com"
|
||||
admin_pwd: "{{ vault_bugzilla_admin_pwd }}"
|
||||
oauth_issuer_url: "{{ authentik_url }}/application/o/bugs"
|
||||
oauth_authorize_url: "{{ authentik_url }}/application/o/authorize/"
|
||||
oauth_token_url: "{{ authentik_url }}/application/o/token/"
|
||||
oauth_userinfo_url: "{{ authentik_url }}/application/o/userinfo/"
|
||||
oauth_jwks_url: "{{ authentik_url }}/application/o/bugs/jwks/"
|
||||
oauth_client_id: "{{ vault_bugzilla_oauth_client_id }}"
|
||||
oauth_client_secret: "{{ vault_bugzilla_oauth_client_secret }}"
|
||||
oauth_crypto_passphrase: "{{ vault_bugzilla_oidc_passphrase }}"
|
||||
smtp_host: "{{ vault_bugzilla_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_bugzilla_smtp_username }}"
|
||||
smtp_password: "{{ vault_bugzilla_smtp_password }}"
|
||||
-
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
apply: { tags: [authentik] }
|
||||
tags: [authentik]
|
||||
vars:
|
||||
vhost_name: accounts
|
||||
server_name: accounts.tiararodney.com
|
||||
ssl_cert: "{{ ssl_cert_tiararodney }}"
|
||||
ssl_key: "{{ ssl_key_tiararodney }}"
|
||||
backend_host: "10.0.0.2"
|
||||
backend_port: 9000
|
||||
websocket: true
|
||||
restricted_locations:
|
||||
-
|
||||
path: "/if/admin/"
|
||||
allowed_ips: ["10.0.0.0/24"]
|
||||
-
|
||||
hosts: idp
|
||||
become: yes
|
||||
tags: [authentik]
|
||||
tasks:
|
||||
-
|
||||
include_role:
|
||||
name: host
|
||||
tasks_from: setup-zram
|
||||
apply: { tags: [host, swap, zram] }
|
||||
tags: [host, swap, zram]
|
||||
-
|
||||
include_role: { name: authentik }
|
||||
vars:
|
||||
version: "2026.2.1"
|
||||
domain: "accounts.tiararodney.com"
|
||||
pg_password: "{{ vault_pg_password }}"
|
||||
secret_key: "{{ vault_secret_key }}"
|
||||
bind_address: "10.0.0.2"
|
||||
smtp_host: "{{ vault_authentik_smtp_hostname }}"
|
||||
smtp_username: "{{ vault_authentik_smtp_username }}"
|
||||
smtp_password: "{{ vault_authentik_smtp_password }}"
|
||||
oauth_applications:
|
||||
-
|
||||
name: Chat
|
||||
slug: chat
|
||||
client_type: public
|
||||
client_id: "{{ vault_xmpp_oauth_client_id }}"
|
||||
redirect_uris:
|
||||
- "https://chat.tiararodney.com/"
|
||||
-
|
||||
name: Chat XMPP
|
||||
slug: chat-xmpp
|
||||
client_id: "{{ vault_xmpp_ropc_client_id }}"
|
||||
client_secret: "{{ vault_xmpp_ropc_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://chat.tiararodney.com/"
|
||||
-
|
||||
name: Comments
|
||||
slug: comments
|
||||
client_id: "{{ vault_comentario_oauth_client_id }}"
|
||||
client_secret: "{{ vault_comentario_oauth_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://comments.tiararodney.com/api/oauth/oidc/callback/authentik"
|
||||
-
|
||||
name: Bugs
|
||||
slug: bugs
|
||||
client_id: "{{ vault_bugzilla_oauth_client_id }}"
|
||||
client_secret: "{{ vault_bugzilla_oauth_client_secret }}"
|
||||
redirect_uris:
|
||||
- "https://bugs.code.tiararodney.com/oidc-callback"
|
||||
social_login_sources:
|
||||
-
|
||||
name: Google Account
|
||||
slug: google
|
||||
provider_type: google
|
||||
client_id: "{{ vault_social_google_client_id }}"
|
||||
client_secret: "{{ vault_social_google_client_secret }}"
|
||||
-
|
||||
name: Microsoft Account
|
||||
slug: microsoft
|
||||
provider_type: entraid
|
||||
client_id: "{{ vault_social_microsoft_client_id }}"
|
||||
client_secret: "{{ vault_social_microsoft_client_secret }}"
|
||||
-
|
||||
name: Apple ID
|
||||
slug: apple
|
||||
provider_type: apple
|
||||
client_id: "{{ vault_social_apple_client_id }}"
|
||||
client_secret: "{{ vault_social_apple_client_secret }}"
|
||||
-
|
||||
name: Facebook Account
|
||||
slug: facebook
|
||||
provider_type: facebook
|
||||
client_id: "{{ vault_social_facebook_client_id }}"
|
||||
client_secret: "{{ vault_social_facebook_client_secret }}"
|
||||
-
|
||||
name: X (formerly Twitter) Account
|
||||
slug: twitter
|
||||
provider_type: twitter
|
||||
client_id: "{{ vault_social_twitter_client_id }}"
|
||||
client_secret: "{{ vault_social_twitter_client_secret }}"
|
||||
-
|
||||
hosts: proxy
|
||||
become: yes
|
||||
tasks:
|
||||
-
|
||||
name: Trigger registry backups
|
||||
tags: [registry-backup, never]
|
||||
command: "{{ item }}"
|
||||
loop:
|
||||
- /etc/restic/pre-backup.d/docker-registry.sh
|
||||
- /etc/restic/pre-backup.d/docker-registry-ghcr.sh
|
||||
4
ansible/requirements.yml
Normal file
4
ansible/requirements.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
collections:
|
||||
- name: community.docker
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
5
ansible/roles/apache/defaults/main.yml
Normal file
5
ansible/roles/apache/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
ssl_cert_tiararodney: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key_tiararodney: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
ssl_cert_administratrix: /etc/letsencrypt/live/administratrix.io/fullchain.pem
|
||||
ssl_key_administratrix: /etc/letsencrypt/live/administratrix.io/privkey.pem
|
||||
6
ansible/roles/apache/handlers/main.yml
Normal file
6
ansible/roles/apache/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
-
|
||||
name: reload apache
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: reloaded
|
||||
2
ansible/roles/apache/meta/main.yml
Normal file
2
ansible/roles/apache/meta/main.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
dependencies: []
|
||||
18
ansible/roles/apache/tasks/deploy-reverse-proxy.yml
Normal file
18
ansible/roles/apache/tasks/deploy-reverse-proxy.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
-
|
||||
name: Ensure Apache is installed
|
||||
include_tasks: main.yml
|
||||
|
||||
-
|
||||
name: "Deploy {{ vhost_name }} reverse proxy vhost"
|
||||
template:
|
||||
src: reverse-proxy-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/{{ vhost_name }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: "Enable {{ vhost_name }} site"
|
||||
command: "{{ apache_enable_site_cmd }} {{ vhost_name }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/{{ vhost_name }}.conf"
|
||||
notify: reload apache
|
||||
27
ansible/roles/apache/tasks/deploy-static-site.yml
Normal file
27
ansible/roles/apache/tasks/deploy-static-site.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
-
|
||||
name: Ensure Apache is installed
|
||||
include_tasks: main.yml
|
||||
|
||||
-
|
||||
name: Ensure document root exists
|
||||
file:
|
||||
path: "{{ document_root }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy vhost configuration
|
||||
template:
|
||||
src: static-site-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/{{ name }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable site
|
||||
command: "{{ apache_enable_site_cmd }} {{ name }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/{{ name }}.conf"
|
||||
notify: reload apache
|
||||
83
ansible/roles/apache/tasks/install-apache.yml
Normal file
83
ansible/roles/apache/tasks/install-apache.yml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
-
|
||||
name: Ensure letsencrypt directory exists
|
||||
file:
|
||||
path: /etc/letsencrypt
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
-
|
||||
name: Deploy SSL certificates
|
||||
unarchive:
|
||||
src: "{{ letsencrypt_archive }}"
|
||||
dest: /etc/letsencrypt/
|
||||
when: letsencrypt_archive is defined
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure SSL private keys are readable by containers
|
||||
shell: find /etc/letsencrypt -name 'privkey*.pem' -exec chmod 644 {} +
|
||||
changed_when: false
|
||||
when: letsencrypt_archive is defined
|
||||
|
||||
-
|
||||
name: Install Apache
|
||||
apt:
|
||||
name: "{{ apache_package }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Enable Apache modules
|
||||
community.general.apache2_module:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- proxy
|
||||
- proxy_http
|
||||
- proxy_wstunnel
|
||||
- ssl
|
||||
- rewrite
|
||||
- headers
|
||||
- auth_basic
|
||||
- autoindex
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Disable default site
|
||||
command: "{{ apache_disable_site_cmd }} 000-default"
|
||||
args:
|
||||
removes: "{{ apache_sites_enabled }}/000-default.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure tiararodney.com document root exists
|
||||
file:
|
||||
path: /var/www/tiararodney.com
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy tiararodney.com vhost
|
||||
template:
|
||||
src: 000-default-redirect.conf.j2
|
||||
dest: "{{ apache_sites_available }}/000-default-redirect.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable tiararodney.com redirect vhost
|
||||
command: "{{ apache_enable_site_cmd }} 000-default-redirect"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/000-default-redirect.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Ensure Apache is started and enabled
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
-
|
||||
name: Ensure Apache is reloaded with current config
|
||||
meta: flush_handlers
|
||||
8
ansible/roles/apache/tasks/main.yml
Normal file
8
ansible/roles/apache/tasks/main.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
-
|
||||
name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Install and configure Apache
|
||||
ansible.builtin.include_tasks: install-apache.yml
|
||||
18
ansible/roles/apache/templates/000-default-redirect.conf.j2
Normal file
18
ansible/roles/apache/templates/000-default-redirect.conf.j2
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName tiararodney.com
|
||||
Redirect permanent / https://tiararodney.com/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName tiararodney.com
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert_tiararodney }}
|
||||
SSLCertificateKeyFile {{ ssl_key_tiararodney }}
|
||||
|
||||
DocumentRoot /var/www/tiararodney.com
|
||||
<Directory /var/www/tiararodney.com>
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
31
ansible/roles/apache/templates/reverse-proxy-vhost.conf.j2
Normal file
31
ansible/roles/apache/templates/reverse-proxy-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ server_name }}
|
||||
Redirect permanent / https://{{ server_name }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ server_name }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
{% for loc in restricted_locations | default([]) %}
|
||||
<Location {{ loc.path }}>
|
||||
{% for ip in loc.allowed_ips %}
|
||||
Require ip {{ ip }}
|
||||
{% endfor %}
|
||||
</Location>
|
||||
{% endfor %}
|
||||
|
||||
ProxyPreserveHost on
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Forwarded-Ssl "on"
|
||||
{% if websocket | default(false) %}
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
||||
RewriteRule ^/(.*) ws://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/$1 [P,L]
|
||||
{% endif %}
|
||||
ProxyPass / http://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/
|
||||
ProxyPassReverse / http://{{ backend_host | default('127.0.0.1') }}:{{ backend_port }}/
|
||||
</VirtualHost>
|
||||
21
ansible/roles/apache/templates/static-site-vhost.conf.j2
Normal file
21
ansible/roles/apache/templates/static-site-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ server_name }}
|
||||
Redirect permanent / https://{{ server_name }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ server_name }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
DocumentRoot {{ document_root }}
|
||||
<Directory {{ document_root }}>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
{% if directory_index is defined %}
|
||||
DirectoryIndex {{ directory_index }}
|
||||
{% endif %}
|
||||
</VirtualHost>
|
||||
7
ansible/roles/apache/vars/Debian.yml
Normal file
7
ansible/roles/apache/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
apache_package: apache2
|
||||
apache_service: apache2
|
||||
apache_sites_available: /etc/apache2/sites-available
|
||||
apache_sites_enabled: /etc/apache2/sites-enabled
|
||||
apache_enable_site_cmd: a2ensite
|
||||
apache_disable_site_cmd: a2dissite
|
||||
9
ansible/roles/authentik/defaults/main.yml
Normal file
9
ansible/roles/authentik/defaults/main.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
install_dir: /opt/authentik
|
||||
bind_address: "0.0.0.0"
|
||||
port: 9000
|
||||
postgres_shared_buffers: 64MB
|
||||
postgres_work_mem: 4MB
|
||||
postgres_maintenance_work_mem: 32MB
|
||||
postgres_effective_cache_size: 256MB
|
||||
redis_maxmemory: 80mb
|
||||
0
ansible/roles/authentik/files/branding/.gitkeep
Normal file
0
ansible/roles/authentik/files/branding/.gitkeep
Normal file
4
ansible/roles/authentik/meta/main.yml
Normal file
4
ansible/roles/authentik/meta/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
92
ansible/roles/authentik/tasks/deploy-authentik.yml
Normal file
92
ansible/roles/authentik/tasks/deploy-authentik.yml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy environment file
|
||||
template:
|
||||
src: env.j2
|
||||
dest: "{{ install_dir }}/.env"
|
||||
|
||||
-
|
||||
name: Ensure blueprints directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/blueprints"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy OAuth2 blueprint
|
||||
template:
|
||||
src: blueprint-oauth2.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/oauth2-applications.yaml"
|
||||
when: oauth_applications is defined and oauth_applications | length > 0
|
||||
|
||||
-
|
||||
name: Deploy enrollment blueprint
|
||||
template:
|
||||
src: blueprint-enrollment.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/enrollment.yaml"
|
||||
|
||||
-
|
||||
name: Deploy social login blueprint
|
||||
template:
|
||||
src: blueprint-social-logins.yml.j2
|
||||
dest: "{{ install_dir }}/blueprints/social-logins.yaml"
|
||||
when: social_login_sources is defined and social_login_sources | length > 0
|
||||
|
||||
-
|
||||
name: Ensure media directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/media/public"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Copy branding assets
|
||||
copy:
|
||||
src: branding/
|
||||
dest: "{{ install_dir }}/media/public/"
|
||||
mode: "0644"
|
||||
when: branding_assets | default(false)
|
||||
|
||||
-
|
||||
name: Ensure custom-templates email directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}/custom-templates/email"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy custom email templates
|
||||
template:
|
||||
src: "email/{{ item }}.j2"
|
||||
dest: "{{ install_dir }}/custom-templates/email/{{ item }}"
|
||||
loop:
|
||||
- account-confirmation.html
|
||||
- password-reset.html
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start Authentik stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Deploy Authentik backup script
|
||||
template:
|
||||
src: backup.sh.j2
|
||||
dest: /etc/restic/pre-backup.d/authentik.sh
|
||||
mode: "0755"
|
||||
4
ansible/roles/authentik/tasks/main.yml
Normal file
4
ansible/roles/authentik/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy Authentik
|
||||
ansible.builtin.include_tasks: deploy-authentik.yml
|
||||
46
ansible/roles/authentik/tasks/restore-authentik.yml
Normal file
46
ansible/roles/authentik/tasks/restore-authentik.yml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_authentik_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/authentik"
|
||||
|
||||
-
|
||||
name: Stop Authentik stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore config files
|
||||
copy:
|
||||
src: "{{ _authentik_backup_dir }}/{{ item }}"
|
||||
dest: "{{ install_dir }}/{{ item }}"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
loop:
|
||||
- .env
|
||||
- docker-compose.yml
|
||||
|
||||
-
|
||||
name: Start Postgres only
|
||||
command: >
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml
|
||||
up -d postgres
|
||||
|
||||
-
|
||||
name: Wait for Postgres to be ready
|
||||
pause:
|
||||
seconds: 10
|
||||
|
||||
-
|
||||
name: Restore Postgres dump
|
||||
shell: >
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml
|
||||
exec -T postgres psql -U authentik authentik
|
||||
< {{ _authentik_backup_dir }}/authentik.sql
|
||||
|
||||
-
|
||||
name: Start full Authentik stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
7
ansible/roles/authentik/templates/backup.sh.j2
Normal file
7
ansible/roles/authentik/templates/backup.sh.j2
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
BACKUP_DIR="{{ backup_staging_dir | default('/var/backups') }}/authentik"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
docker compose -f {{ install_dir }}/docker-compose.yml exec -T postgres pg_dump -U authentik authentik > "$BACKUP_DIR/authentik.sql"
|
||||
cp "{{ install_dir }}/.env" "$BACKUP_DIR/.env"
|
||||
cp "{{ install_dir }}/docker-compose.yml" "$BACKUP_DIR/docker-compose.yml"
|
||||
326
ansible/roles/authentik/templates/blueprint-enrollment.yml.j2
Normal file
326
ansible/roles/authentik/templates/blueprint-enrollment.yml.j2
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: enrollment-flow
|
||||
entries:
|
||||
# --- Brand configuration ---
|
||||
- model: authentik_brands.brand
|
||||
identifiers:
|
||||
domain: authentik-default
|
||||
state: present
|
||||
attrs:
|
||||
branding_title: {{ domain }}
|
||||
{% if branding_logo is defined %}
|
||||
branding_logo: {{ branding_logo }}
|
||||
{% endif %}
|
||||
{% if branding_favicon is defined %}
|
||||
branding_favicon: {{ branding_favicon }}
|
||||
{% endif %}
|
||||
|
||||
# --- Enrollment flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: enrollment-flow
|
||||
identifiers:
|
||||
slug: default-enrollment-flow
|
||||
attrs:
|
||||
name: Sign Up
|
||||
title: Create an account
|
||||
designation: enrollment
|
||||
authentication: require_unauthenticated
|
||||
|
||||
# --- Recovery flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: recovery-flow
|
||||
identifiers:
|
||||
slug: default-recovery-flow
|
||||
attrs:
|
||||
name: Password Recovery
|
||||
title: Reset your password
|
||||
designation: recovery
|
||||
authentication: require_unauthenticated
|
||||
|
||||
# --- Prompt fields ---
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-email
|
||||
identifiers:
|
||||
name: enrollment-field-email
|
||||
attrs:
|
||||
field_key: email
|
||||
label: Email
|
||||
type: email
|
||||
required: true
|
||||
placeholder: Email
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-username
|
||||
identifiers:
|
||||
name: enrollment-field-username
|
||||
attrs:
|
||||
field_key: username
|
||||
label: Username
|
||||
type: username
|
||||
required: true
|
||||
placeholder: Username
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-password
|
||||
identifiers:
|
||||
name: enrollment-field-password
|
||||
attrs:
|
||||
field_key: password
|
||||
label: Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password
|
||||
placeholder_expression: false
|
||||
order: 2
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-password-repeat
|
||||
identifiers:
|
||||
name: enrollment-field-password-repeat
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 3
|
||||
|
||||
# --- Password policy ---
|
||||
- model: authentik_policies_password.passwordpolicy
|
||||
id: password-policy
|
||||
identifiers:
|
||||
name: enrollment-password-policy
|
||||
attrs:
|
||||
name: enrollment-password-policy
|
||||
length_min: 10
|
||||
amount_uppercase: 1
|
||||
amount_lowercase: 1
|
||||
amount_digits: 1
|
||||
amount_symbols: 1
|
||||
check_static_rules: true
|
||||
check_have_i_been_pwned: true
|
||||
check_zxcvbn: true
|
||||
zxcvbn_score_threshold: 3
|
||||
error_message: "Password must be at least 10 characters with uppercase, lowercase, digit, and symbol."
|
||||
|
||||
# --- Enrollment stages ---
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
id: enrollment-prompt-stage
|
||||
identifiers:
|
||||
name: enrollment-prompt
|
||||
attrs:
|
||||
fields:
|
||||
- !KeyOf field-email
|
||||
- !KeyOf field-username
|
||||
- !KeyOf field-password
|
||||
- !KeyOf field-password-repeat
|
||||
validation_policies:
|
||||
- !KeyOf password-policy
|
||||
|
||||
- model: authentik_stages_user_write.userwritestage
|
||||
id: enrollment-user-write
|
||||
identifiers:
|
||||
name: enrollment-user-write
|
||||
attrs:
|
||||
user_creation_mode: always_create
|
||||
create_users_as_inactive: true
|
||||
|
||||
- model: authentik_stages_email.emailstage
|
||||
id: enrollment-email-verification
|
||||
identifiers:
|
||||
name: enrollment-email-verification
|
||||
attrs:
|
||||
use_global_settings: true
|
||||
activate_user_on_success: true
|
||||
subject: Verify your email address
|
||||
template: email/account-confirmation.html
|
||||
|
||||
- model: authentik_stages_user_login.userloginstage
|
||||
id: enrollment-user-login
|
||||
identifiers:
|
||||
name: enrollment-user-login
|
||||
|
||||
# --- Enrollment flow stage bindings ---
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-prompt-stage
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-user-write
|
||||
order: 20
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-email-verification
|
||||
order: 30
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf enrollment-flow
|
||||
stage: !KeyOf enrollment-user-login
|
||||
order: 100
|
||||
|
||||
# --- Recovery stages ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
id: recovery-identification
|
||||
identifiers:
|
||||
name: recovery-identification
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
|
||||
- model: authentik_stages_email.emailstage
|
||||
id: recovery-email
|
||||
identifiers:
|
||||
name: recovery-email
|
||||
attrs:
|
||||
use_global_settings: true
|
||||
subject: Reset your password
|
||||
template: email/password-reset.html
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-recovery-password
|
||||
identifiers:
|
||||
name: recovery-field-password
|
||||
attrs:
|
||||
field_key: password
|
||||
label: New Password
|
||||
type: password
|
||||
required: true
|
||||
placeholder: New Password
|
||||
placeholder_expression: false
|
||||
order: 0
|
||||
|
||||
- model: authentik_stages_prompt.prompt
|
||||
id: field-recovery-password-repeat
|
||||
identifiers:
|
||||
name: recovery-field-password-repeat
|
||||
attrs:
|
||||
field_key: password_repeat
|
||||
label: New Password (repeat)
|
||||
type: password
|
||||
required: true
|
||||
placeholder: New Password (repeat)
|
||||
placeholder_expression: false
|
||||
order: 1
|
||||
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
id: recovery-password-stage
|
||||
identifiers:
|
||||
name: recovery-password-prompt
|
||||
attrs:
|
||||
fields:
|
||||
- !KeyOf field-recovery-password
|
||||
- !KeyOf field-recovery-password-repeat
|
||||
validation_policies:
|
||||
- !KeyOf password-policy
|
||||
|
||||
- model: authentik_stages_user_write.userwritestage
|
||||
id: recovery-user-write
|
||||
identifiers:
|
||||
name: recovery-user-write
|
||||
attrs:
|
||||
user_creation_mode: never_create
|
||||
|
||||
- model: authentik_stages_user_login.userloginstage
|
||||
id: recovery-user-login
|
||||
identifiers:
|
||||
name: recovery-user-login
|
||||
|
||||
# --- Recovery flow stage bindings ---
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-identification
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-email
|
||||
order: 20
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-password-stage
|
||||
order: 30
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-user-write
|
||||
order: 40
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf recovery-flow
|
||||
stage: !KeyOf recovery-user-login
|
||||
order: 100
|
||||
|
||||
# --- Unenrollment (account deletion) flow ---
|
||||
- model: authentik_flows.flow
|
||||
id: unenrollment-flow
|
||||
identifiers:
|
||||
slug: default-unenrollment-flow
|
||||
attrs:
|
||||
name: Delete Account
|
||||
title: Delete your account
|
||||
designation: unenrollment
|
||||
|
||||
- model: authentik_stages_consent.consentstage
|
||||
id: unenrollment-consent
|
||||
identifiers:
|
||||
name: unenrollment-consent
|
||||
attrs:
|
||||
mode: always_require
|
||||
|
||||
- model: authentik_stages_user_delete.userdeletestage
|
||||
id: unenrollment-user-delete
|
||||
identifiers:
|
||||
name: unenrollment-user-delete
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf unenrollment-flow
|
||||
stage: !KeyOf unenrollment-consent
|
||||
order: 10
|
||||
|
||||
- model: authentik_flows.flowstagebinding
|
||||
identifiers:
|
||||
target: !KeyOf unenrollment-flow
|
||||
stage: !KeyOf unenrollment-user-delete
|
||||
order: 20
|
||||
|
||||
# --- Set recovery and unenrollment flows on brand ---
|
||||
- model: authentik_brands.brand
|
||||
identifiers:
|
||||
domain: authentik-default
|
||||
state: present
|
||||
attrs:
|
||||
flow_recovery: !KeyOf recovery-flow
|
||||
flow_unenrollment: !KeyOf unenrollment-flow
|
||||
|
||||
{% if social_login_sources is not defined or social_login_sources | length == 0 %}
|
||||
# --- Bind enrollment flow to the default login identification stage ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
identifiers:
|
||||
name: default-authentication-identification
|
||||
state: present
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
- username
|
||||
enrollment_flow: !KeyOf enrollment-flow
|
||||
{% endif %}
|
||||
67
ansible/roles/authentik/templates/blueprint-oauth2.yml.j2
Normal file
67
ansible/roles/authentik/templates/blueprint-oauth2.yml.j2
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: oauth2-applications
|
||||
entries:
|
||||
{% for app in oauth_applications %}
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
identifiers:
|
||||
name: {{ app.name }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ app.name }}
|
||||
client_type: {{ app.client_type | default('confidential') }}
|
||||
client_id: {{ app.client_id }}
|
||||
{% if app.client_secret is defined %}
|
||||
client_secret: {{ app.client_secret }}
|
||||
{% endif %}
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
redirect_uris:
|
||||
{% for uri in app.redirect_uris %}
|
||||
- url: "{{ uri }}"
|
||||
matching_mode: strict
|
||||
{% endfor %}
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: {{ app.slug }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ app.name }}
|
||||
slug: {{ app.slug }}
|
||||
provider: !Find [authentik_providers_oauth2.oauth2provider, [name, {{ app.name }}]]
|
||||
policy_engine_mode: any
|
||||
|
||||
{% endfor %}
|
||||
- model: authentik_policies_expression.expressionpolicy
|
||||
identifiers:
|
||||
name: enforce-unique-email
|
||||
state: present
|
||||
attrs:
|
||||
name: enforce-unique-email
|
||||
expression: |
|
||||
from authentik.core.models import User
|
||||
email = request.context.get("prompt_data", {}).get("email", "")
|
||||
if not email:
|
||||
return True
|
||||
if User.objects.filter(email=email).exists():
|
||||
ak_message("This email address is already in use.")
|
||||
return False
|
||||
return True
|
||||
execution_logging: true
|
||||
|
||||
- model: authentik_stages_prompt.promptstage
|
||||
identifiers:
|
||||
name: default-source-enrollment-prompt
|
||||
state: present
|
||||
attrs:
|
||||
fields:
|
||||
- !Find [authentik_stages_prompt.prompt, [name, default-source-enrollment-field-username]]
|
||||
validation_policies:
|
||||
- !Find [authentik_policies_expression.expressionpolicy, [name, enforce-unique-email]]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: social-login-sources
|
||||
entries:
|
||||
{% for source in social_login_sources %}
|
||||
- model: authentik_sources_oauth.oauthsource
|
||||
id: source-{{ source.slug }}
|
||||
identifiers:
|
||||
slug: {{ source.slug }}
|
||||
state: present
|
||||
attrs:
|
||||
name: {{ source.name }}
|
||||
slug: {{ source.slug }}
|
||||
provider_type: {{ source.provider_type }}
|
||||
consumer_key: {{ source.client_id }}
|
||||
consumer_secret: {{ source.client_secret }}
|
||||
authentication_flow: !Find [authentik_flows.flow, [slug, default-source-authentication]]
|
||||
enrollment_flow: !Find [authentik_flows.flow, [slug, default-source-enrollment]]
|
||||
|
||||
{% endfor %}
|
||||
# --- Add social login sources to the login identification stage ---
|
||||
- model: authentik_stages_identification.identificationstage
|
||||
identifiers:
|
||||
name: default-authentication-identification
|
||||
state: present
|
||||
attrs:
|
||||
user_fields:
|
||||
- email
|
||||
- username
|
||||
enrollment_flow: !Find [authentik_flows.flow, [slug, default-enrollment-flow]]
|
||||
recovery_flow: !Find [authentik_flows.flow, [slug, default-recovery-flow]]
|
||||
sources:
|
||||
{% for source in social_login_sources %}
|
||||
- !KeyOf source-{{ source.slug }}
|
||||
{% endfor %}
|
||||
51
ansible/roles/authentik/templates/docker-compose.yml.j2
Normal file
51
ansible/roles/authentik/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "${AUTHENTIK_POSTGRESQL__PASSWORD}"
|
||||
POSTGRES_USER: "authentik"
|
||||
POSTGRES_DB: "authentik"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
command: >
|
||||
postgres -c shared_buffers={{ postgres_shared_buffers }}
|
||||
-c work_mem={{ postgres_work_mem }}
|
||||
-c maintenance_work_mem={{ postgres_maintenance_work_mem }}
|
||||
-c effective_cache_size={{ postgres_effective_cache_size }}
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
command: ["redis-server", "--maxmemory", "{{ redis_maxmemory }}", "--maxmemory-policy", "allkeys-lru"]
|
||||
restart: unless-stopped
|
||||
|
||||
server:
|
||||
image: ghcr.io/goauthentik/server:{{ version }}
|
||||
command: server
|
||||
env_file: .env
|
||||
ports:
|
||||
- "{{ bind_address }}:{{ port }}:9000"
|
||||
volumes:
|
||||
- ./blueprints:/blueprints/custom:ro
|
||||
- ./media:/media:ro
|
||||
- ./custom-templates:/templates:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
worker:
|
||||
image: ghcr.io/goauthentik/server:{{ version }}
|
||||
command: worker
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./blueprints:/blueprints/custom:ro
|
||||
- ./media:/media:ro
|
||||
- ./custom-templates:/templates:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{% raw %}{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% blocktrans with domain=branding_title %}Verify your email - {{ domain }}{% endblocktrans %}</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
|
||||
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
|
||||
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>{% blocktrans with domain=branding_title %}Welcome to {{ domain }}{% endblocktrans %}</h1>
|
||||
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
|
||||
<p>{% blocktrans %}Please click the button below to verify your email address and activate your account.{% endblocktrans %}</p>
|
||||
<p style="text-align: center; margin: 32px 0;">
|
||||
<a href="{{ url }}" class="btn">{% trans "Verify Email" %}</a>
|
||||
</p>
|
||||
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
|
||||
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not create an account, you can safely ignore this email.{% endblocktrans %}</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>{{ branding_title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>{% endraw %}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{% raw %}{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% blocktrans with domain=branding_title %}Reset your password - {{ domain }}{% endblocktrans %}</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; background-color: #f4f4f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.card { background-color: #ffffff; border-radius: 8px; padding: 40px; margin-top: 20px; }
|
||||
.btn { display: inline-block; background-color: #4050b5; color: #ffffff; text-decoration: none; padding: 12px 32px; border-radius: 6px; font-weight: 600; }
|
||||
.footer { text-align: center; color: #888; font-size: 13px; margin-top: 24px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>{% trans "Reset your password" %}</h1>
|
||||
<p>{% blocktrans with username=user.username %}Hi {{ username }},{% endblocktrans %}</p>
|
||||
<p>{% blocktrans with domain=branding_title %}A password reset was requested for your account on {{ domain }}.{% endblocktrans %}</p>
|
||||
<p style="text-align: center; margin: 32px 0;">
|
||||
<a href="{{ url }}" class="btn">{% trans "Reset Password" %}</a>
|
||||
</p>
|
||||
<p>{% blocktrans with expiry=expires|naturaltime %}This link will expire {{ expiry }}.{% endblocktrans %}</p>
|
||||
<p style="color: #888; font-size: 13px;">{% blocktrans %}If you did not request a password reset, you can safely ignore this email.{% endblocktrans %}</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>{{ branding_title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>{% endraw %}
|
||||
19
ansible/roles/authentik/templates/env.j2
Normal file
19
ansible/roles/authentik/templates/env.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
AUTHENTIK_SECRET_KEY={{ secret_key }}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD={{ pg_password }}
|
||||
AUTHENTIK_POSTGRESQL__HOST=postgres
|
||||
AUTHENTIK_POSTGRESQL__USER=authentik
|
||||
AUTHENTIK_POSTGRESQL__NAME=authentik
|
||||
AUTHENTIK_REDIS__HOST=redis
|
||||
AUTHENTIK_HOST={{ domain }}
|
||||
AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS=10.0.0.0/24,172.16.0.0/12
|
||||
AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true
|
||||
AUTHENTIK_DISABLE_UPDATE_CHECK=true
|
||||
AUTHENTIK_ERROR_REPORTING__ENABLED=false
|
||||
{% if smtp_host is defined %}
|
||||
AUTHENTIK_EMAIL__HOST={{ smtp_host }}
|
||||
AUTHENTIK_EMAIL__PORT={{ smtp_port | default(587) }}
|
||||
AUTHENTIK_EMAIL__USERNAME={{ smtp_username }}
|
||||
AUTHENTIK_EMAIL__PASSWORD={{ smtp_password }}
|
||||
AUTHENTIK_EMAIL__USE_TLS=true
|
||||
AUTHENTIK_EMAIL__FROM={{ smtp_from | default(smtp_username) }}
|
||||
{% endif %}
|
||||
9
ansible/roles/bugzilla/defaults/main.yml
Normal file
9
ansible/roles/bugzilla/defaults/main.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
install_dir: /opt/bugzilla
|
||||
bugzilla_dir: /opt/bugzilla/bugzilla
|
||||
bugzilla_download_url: "https://ftp.mozilla.org/pub/webtools/bugzilla/5.0-branch/bugzilla-{{ version }}.tar.gz"
|
||||
db_port: 5433
|
||||
db_name: bugzilla
|
||||
db_user: bugzilla
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
6
ansible/roles/bugzilla/meta/main.yml
Normal file
6
ansible/roles/bugzilla/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
160
ansible/roles/bugzilla/tasks/deploy-bugzilla.yml
Normal file
160
ansible/roles/bugzilla/tasks/deploy-bugzilla.yml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
-
|
||||
name: Include OS-specific variables
|
||||
include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Install Bugzilla Perl dependencies
|
||||
apt:
|
||||
name: "{{ bugzilla_packages }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Enable Apache modules for Bugzilla
|
||||
community.general.apache2_module:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- cgid
|
||||
- expires
|
||||
- auth_openidc
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start bugzilla database
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Download Bugzilla
|
||||
unarchive:
|
||||
src: "{{ bugzilla_download_url }}"
|
||||
dest: "{{ install_dir }}"
|
||||
remote_src: yes
|
||||
creates: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
|
||||
-
|
||||
name: Symlink versioned directory to bugzilla_dir
|
||||
file:
|
||||
src: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
dest: "{{ bugzilla_dir }}"
|
||||
state: link
|
||||
when: bugzilla_dir != install_dir + '/bugzilla-' + version
|
||||
|
||||
-
|
||||
name: Deploy localconfig
|
||||
template:
|
||||
src: localconfig.j2
|
||||
dest: "{{ bugzilla_dir }}/localconfig"
|
||||
mode: "0640"
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy checksetup answers file
|
||||
template:
|
||||
src: checksetup-answers.j2
|
||||
dest: "{{ install_dir }}/checksetup-answers.pl"
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Wait for PostgreSQL to be ready
|
||||
wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ db_port }}"
|
||||
delay: 2
|
||||
timeout: 30
|
||||
|
||||
-
|
||||
name: Run Bugzilla checksetup
|
||||
command:
|
||||
cmd: "perl checksetup.pl {{ install_dir }}/checksetup-answers.pl"
|
||||
chdir: "{{ bugzilla_dir }}"
|
||||
register: checksetup_result
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: checksetup_result.rc == 0
|
||||
|
||||
-
|
||||
name: Run Bugzilla checksetup again to generate params.json
|
||||
command:
|
||||
cmd: "perl checksetup.pl {{ install_dir }}/checksetup-answers.pl"
|
||||
chdir: "{{ bugzilla_dir }}"
|
||||
creates: "{{ bugzilla_dir }}/data/params.json"
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth login class
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"user_info_class"\s*:\s*"CGI"'
|
||||
replace: '"user_info_class" : "Env,CGI"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth email variable
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"auth_env_email"\s*:\s*""'
|
||||
replace: '"auth_env_email" : "OIDC_CLAIM_email"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Configure Bugzilla Env auth realname variable
|
||||
replace:
|
||||
path: "{{ bugzilla_dir }}/data/params.json"
|
||||
regexp: '"auth_env_realname"\s*:\s*""'
|
||||
replace: '"auth_env_realname" : "OIDC_CLAIM_name"'
|
||||
when: oauth_client_id is defined
|
||||
|
||||
-
|
||||
name: Set Bugzilla file ownership
|
||||
file:
|
||||
path: "{{ install_dir }}/bugzilla-{{ version }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
recurse: yes
|
||||
|
||||
-
|
||||
name: Deploy bugzilla vhost
|
||||
template:
|
||||
src: bugzilla-vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/bugzilla.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable bugzilla site
|
||||
command: "{{ apache_enable_site_cmd }} bugzilla"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/bugzilla.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy bugzilla backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: bugzilla
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- bugzilla_postgres_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ bugzilla_dir }}/localconfig"
|
||||
4
ansible/roles/bugzilla/tasks/main.yml
Normal file
4
ansible/roles/bugzilla/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy bugzilla
|
||||
ansible.builtin.include_tasks: deploy-bugzilla.yml
|
||||
42
ansible/roles/bugzilla/tasks/restore.yml
Normal file
42
ansible/roles/bugzilla/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_bugzilla_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/bugzilla"
|
||||
|
||||
-
|
||||
name: Stop bugzilla database
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _bugzilla_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore localconfig
|
||||
copy:
|
||||
src: "{{ _bugzilla_backup_dir }}/localconfig"
|
||||
dest: "{{ bugzilla_dir }}/localconfig"
|
||||
remote_src: yes
|
||||
mode: "0640"
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Restore bugzilla postgres volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v bugzilla_postgres_data:/data
|
||||
-v {{ _bugzilla_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/postgres_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start bugzilla database
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
52
ansible/roles/bugzilla/templates/bugzilla-vhost.conf.j2
Normal file
52
ansible/roles/bugzilla/templates/bugzilla-vhost.conf.j2
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ domain }}
|
||||
Redirect permanent / https://{{ domain }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ domain }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
{% if oauth_client_id is defined %}
|
||||
OIDCProviderIssuer {{ oauth_issuer_url }}
|
||||
OIDCProviderAuthorizationEndpoint {{ oauth_authorize_url }}
|
||||
OIDCProviderTokenEndpoint {{ oauth_token_url }}
|
||||
OIDCProviderTokenEndpointAuth client_secret_post
|
||||
OIDCProviderUserInfoEndpoint {{ oauth_userinfo_url }}
|
||||
OIDCProviderJwksUri {{ oauth_jwks_url }}
|
||||
OIDCClientID {{ oauth_client_id }}
|
||||
OIDCClientSecret {{ oauth_client_secret }}
|
||||
OIDCRedirectURI https://{{ domain }}/oidc-callback
|
||||
OIDCCryptoPassphrase {{ oauth_crypto_passphrase }}
|
||||
OIDCScope "openid profile email"
|
||||
OIDCRemoteUserClaim preferred_username
|
||||
OIDCPassClaimsAs environment
|
||||
OIDCSSLValidateServer Off
|
||||
OIDCProviderEndSessionEndpoint {{ oauth_issuer_url }}/end-session/
|
||||
|
||||
<Location />
|
||||
AuthType openid-connect
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
||||
<Location /oidc-callback>
|
||||
AuthType openid-connect
|
||||
Require valid-user
|
||||
</Location>
|
||||
|
||||
RedirectMatch ^/logout$ /oidc-callback?logout=https%3A%2F%2F{{ domain }}%2F
|
||||
{% endif %}
|
||||
|
||||
DocumentRoot {{ bugzilla_dir }}
|
||||
<Directory {{ bugzilla_dir }}>
|
||||
AddHandler cgi-script .cgi
|
||||
Options +ExecCGI +FollowSymLinks
|
||||
DirectoryIndex index.cgi index.html
|
||||
AllowOverride All
|
||||
{% if oauth_client_id is not defined %}
|
||||
Require all granted
|
||||
{% endif %}
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
4
ansible/roles/bugzilla/templates/checksetup-answers.j2
Normal file
4
ansible/roles/bugzilla/templates/checksetup-answers.j2
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
$answer{'ADMIN_EMAIL'} = '{{ admin_email }}';
|
||||
$answer{'ADMIN_PASSWORD'} = '{{ admin_pwd }}';
|
||||
$answer{'ADMIN_REALNAME'} = '{{ admin_name | default("Admin") }}';
|
||||
$answer{'NO_PAUSE'} = 1;
|
||||
15
ansible/roles/bugzilla/templates/docker-compose.yml.j2
Normal file
15
ansible/roles/bugzilla/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: {{ db_name }}
|
||||
POSTGRES_USER: {{ db_user }}
|
||||
POSTGRES_PASSWORD: "{{ db_password }}"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:{{ db_port }}:5432"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
15
ansible/roles/bugzilla/templates/localconfig.j2
Normal file
15
ansible/roles/bugzilla/templates/localconfig.j2
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
$db_driver = 'Pg';
|
||||
$db_host = '127.0.0.1';
|
||||
$db_port = {{ db_port }};
|
||||
$db_name = '{{ db_name }}';
|
||||
$db_user = '{{ db_user }}';
|
||||
$db_pass = '{{ db_password }}';
|
||||
$webservergroup = 'www-data';
|
||||
{% if smtp_host is defined %}
|
||||
$smtp_server = '{{ smtp_host }}';
|
||||
$smtp_port = {{ smtp_port | default(587) }};
|
||||
$smtp_username = '{{ smtp_username }}';
|
||||
$smtp_password = '{{ smtp_password }}';
|
||||
$smtp_ssl = 'On';
|
||||
$mailfrom = '{{ smtp_from | default(smtp_username) }}';
|
||||
{% endif %}
|
||||
31
ansible/roles/bugzilla/vars/Debian.yml
Normal file
31
ansible/roles/bugzilla/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
bugzilla_packages:
|
||||
- build-essential
|
||||
- libgd-dev
|
||||
- libappconfig-perl
|
||||
- libdate-calc-perl
|
||||
- libtemplate-perl
|
||||
- libmime-tools-perl
|
||||
- libdbi-perl
|
||||
- libdbd-pg-perl
|
||||
- libcgi-pm-perl
|
||||
- libmath-random-isaac-perl
|
||||
- libapache2-mod-perl2
|
||||
- libchart-perl
|
||||
- libxml-twig-perl
|
||||
- libgd-perl
|
||||
- libgd-graph-perl
|
||||
- libtemplate-plugin-gd-perl
|
||||
- libsoap-lite-perl
|
||||
- libhtml-scrubber-perl
|
||||
- libemail-sender-perl
|
||||
- libemail-mime-perl
|
||||
- libjson-xs-perl
|
||||
- libencode-detect-perl
|
||||
- libtheschwartz-perl
|
||||
- libdaemon-generic-perl
|
||||
- libdatetime-perl
|
||||
- libdatetime-timezone-perl
|
||||
- libemail-address-perl
|
||||
- perlmagick
|
||||
- libapache2-mod-auth-openidc
|
||||
5
ansible/roles/comentario/defaults/main.yml
Normal file
5
ansible/roles/comentario/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/comentario
|
||||
port: 8060
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
6
ansible/roles/comentario/meta/main.yml
Normal file
6
ansible/roles/comentario/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
52
ansible/roles/comentario/tasks/deploy-comentario.yml
Normal file
52
ansible/roles/comentario/tasks/deploy-comentario.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy secrets file
|
||||
template:
|
||||
src: secrets.yaml.j2
|
||||
dest: "{{ install_dir }}/secrets.yaml"
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start comentario stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Deploy comentario vhost
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
vars:
|
||||
vhost_name: comentario
|
||||
server_name: "{{ domain }}"
|
||||
backend_port: "{{ port }}"
|
||||
|
||||
-
|
||||
name: Deploy comentario backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: comentario
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- comentario_comentario_postgres_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ install_dir }}/secrets.yaml"
|
||||
4
ansible/roles/comentario/tasks/main.yml
Normal file
4
ansible/roles/comentario/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy comentario
|
||||
ansible.builtin.include_tasks: deploy-comentario.yml
|
||||
41
ansible/roles/comentario/tasks/restore.yml
Normal file
41
ansible/roles/comentario/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_comentario_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/comentario"
|
||||
|
||||
-
|
||||
name: Stop comentario stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _comentario_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore secrets file
|
||||
copy:
|
||||
src: "{{ _comentario_backup_dir }}/secrets.yaml"
|
||||
dest: "{{ install_dir }}/secrets.yaml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore comentario postgres volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v comentario_comentario_postgres_data:/data
|
||||
-v {{ _comentario_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/comentario_postgres_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start comentario stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
26
ansible/roles/comentario/templates/docker-compose.yml.j2
Normal file
26
ansible/roles/comentario/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: comentario
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
comentario:
|
||||
image: registry.gitlab.com/comentario/comentario:{{ version }}
|
||||
ports:
|
||||
- "127.0.0.1:{{ port }}:80"
|
||||
environment:
|
||||
BASE_URL: https://{{ domain }}
|
||||
SECRETS_FILE: /secrets.yaml
|
||||
volumes:
|
||||
- ./secrets.yaml:/secrets.yaml:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
27
ansible/roles/comentario/templates/secrets.yaml.j2
Normal file
27
ansible/roles/comentario/templates/secrets.yaml.j2
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
postgres:
|
||||
host: postgres
|
||||
port: 5432
|
||||
database: comentario
|
||||
username: postgres
|
||||
password: postgres
|
||||
{% if smtp_host is defined %}
|
||||
smtp:
|
||||
host: {{ smtp_host }}
|
||||
port: {{ smtp_port | default(587) }}
|
||||
username: {{ smtp_username }}
|
||||
password: {{ smtp_password }}
|
||||
from: {{ smtp_from | default(smtp_username) }}
|
||||
{% endif %}
|
||||
{% if oauth_client_id is defined %}
|
||||
idp:
|
||||
oidc:
|
||||
- id: authentik
|
||||
name: Authentik
|
||||
url: {{ oauth_issuer_url }}
|
||||
key: {{ oauth_client_id }}
|
||||
secret: {{ oauth_client_secret }}
|
||||
scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
{% endif %}
|
||||
5
ansible/roles/conversejs/defaults/main.yml
Normal file
5
ansible/roles/conversejs/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /var/www/chat.tiararodney.com
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
prosody_port: 5280
|
||||
4
ansible/roles/conversejs/meta/main.yml
Normal file
4
ansible/roles/conversejs/meta/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: apache
|
||||
49
ansible/roles/conversejs/tasks/deploy-conversejs.yml
Normal file
49
ansible/roles/conversejs/tasks/deploy-conversejs.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
-
|
||||
name: Ensure converse.js document root exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Download converse.js release
|
||||
unarchive:
|
||||
src: "https://github.com/conversejs/converse.js/releases/download/v{{ version }}/converse.js-{{ version }}.tgz"
|
||||
dest: "{{ install_dir }}"
|
||||
remote_src: yes
|
||||
extra_opts: ["--strip-components=1"]
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Download libsignal-protocol for OMEMO
|
||||
get_url:
|
||||
url: "https://cdn.conversejs.org/3rdparty/libsignal-protocol.min.js"
|
||||
dest: "{{ install_dir }}/dist/libsignal-protocol.min.js"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy converse.js index page
|
||||
template:
|
||||
src: index.html.j2
|
||||
dest: "{{ install_dir }}/index.html"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
|
||||
-
|
||||
name: Deploy chat vhost
|
||||
template:
|
||||
src: vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/chat.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable chat site
|
||||
command: "{{ apache_enable_site_cmd }} chat"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/chat.conf"
|
||||
notify: reload apache
|
||||
4
ansible/roles/conversejs/tasks/main.yml
Normal file
4
ansible/roles/conversejs/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy conversejs
|
||||
ansible.builtin.include_tasks: deploy-conversejs.yml
|
||||
195
ansible/roles/conversejs/templates/index.html.j2
Normal file
195
ansible/roles/conversejs/templates/index.html.j2
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Chat - {{ domain }}</title>
|
||||
<link rel="stylesheet" href="dist/converse.min.css">
|
||||
<script src="dist/libsignal-protocol.min.js"></script>
|
||||
{% if oauth_client_id is defined %}
|
||||
<style>
|
||||
#oauth-login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: #2e3436;
|
||||
}
|
||||
#oauth-login a {
|
||||
padding: 16px 32px;
|
||||
background: #3584e4;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#oauth-login a:hover { background: #1c71d8; }
|
||||
</style>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
{% if oauth_client_id is defined %}
|
||||
<div id="oauth-login">
|
||||
<a id="login-btn" href="#">Login with Authentik</a>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
const CLIENT_ID = '{{ oauth_client_id }}';
|
||||
const AUTHORIZE_URL = '{{ oauth_authorize_url }}';
|
||||
const TOKEN_URL = '{{ oauth_token_url }}';
|
||||
const REDIRECT_URI = window.location.origin + window.location.pathname;
|
||||
const DOMAIN = '{{ domain }}';
|
||||
|
||||
function generateCodeVerifier() {
|
||||
const arr = new Uint8Array(32);
|
||||
crypto.getRandomValues(arr);
|
||||
return btoa(String.fromCharCode(...arr))
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
|
||||
async function generateCodeChallenge(verifier) {
|
||||
const data = new TextEncoder().encode(verifier);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
return btoa(String.fromCharCode(...new Uint8Array(hash)))
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
|
||||
async function startOAuth() {
|
||||
const state = generateCodeVerifier();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
sessionStorage.setItem('oauth_state', state);
|
||||
sessionStorage.setItem('oauth_code_verifier', codeVerifier);
|
||||
const params = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
scope: 'openid profile email',
|
||||
state: state,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
window.location.href = AUTHORIZE_URL + '?' + params.toString();
|
||||
}
|
||||
|
||||
async function exchangeCode(code) {
|
||||
const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
|
||||
const resp = await fetch(TOKEN_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
client_id: CLIENT_ID,
|
||||
code: code,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
code_verifier: codeVerifier
|
||||
})
|
||||
});
|
||||
const data = await resp.json();
|
||||
sessionStorage.removeItem('oauth_state');
|
||||
sessionStorage.removeItem('oauth_code_verifier');
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
function parseJwt(token) {
|
||||
const base64 = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
return JSON.parse(atob(base64));
|
||||
}
|
||||
|
||||
function loadConverse() {
|
||||
return new Promise(function(resolve) {
|
||||
var s = document.createElement('script');
|
||||
s.src = 'dist/converse.min.js';
|
||||
s.onload = resolve;
|
||||
document.body.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
function isTokenExpired(token) {
|
||||
try {
|
||||
const claims = parseJwt(token);
|
||||
return claims.exp && (claims.exp * 1000) < Date.now();
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function initConverse(token) {
|
||||
if (isTokenExpired(token)) {
|
||||
sessionStorage.removeItem('oauth_token');
|
||||
startOAuth();
|
||||
return;
|
||||
}
|
||||
document.getElementById('oauth-login').style.display = 'none';
|
||||
const claims = parseJwt(token);
|
||||
const jid = claims.preferred_username + '@' + DOMAIN;
|
||||
loadConverse().then(function() {
|
||||
converse.initialize({
|
||||
bosh_service_url: 'https://' + DOMAIN + '/http-bind',
|
||||
websocket_url: 'wss://' + DOMAIN + '/xmpp-websocket',
|
||||
view_mode: 'fullscreen',
|
||||
authentication: 'login',
|
||||
locked_domain: DOMAIN,
|
||||
muc_domain: 'conference.' + DOMAIN,
|
||||
locked_muc_domain: 'hidden',
|
||||
muc_instant_rooms: true,
|
||||
muc_show_logs_before_join: true,
|
||||
visible_toolbar_buttons: { toggle_occupants: true },
|
||||
jid: jid,
|
||||
password: token,
|
||||
auto_login: true,
|
||||
keepalive: true,
|
||||
omemo_default: true
|
||||
});
|
||||
converse.listen.on('logout', function() {
|
||||
sessionStorage.removeItem('oauth_token');
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
|
||||
if (code && state === sessionStorage.getItem('oauth_state')) {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
exchangeCode(code).then(function(token) {
|
||||
if (token) {
|
||||
sessionStorage.setItem('oauth_token', token);
|
||||
initConverse(token);
|
||||
}
|
||||
});
|
||||
} else if (sessionStorage.getItem('oauth_token')) {
|
||||
initConverse(sessionStorage.getItem('oauth_token'));
|
||||
} else {
|
||||
document.getElementById('login-btn').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
startOAuth();
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% else %}
|
||||
<script src="dist/converse.min.js"></script>
|
||||
<script>
|
||||
converse.initialize({
|
||||
bosh_service_url: 'https://{{ domain }}/http-bind',
|
||||
websocket_url: 'wss://{{ domain }}/xmpp-websocket',
|
||||
view_mode: 'fullscreen',
|
||||
authentication: 'login',
|
||||
locked_domain: '{{ domain }}',
|
||||
muc_domain: 'conference.{{ domain }}',
|
||||
locked_muc_domain: 'hidden',
|
||||
muc_instant_rooms: false,
|
||||
muc_show_logs_before_join: true,
|
||||
visible_toolbar_buttons: {
|
||||
toggle_occupants: true
|
||||
},
|
||||
omemo_default: true
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
31
ansible/roles/conversejs/templates/vhost.conf.j2
Normal file
31
ansible/roles/conversejs/templates/vhost.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ domain }}
|
||||
Redirect permanent / https://{{ domain }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ domain }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
DocumentRoot {{ install_dir }}
|
||||
<Directory {{ install_dir }}>
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Proxy BOSH requests to Prosody
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /http-bind http://127.0.0.1:{{ prosody_port }}/http-bind
|
||||
ProxyPassReverse /http-bind http://127.0.0.1:{{ prosody_port }}/http-bind
|
||||
|
||||
# Proxy WebSocket requests to Prosody
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
RewriteCond %{HTTP:Connection} upgrade [NC]
|
||||
RewriteRule ^/xmpp-websocket(.*) ws://127.0.0.1:{{ prosody_port }}/xmpp-websocket$1 [P,L]
|
||||
ProxyPass /xmpp-websocket http://127.0.0.1:{{ prosody_port }}/xmpp-websocket
|
||||
ProxyPassReverse /xmpp-websocket http://127.0.0.1:{{ prosody_port }}/xmpp-websocket
|
||||
</VirtualHost>
|
||||
5
ansible/roles/devpi/defaults/main.yml
Normal file
5
ansible/roles/devpi/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/devpi
|
||||
port: 3141
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
11
ansible/roles/devpi/files/Dockerfile
Normal file
11
ansible/roles/devpi/files/Dockerfile
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
RUN pip install --no-cache-dir devpi-server devpi-web
|
||||
|
||||
RUN devpi-init --serverdir /data || true
|
||||
|
||||
EXPOSE 3141
|
||||
|
||||
VOLUME /data
|
||||
|
||||
CMD ["devpi-server", "--serverdir", "/data", "--host", "0.0.0.0", "--port", "3141"]
|
||||
6
ansible/roles/devpi/meta/main.yml
Normal file
6
ansible/roles/devpi/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
-
|
||||
role: docker
|
||||
-
|
||||
role: apache
|
||||
51
ansible/roles/devpi/tasks/deploy-devpi.yml
Normal file
51
ansible/roles/devpi/tasks/deploy-devpi.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Copy Dockerfile
|
||||
copy:
|
||||
src: Dockerfile
|
||||
dest: "{{ install_dir }}/Dockerfile"
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start devpi stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
compose_build: policy
|
||||
|
||||
-
|
||||
name: Deploy devpi vhost
|
||||
include_role:
|
||||
name: apache
|
||||
tasks_from: deploy-reverse-proxy
|
||||
vars:
|
||||
vhost_name: devpi
|
||||
server_name: "{{ hostname }}"
|
||||
backend_port: "{{ port }}"
|
||||
|
||||
-
|
||||
name: Deploy devpi backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: devpi
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- devpi_devpi_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
4
ansible/roles/devpi/tasks/main.yml
Normal file
4
ansible/roles/devpi/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy devpi
|
||||
ansible.builtin.include_tasks: deploy-devpi.yml
|
||||
33
ansible/roles/devpi/tasks/restore.yml
Normal file
33
ansible/roles/devpi/tasks/restore.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
-
|
||||
name: Set backup staging directory
|
||||
set_fact:
|
||||
_devpi_backup_dir: "{{ backup_staging_dir | default('/var/backups') }}/devpi"
|
||||
|
||||
-
|
||||
name: Stop devpi stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: absent
|
||||
|
||||
-
|
||||
name: Restore docker-compose file
|
||||
copy:
|
||||
src: "{{ _devpi_backup_dir }}/docker-compose.yml"
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
remote_src: yes
|
||||
mode: "0600"
|
||||
|
||||
-
|
||||
name: Restore devpi data volume
|
||||
command: >
|
||||
docker run --rm
|
||||
-v devpi_devpi_data:/data
|
||||
-v {{ _devpi_backup_dir }}:/backup
|
||||
alpine sh -c "rm -rf /data/* && tar xzf /backup/devpi_data.tar.gz -C /data"
|
||||
|
||||
-
|
||||
name: Start devpi stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: present
|
||||
11
ansible/roles/devpi/templates/docker-compose.yml.j2
Normal file
11
ansible/roles/devpi/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
services:
|
||||
devpi:
|
||||
build: .
|
||||
ports:
|
||||
- "127.0.0.1:{{ port }}:3141"
|
||||
volumes:
|
||||
- devpi_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
devpi_data:
|
||||
7
ansible/roles/dnsmasq/defaults/main.yml
Normal file
7
ansible/roles/dnsmasq/defaults/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
dns_listen_address: "10.0.0.1"
|
||||
dns_port: 53
|
||||
dns_upstream:
|
||||
- "1.1.1.1"
|
||||
- "1.0.0.1"
|
||||
dns_records: []
|
||||
6
ansible/roles/dnsmasq/handlers/main.yml
Normal file
6
ansible/roles/dnsmasq/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
-
|
||||
name: restart dnsmasq
|
||||
service:
|
||||
name: dnsmasq
|
||||
state: restarted
|
||||
20
ansible/roles/dnsmasq/tasks/main.yml
Normal file
20
ansible/roles/dnsmasq/tasks/main.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
-
|
||||
name: Install dnsmasq
|
||||
apt:
|
||||
name: dnsmasq
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Deploy dnsmasq configuration
|
||||
template:
|
||||
src: dnsmasq.conf.j2
|
||||
dest: /etc/dnsmasq.d/local.conf
|
||||
notify: restart dnsmasq
|
||||
|
||||
-
|
||||
name: Ensure dnsmasq is running
|
||||
service:
|
||||
name: dnsmasq
|
||||
state: started
|
||||
enabled: yes
|
||||
11
ansible/roles/dnsmasq/templates/dnsmasq.conf.j2
Normal file
11
ansible/roles/dnsmasq/templates/dnsmasq.conf.j2
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
listen-address={{ dns_listen_address }}
|
||||
port={{ dns_port }}
|
||||
bind-interfaces
|
||||
no-resolv
|
||||
no-hosts
|
||||
{% for server in dns_upstream %}
|
||||
server={{ server }}
|
||||
{% endfor %}
|
||||
{% for record in dns_records %}
|
||||
address=/{{ record.domain }}/{{ record.ip }}
|
||||
{% endfor %}
|
||||
11
ansible/roles/docker/handlers/main.yml
Normal file
11
ansible/roles/docker/handlers/main.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
-
|
||||
name: restart containerd
|
||||
service:
|
||||
name: containerd
|
||||
state: restarted
|
||||
-
|
||||
name: restart docker
|
||||
service:
|
||||
name: docker
|
||||
state: restarted
|
||||
2
ansible/roles/docker/meta/main.yml
Normal file
2
ansible/roles/docker/meta/main.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
dependencies: []
|
||||
29
ansible/roles/docker/tasks/configure-mirror.yml
Normal file
29
ansible/roles/docker/tasks/configure-mirror.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
-
|
||||
name: Add registry mirror host entries
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: "{{ item.mirror | urlsplit('hostname') | regex_escape }}"
|
||||
line: "{{ registry_mirror_ip }} {{ item.mirror | urlsplit('hostname') }}"
|
||||
loop: "{{ registry_mirrors }}"
|
||||
when: registry_mirror_ip is defined
|
||||
|
||||
-
|
||||
name: Configure Docker Hub registry mirror
|
||||
copy:
|
||||
dest: /etc/docker/daemon.json
|
||||
content: |
|
||||
{
|
||||
"registry-mirrors": [
|
||||
{% for item in registry_mirrors if item.upstream == 'docker.io' %}
|
||||
"{{ item.mirror }}"{% if not loop.last %},{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
mode: "0644"
|
||||
notify: restart docker
|
||||
|
||||
-
|
||||
name: Ensure Docker is restarted if mirror changed
|
||||
meta: flush_handlers
|
||||
7
ansible/roles/docker/tasks/deploy-backup.yml
Normal file
7
ansible/roles/docker/tasks/deploy-backup.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
-
|
||||
name: "Deploy {{ backup_name }} docker volume backup script"
|
||||
template:
|
||||
src: backup-docker-volumes.sh.j2
|
||||
dest: "{{ backup_hook_dir }}/{{ backup_name }}.sh"
|
||||
mode: "0755"
|
||||
34
ansible/roles/docker/tasks/install-docker.yml
Normal file
34
ansible/roles/docker/tasks/install-docker.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
-
|
||||
name: Install Docker prerequisites
|
||||
apt:
|
||||
name: "{{ docker_prerequisites }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Add Docker GPG key
|
||||
apt_key:
|
||||
url: "{{ docker_gpg_url }}"
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Add Docker repository
|
||||
apt_repository:
|
||||
repo: "{{ docker_repo }}"
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Install Docker Engine and Compose plugin
|
||||
apt:
|
||||
name: "{{ docker_packages }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
-
|
||||
name: Ensure Docker service is running
|
||||
service:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
13
ansible/roles/docker/tasks/main.yml
Normal file
13
ansible/roles/docker/tasks/main.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
-
|
||||
name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Install and configure Docker
|
||||
ansible.builtin.include_tasks: install-docker.yml
|
||||
|
||||
-
|
||||
name: Configure registry mirrors
|
||||
ansible.builtin.include_tasks: configure-mirror.yml
|
||||
when: registry_mirrors is defined
|
||||
13
ansible/roles/docker/tasks/start-compose.yml
Normal file
13
ansible/roles/docker/tasks/start-compose.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
-
|
||||
name: "Create {{ compose_project_dir }} directory"
|
||||
file:
|
||||
path: "{{ compose_project_dir }}"
|
||||
state: directory
|
||||
|
||||
-
|
||||
name: "Start docker compose stack in {{ compose_project_dir }}"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ compose_project_dir }}"
|
||||
state: present
|
||||
build: "{{ compose_build | default(omit) }}"
|
||||
10
ansible/roles/docker/templates/backup-docker-volumes.sh.j2
Normal file
10
ansible/roles/docker/templates/backup-docker-volumes.sh.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
BACKUP_DIR="{{ backup_staging_dir | default('/var/backups') }}/{{ backup_name }}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
{% for vol in backup_volumes | default([]) %}
|
||||
docker run --rm -v {{ vol }}:/data:ro -v "$BACKUP_DIR":/backup alpine sh -c "tar czf /backup/{{ vol }}.tar.gz -C /data . || [ \$? -eq 1 ]"
|
||||
{% endfor %}
|
||||
{% for f in backup_files | default([]) %}
|
||||
cp "{{ f }}" "$BACKUP_DIR/"
|
||||
{% endfor %}
|
||||
15
ansible/roles/docker/vars/Debian.yml
Normal file
15
ansible/roles/docker/vars/Debian.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
docker_prerequisites:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
docker_packages:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
docker_repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_facts['distribution_release'] }} stable"
|
||||
docker_gpg_url: "https://download.docker.com/linux/debian/gpg"
|
||||
5
ansible/roles/docker_registry/defaults/main.yml
Normal file
5
ansible/roles/docker_registry/defaults/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
install_dir: /opt/docker-registry
|
||||
port: 5050
|
||||
ssl_cert: /etc/letsencrypt/live/tiararodney.com/fullchain.pem
|
||||
ssl_key: /etc/letsencrypt/live/tiararodney.com/privkey.pem
|
||||
11
ansible/roles/docker_registry/handlers/main.yml
Normal file
11
ansible/roles/docker_registry/handlers/main.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
-
|
||||
name: restart docker-registry
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: restarted
|
||||
-
|
||||
name: reload apache
|
||||
service:
|
||||
name: "{{ apache_service }}"
|
||||
state: reloaded
|
||||
61
ansible/roles/docker_registry/tasks/deploy-registry.yml
Normal file
61
ansible/roles/docker_registry/tasks/deploy-registry.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
-
|
||||
name: Ensure install directory exists
|
||||
file:
|
||||
path: "{{ install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
-
|
||||
name: Deploy registry configuration
|
||||
template:
|
||||
src: config.yml.j2
|
||||
dest: "{{ install_dir }}/config.yml"
|
||||
notify: restart docker-registry
|
||||
|
||||
-
|
||||
name: Deploy docker-compose file
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ install_dir }}/docker-compose.yml"
|
||||
|
||||
-
|
||||
name: Start registry stack
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: start-compose
|
||||
vars:
|
||||
compose_project_dir: "{{ install_dir }}"
|
||||
|
||||
-
|
||||
name: Load Apache variables
|
||||
include_vars:
|
||||
file: "{{ role_path }}/../apache/vars/{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Deploy registry vhost
|
||||
template:
|
||||
src: vhost.conf.j2
|
||||
dest: "{{ apache_sites_available }}/docker-registry-{{ hostname | regex_replace('\\..*', '') }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Enable registry vhost
|
||||
command: "{{ apache_enable_site_cmd }} docker-registry-{{ hostname | regex_replace('\\..*', '') }}"
|
||||
args:
|
||||
creates: "{{ apache_sites_enabled }}/docker-registry-{{ hostname | regex_replace('\\..*', '') }}.conf"
|
||||
notify: reload apache
|
||||
|
||||
-
|
||||
name: Deploy registry backup script
|
||||
include_role:
|
||||
name: docker
|
||||
tasks_from: deploy-backup
|
||||
vars:
|
||||
backup_name: docker-registry
|
||||
backup_hook_dir: /etc/restic/pre-backup.d
|
||||
backup_volumes:
|
||||
- docker-registry_registry_data
|
||||
backup_files:
|
||||
- "{{ install_dir }}/docker-compose.yml"
|
||||
- "{{ install_dir }}/config.yml"
|
||||
4
ansible/roles/docker_registry/tasks/main.yml
Normal file
4
ansible/roles/docker_registry/tasks/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
-
|
||||
name: Deploy registry
|
||||
ansible.builtin.include_tasks: deploy-registry.yml
|
||||
22
ansible/roles/docker_registry/tasks/restore-registry.yml
Normal file
22
ansible/roles/docker_registry/tasks/restore-registry.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
-
|
||||
name: Check if registry backup archive exists
|
||||
stat:
|
||||
path: /var/backups/docker-registry/docker-registry_registry_data.tar.gz
|
||||
register: registry_backup
|
||||
|
||||
-
|
||||
name: Restore registry volume from backup
|
||||
command: >
|
||||
docker run --rm
|
||||
-v docker-registry_registry_data:/data
|
||||
-v /var/backups/docker-registry:/backup:ro
|
||||
alpine sh -c "tar xzf /backup/docker-registry_registry_data.tar.gz -C /data"
|
||||
when: registry_backup.stat.exists
|
||||
|
||||
-
|
||||
name: Restart registry after restore
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ install_dir }}"
|
||||
state: restarted
|
||||
when: registry_backup.stat.exists
|
||||
13
ansible/roles/docker_registry/templates/config.yml.j2
Normal file
13
ansible/roles/docker_registry/templates/config.yml.j2
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
version: 0.1
|
||||
log:
|
||||
fields:
|
||||
service: registry
|
||||
storage:
|
||||
filesystem:
|
||||
rootdirectory: /var/lib/registry
|
||||
delete:
|
||||
enabled: true
|
||||
http:
|
||||
addr: :5000
|
||||
proxy:
|
||||
remoteurl: {{ remote_url | default('https://registry-1.docker.io') }}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- "127.0.0.1:{{ port }}:5000"
|
||||
volumes:
|
||||
- registry_data:/var/lib/registry
|
||||
- ./config.yml:/etc/docker/registry/config.yml:ro
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
registry_data:
|
||||
28
ansible/roles/docker_registry/templates/vhost.conf.j2
Normal file
28
ansible/roles/docker_registry/templates/vhost.conf.j2
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<VirtualHost *:80>
|
||||
ServerName {{ hostname }}
|
||||
Redirect permanent / https://{{ hostname }}/
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName {{ hostname }}
|
||||
SSLEngine on
|
||||
SSLCertificateFile {{ ssl_cert }}
|
||||
SSLCertificateKeyFile {{ ssl_key }}
|
||||
|
||||
# Return an empty OCI index for referrers requests.
|
||||
# registry:2 does not support the OCI referrers API and proxies
|
||||
# the request to upstream which may return HTML error pages,
|
||||
# causing Docker 29+ to fail with "failed to decode referrers index".
|
||||
RewriteEngine on
|
||||
RewriteRule "^/v2/.*/referrers/" - [R=200,L,E=REFERRERS:1]
|
||||
Header always set Content-Type "application/vnd.oci.image.index.v1+json" env=REFERRERS
|
||||
<LocationMatch "^/v2/.*/referrers/">
|
||||
ErrorDocument 200 '{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[]}'
|
||||
</LocationMatch>
|
||||
|
||||
ProxyPreserveHost on
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Forwarded-Ssl "on"
|
||||
ProxyPass / http://127.0.0.1:{{ port }}/
|
||||
ProxyPassReverse / http://127.0.0.1:{{ port }}/
|
||||
</VirtualHost>
|
||||
3
ansible/roles/host/defaults/main.yml
Normal file
3
ansible/roles/host/defaults/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
admin_user: tiara
|
||||
admin_shell: /bin/bash
|
||||
11
ansible/roles/host/handlers/main.yml
Normal file
11
ansible/roles/host/handlers/main.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
-
|
||||
name: restart sshd
|
||||
service:
|
||||
name: sshd
|
||||
state: restarted
|
||||
-
|
||||
name: restart zramswap
|
||||
systemd:
|
||||
name: zramswap
|
||||
state: restarted
|
||||
2
ansible/roles/host/meta/main.yml
Normal file
2
ansible/roles/host/meta/main.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
dependencies: []
|
||||
13
ansible/roles/host/tasks/main.yml
Normal file
13
ansible/roles/host/tasks/main.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
-
|
||||
name: Load OS-specific variables
|
||||
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
|
||||
|
||||
-
|
||||
name: Set up admin user
|
||||
ansible.builtin.include_tasks: setup-admin.yml
|
||||
when: ssh_pubkey_dir is defined
|
||||
|
||||
-
|
||||
name: Set up base system
|
||||
ansible.builtin.include_tasks: setup-base.yml
|
||||
35
ansible/roles/host/tasks/setup-admin.yml
Normal file
35
ansible/roles/host/tasks/setup-admin.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
-
|
||||
name: Create admin user
|
||||
user:
|
||||
name: "{{ admin_user }}"
|
||||
shell: "{{ admin_shell }}"
|
||||
groups: sudo
|
||||
append: yes
|
||||
create_home: yes
|
||||
|
||||
-
|
||||
name: Allow admin user passwordless sudo
|
||||
copy:
|
||||
dest: "/etc/sudoers.d/{{ admin_user }}"
|
||||
content: "{{ admin_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
mode: "0440"
|
||||
validate: "visudo -cf %s"
|
||||
|
||||
-
|
||||
name: Find SSH public keys
|
||||
find:
|
||||
paths: "{{ ssh_pubkey_dir }}"
|
||||
patterns: "*.pub"
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
register: ssh_pubkeys
|
||||
|
||||
-
|
||||
name: Deploy SSH authorized keys
|
||||
authorized_key:
|
||||
user: "{{ admin_user }}"
|
||||
key: "{{ lookup('file', item.path) }}"
|
||||
loop: "{{ ssh_pubkeys.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
69
ansible/roles/host/tasks/setup-base.yml
Normal file
69
ansible/roles/host/tasks/setup-base.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
-
|
||||
name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 0
|
||||
|
||||
-
|
||||
name: Install base packages
|
||||
apt:
|
||||
name: "{{ host_base_packages }}"
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Disable SSH password authentication
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "^#?PasswordAuthentication"
|
||||
line: "PasswordAuthentication no"
|
||||
notify: restart sshd
|
||||
|
||||
-
|
||||
name: Disable SSH root login
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "^#?PermitRootLogin"
|
||||
line: "PermitRootLogin no"
|
||||
notify: restart sshd
|
||||
|
||||
-
|
||||
name: Allow SSH through UFW
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "22"
|
||||
proto: tcp
|
||||
|
||||
-
|
||||
name: Allow additional UFW ports
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ item.port }}"
|
||||
proto: "{{ item.proto | default('tcp') }}"
|
||||
from_ip: "{{ item.from | default('any') }}"
|
||||
loop: "{{ ufw_allow | default([]) }}"
|
||||
|
||||
-
|
||||
name: Enable UFW with default deny
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
default: deny
|
||||
direction: incoming
|
||||
|
||||
-
|
||||
name: Configure fail2ban backend
|
||||
copy:
|
||||
dest: /etc/fail2ban/jail.local
|
||||
content: |
|
||||
[DEFAULT]
|
||||
backend = {{ fail2ban_backend }}
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
-
|
||||
name: Ensure fail2ban is running
|
||||
service:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
enabled: yes
|
||||
37
ansible/roles/host/tasks/setup-swap.yml
Normal file
37
ansible/roles/host/tasks/setup-swap.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
-
|
||||
name: Ensure swap exists
|
||||
command: fallocate -l {{ swap_size | default('2G') }} /swapfile
|
||||
args:
|
||||
creates: /swapfile
|
||||
|
||||
-
|
||||
name: Set swap permissions
|
||||
file:
|
||||
path: /swapfile
|
||||
mode: '0600'
|
||||
|
||||
-
|
||||
name: Make swap
|
||||
command: mkswap /swapfile
|
||||
args:
|
||||
creates: /swapfile.swap
|
||||
|
||||
-
|
||||
name: Mark swapfile as initialized
|
||||
file:
|
||||
path: /swapfile.swap
|
||||
state: touch
|
||||
|
||||
-
|
||||
name: Enable swap
|
||||
command: swapon /swapfile
|
||||
register: swap_on
|
||||
failed_when: false
|
||||
|
||||
-
|
||||
name: Add swap to fstab
|
||||
lineinfile:
|
||||
path: /etc/fstab
|
||||
line: "/swapfile none swap sw 0 0"
|
||||
state: present
|
||||
37
ansible/roles/host/tasks/setup-zram.yml
Normal file
37
ansible/roles/host/tasks/setup-zram.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
-
|
||||
name: Install zram-tools
|
||||
apt:
|
||||
name: zram-tools
|
||||
state: present
|
||||
|
||||
-
|
||||
name: Configure zram
|
||||
copy:
|
||||
dest: /etc/default/zramswap
|
||||
content: |
|
||||
ALGO={{ zram_algorithm | default('zstd') }}
|
||||
PERCENT={{ zram_percent | default(50) }}
|
||||
PRIORITY={{ zram_priority | default(100) }}
|
||||
mode: '0644'
|
||||
notify: restart zramswap
|
||||
|
||||
-
|
||||
name: Enable zramswap service
|
||||
systemd:
|
||||
name: zramswap
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
-
|
||||
name: Disable file-backed swap if present
|
||||
command: swapoff /swapfile
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
-
|
||||
name: Remove file-backed swap from fstab
|
||||
lineinfile:
|
||||
path: /etc/fstab
|
||||
line: "/swapfile none swap sw 0 0"
|
||||
state: absent
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue