feat: remove mail stack and Vaultwarden
Some checks failed
CI/CD / syntax-check (push) Successful in 1m15s
CI/CD / deploy (push) Has been cancelled

Removed services:
- docker-mailserver (Postfix + Dovecot)
- SnappyMail webmail
- Vaultwarden password manager

Removed infrastructure:
- certbot + Cloudflare DNS-01 TLS for mx.csrx.ru
- UFW rules for ports 25/587/993/465
- mail-internal and webmail-internal Docker networks
- SMTP config from Outline env
- vault, mail Traefik routes
- All related vault secrets and variables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jack 2026-03-26 04:06:29 +07:00
parent 207e1dcff0
commit 75bed6bb04
9 changed files with 105 additions and 560 deletions

View file

@ -3,7 +3,6 @@
domain_base: "csrx.ru"
# Derived domains
domain_vault: "vault.{{ domain_base }}"
domain_git: "git.{{ domain_base }}"
domain_plane: "plane.{{ domain_base }}"
domain_traefik: "traefik.{{ domain_base }}"
@ -12,8 +11,6 @@ domain_auth: "auth.{{ domain_base }}"
domain_status: "status.{{ domain_base }}"
domain_wiki: "wiki.{{ domain_base }}"
domain_n8n: "n8n.{{ domain_base }}"
domain_mail: "mail.{{ domain_base }}" # SnappyMail webmail (HTTPS via Traefik)
domain_mx: "mx.{{ domain_base }}" # docker-mailserver FQDN (SMTP/IMAP direct)
# Service paths
services_root: /opt/services
@ -22,7 +19,6 @@ deploy_group: deploy
# Secrets (from vault)
acme_email: "{{ vault_acme_email }}"
vaultwarden_admin_token: "{{ vault_vaultwarden_admin_token }}"
forgejo_db_password: "{{ vault_forgejo_db_password }}"
plane_db_password: "{{ vault_plane_db_password }}"
plane_secret_key: "{{ vault_plane_secret_key }}"
@ -46,10 +42,6 @@ outline_utils_secret: "{{ vault_outline_utils_secret }}"
outline_db_password: "{{ vault_outline_db_password }}"
n8n_encryption_key: "{{ vault_n8n_encryption_key }}"
n8n_jwt_secret: "{{ vault_n8n_jwt_secret }}"
mailserver_noreply_password: "{{ vault_mailserver_noreply_password }}"
snappymail_admin_password: "{{ vault_snappymail_admin_password }}"
mailserver_admin_password: "{{ vault_mailserver_admin_password }}"
mailserver_jack_password: "{{ vault_mailserver_jack_password }}"
# Server IPs (used for cross-server Traefik routing)
ip_main: "87.249.49.32"

View file

@ -1,122 +1,106 @@
$ANSIBLE_VAULT;1.1;AES256
38623734633034386664333334383830336637343861383134323961363536333136346436363838
3836343032363036633735316362646165623030613161320a663961653037633132376166303635
30653738383130393738383931323535633631303434343233663463346635376561313662336664
6434633432643536330a336635616262306632616332616361313738383239316135393735643063
39336634306164363632373066313866366462653536313537396539363235626339303334353031
65663866626361383663343834333466376339383463366235343166323361366131643534383665
64663038316634376161633161366663376232636535356239333733303933396561373235363863
35366335313066343835663362313434363438313137643766323835393531306536346163313933
31343930316637623830623666633164343765663434396365366134306439336164333466316133
33346364613731623633346136313033333461623762313930323565313633346633396330646565
33386264663561666330356662366430383532316639373536356166383365343035623564623832
37353565303732316665363862363266343836393839663836666434333237326530613936376231
33613439633265626166383162646530653339313530353136643338666633393036663665663539
30333932343464636231323231353235623264316536386233353563623235373932316236356563
65653533383064646130366236303064396662623664326662353737623437653730363463646139
32333861663339343361383234383332623737626233323666343730626663363738333932643334
64353365643334323735623937396664393561333434663139363739613532386265656366316136
33303232663939353566376163366131393066646233333035396135653861643566633032656264
65353338373661663466613263343866643664353063656333346363373065303263373863326163
31386565396364353634636231306166393862326364373463393930373036373531323830346163
34333262333734356436313032356337356336343138643463343635643234653639346337336138
30333764333466356230626462353463316636636330383333646530623039316330313336656266
38366338346562613864663961633439656234313662663661303665303466393863623164346464
36333136303430313034653365343437343166643263356635636433613361663462396233336163
64316530633063666465396331393030313666333265626634313635613335383037363366353638
36366132356661316362643533336366326437326561393464366533656134633366643433653061
38343839643935376430323563373733623334323562653666356333613130336537643864393737
33353839313438306164666238313131353166316632623633326261393961613333336331643831
33356636626161613263366261653262633661613766323835366562333062343462623439663462
64306238386264626364303963376164626531356436353731383965306365386431356166336238
61323235373033323433306161663832366330646339303235386133323630363064383930336233
30636338656439376138363238323965653734336538363761633033626464313535653262363638
39663763623637636635396430623539666162373439643832303766323061383133343865366466
61653133343061366334343933623330313764666639313931636662383161613432366239613238
30373034343864383630316233616434646131643337303132313362323935313333323534343032
32666633616237353161363265376232346663343937393965343665396338613932363864303366
33613062383531666437306436636364636535323631626163636437396165313663366432343938
36306332353739636439343130353464646432313761326231643962616166366536656139643963
36623134663364376237323639643930393664313866393338316135306666653733663930333266
33396662396339623839643936626465666665396137623731333265363931343137643237623435
39623132613435346231373632333135303666336533303363393530306533666566633565326466
35356362646335323861343634386531633035393730646463623337333435663365616163333863
37666132356463613064326438316132613564383234363134313739356134323236633636633237
39343236663039383230336463316663363564383638343736613665343238613736646234663366
35613364643164626364643034323766666266313561386539336437646337353730343363383434
61346361613637656336346131363830366539653264376330356661363464316139303963663833
63613632623934313366333965356630646237376636383138393731363564356131333838336538
36666362336239633665613734323536393630653433643337666166306230623161666366356336
32326161313432646465393365396265363634633633343065623762653438353139313839396163
30633764656636303965616434373235643231633336626133356363386333623339373234646664
66663066333063303465336338623165666164303531303332663964366461643931303863643061
30336461643662393036356536663335643536613335323664326234623834383932653263633733
36303864316361643766353439366230363532616562333062653632623737313931343064366433
36626634323135333764343339313634323734613765343264386632373733326430363137303030
66356637353930353539303062386564346166343237633239653037333561306365316635356131
38613331383131303233646331623035626661313233346561383938323164356536306332623136
38333765323836366535396464616332313665663166663161653330613764306630643665643739
31663866653666333130316136393861666236343935396133636538323938376330346339366162
62663137326339376136343230396462333966396235383331313566386664396137303663353437
35363139666263666232346661643766656362343339396539346631393330346236633163393633
36396165646231343364333662633639353437303634343065656461386563373531343234356131
63653865623039626130633731306439613435353265306262386564373765306539623939666361
38383131323061613362386438633866653131666261333838363134336138613462303939373062
34396532316535666331336330306463643662346339646565303532653364383730383239616237
62333832643038613837316538303931303537326663616564343531326461363536663033303133
31353238656537396234636133616666333364383730653733396631373038313966663631356238
33363636616336636166306234393538633133343535303166343766623136356162333163616135
64386635656438643132623430616435353636313739373634653530386461643136333533396633
34633561333963653036623632326131323664323938343735666138623534303136333165313763
34633565383935643738666335633633353534386166306631613536373830663733363063306138
34373637303438633330393037306665386131633964393137373963623438336232643439333261
35633831376533376333336235326234633231643036363430646364653330313461333534623065
31666131656366663962353230643839646661623165653530653533306563633166653839316633
35356138636134323333323961393961366231353736383463626132643031613165623630346334
32663464613230343839643166663538303734373263663061383031643538633634636632346531
39663464373439663264376162613464346666326137386163306132383763343337653062366162
33333431333133633438636633663133613138633732623833313264313766643131366634303234
65323831643661396330393439306437333438646130326462303964376632306139363661636336
36323738303436386431643332303862356634643464326130386662643833613835633335313635
33643365326138326638343539303437643933643733383930633236663839306636333332316132
65346531663265663439616332663964363639353061666531616164613139303265323137343062
36653332326338633661323463303637393338613534343835323638376231656334333530323034
34663036383261303463353232313431383837313162313061363265343431396337633731343365
64333661333637343564343432353031303538303165383566656537383863643564333336646535
34376533383766363131363664646635386133636263613532383137376233373130303264653231
61326363313365396631346565306166366164653530636434346132666537653637363133313866
34643130346539353736353936656139616230646637396264333839626339653638623839323361
39303639373732616338383930663236333464386638646664306430656333366531623466353866
36306333643336373330333661376333663964363964633364633731363831333536393064313566
37313439386664623832656230376334643535343336663261363230323662306134663961323131
37373630343164323839656237343861363633323865363532666438373936386531313532623938
36326631373635323664353463396438643264613135626463653037303739633762636537653661
64336236383631353766623536383063336636333566643434333237363635656634643437383838
34653166646332336135303732373365636366343236393633326430326339646239656561336136
38626162626236346162373335643964333136386438636234333238306264373663643963613339
33333831353263393739653964313837636161373231663830383538363163393833623837633832
35303061336632373630653634393633623533313561326639343934306231363265316163313137
34633962613165616239333061613332613531326564613963346634356561626639643735623836
63636333356536373239623034613331666230626638373233663937353036633936313333666164
66343933373164653937326635356436306165363761393330353334633765333633383639303133
32646534633762303232383332373932653866663035323666363039386632643062346562373035
31353737383538313036656333623630363234356136343533333539356533623566336430373961
65323738393466633930623334386365656163313836316165656138313936303364623763666266
32383130346139623966303061303765363537303664383433633638643032333031326232633538
34636636643031366335623362323062356534356163326663656530663835636165366430616564
37343462663230653830613862373732636636346262353436323037656535343436356133663464
62316635353064646564626366396565326233376465343264356165323664623834373562323664
33333636373834346635386331383037623933643036653364383932373637383766393738353762
36356233383139366431363637376265383639323738643133653764663734666638363537303763
65356535323365326337626464316138386464373566383866356639613961353130356233306630
32306633393961336363346561376636346466613161643236353038313665626161383830313839
35336632646462366235656236303362326262373130623166303833666430653563623764323230
34616134386339373339386264626435666239363264356631653438323666613765626134353830
37363930353833336337633861623264653163616565393962323839613063363537653634666439
32663736333131663166303563376263353333303537346635383361306665653634653631626262
65663165313036393437343332353937633538363533363165396136383261363234386236393962
63333664656562623466366134373263366165623739326334386332653861353964343363386163
33656365363131313535316262366666393936303366636439623032636131343537333438336562
33336663373331643037306439363533643933313937346432636236633663393166386234333931
32393166306461323631626561383630363561643830646239656330643837326635663438353665
66363534356265386664376633363739373231663037643930653766373931333633
62303439636364323266353466656530343431326238393630386234356163306338393062373837
6631373736376131373162333431396461656139623566370a653734356261613164383139336335
36333863613661306239326634373162666361366630633265613266366562323337666537306363
6465643139336636650a343230653364343262656436393563656466386530616130643764623832
33346463373263373135356365633063666631316563643132316262383637643833393230313565
31356431623762336438373839393938663864313262383230316630633566303638616634316136
36313135663963333139613036366264643130663332393163303664633636623765623536633562
35323737376661373835626134633035373830353637353964613061323463333739353933316336
33383934383235376563333239373833343462376133336439653736313561303866636166653439
31333366326236316266363966623332323535666439633232386634646362653433646563313265
64376662316537326133326236396137643635656564346466646334393535633464306633346462
39363931383066303766626165323561363562386363666134623662386465643237343738656439
36656363396464316235636339653966313563616166376239346431323433383462623462383737
30353464393365396138613638336464373862353730623836336365393932396165353339636139
61356365653966616430643261663864356466646136373236303764616531303061336162626435
36323732383736623239343835353265303165373865643966383234666334653231396437396433
36326536343135636135643135646538356465663861303564396330306536656162383032346233
62333037316439626638373564363033313632333330323939353863396539353534663931386166
66623762623330613530323136336630306538666663336134636439303433313430316137393163
61613630393637333762373163376663646561313064353932633263316634656361386432663865
39333131323837383065663038383362393239306463346338393634326663623538353037373866
65393736653432393463653339353166343233633635383437356233656634323637376632666362
35303265623538356464383736396165346234666663356334656436356431343162353337343465
66323230316636303432343664343236376535646564313434306564666434616663383233323839
34356635633030653865386264383836646165366238393330613231373939653565663265313165
31653638343631366562643731666262343532376464316164643262386166663132356330336464
35663064663532653738653065333864363434643764653466373138626630376330643064323334
37353033383861333230636565383831353332396239353765666630306664313061326135313732
37363066393763363939613466333839626563363264383430663238346463663235363732366332
32646639393036616239663237303430356563656563376133326565313761626331323139646430
33333832376638653334666531623634393331623663376134653437303438396437666134303635
62653165306436343732336437333837383562373263653764363931313163356439666664616430
61303733633135623564626230623932366633396538343636323530663636643738393032366663
35626134376535623934323565356465363835316466353937646163393966653966376137323535
65396330616333316231626162643462393662343063336639396166613834343030393435313338
38323332366235363733366637633635336666363831376339303466396234316462346539656639
39303836343638373365663539353239616638636639323738373930326237613033656162353133
36316330353437616332336262643935396637653736363038306137303632653634343562313238
39616137666639633838666262326239323365643033666234633865626136373165363838343761
36653438333336613236613061316137333665386536663566623139313232383033303633333661
64633933303236363335383038346531663230396231653563386465323537613064393064353035
65396262636437336238366331316136386639333637663764613362306137366362363139316265
66353938353237303737343263336262656234663034653432613137326232303163656431323263
31613536643062343635623962653363343031666438636637333030323033303637383239353930
64353633373033646330343531356132636663336231363733356334633030656539616561373130
66373338386430613235323733643539653364343438343037366133316635663461353537396361
36663664663631303762616166303432643633356635306563393237663435393565653164613763
38663839323538623034323333393038633833303730333865643036663331383838373532316162
30343531666665373231653737386662633537663733386439653534353264616565653562656464
65383230656137316536653437316435353339633734616131643564383632373265303533616262
31346530376166356531663234636265306339356231313765373165643566656231366136323030
66313037336436373035386364613263353635353263383161646139353934326663623466653431
63343764313334613137393764623731633065663835393930336434333931643139336166383138
37393233323464316433636336326233313566303332633731363562633139616638383962366366
34613737636233306162666239396563323537626332643132383938623532323666626437363066
30666236353639633930616264393166356561346134333034336335636231333736346331316233
64333262323230613534646562613930643732323834643530353534613439373937313531623562
33333632623735613436626366373431613132316633363338343335656266636663326133376330
32626535306661623138623065366363616162373166303565613861366261316533373764376266
30383833383133373431316264373335323531303763653636306236396639306539663964623032
30613037633638346339613638353935383763623534306633623037643333306362653365336438
32396432376266346137323731643865636264613832366137393963396137333532393538303165
35343037613338353634656464306131616664323637643035336664363031333166313566316266
35653438313065373666303539396139386332373061373433333035653262613930303834306633
66666132383939636535353565626333616332346261346332373566346331646565653835643065
64336265613137366539613130336435343961663430666239343839633565633939393338373632
34653434646437393862313535643563623730333435333231343630623331343266623832313263
34353265656236326635353432643932373231653732616561663938653137373262376432323635
64643731343730623063313664613139613063616132653232336138363761353833306238663430
37616463363363333231636530653731663263383838383662616534356430356666653731303266
65346437383138666338613133336266393235373266643237386639323838306661373532366537
31343535633235376137346638316363346435663833326165613030336138663439316132663935
35336433366231653963333630643635346439616165396538616637303364663662643735326430
62306431643833323432646239383064636132363731626365636337333333666333366339356433
34336363353866353138663634363936363962646137636163663536333732653965306131636234
31383866623639636566363731343662346364313366613832313032393965613465303433383738
35343161316237613932643466366334663963623332633033303534323365386631633131656464
34636435643366353238633039353930326430336439623661636433663431626463396666343534
39643263353738313238356635303235353162376436313130613733623665613630336461363464
35613832393437653033306234333961343863643961363430316438383834636530343761656434
35623936356430363633386630663736356637343539393864393730616230396662613731313363
63396562623838306435363761323935656237653262623638346163346331343330633262383432
36316433373462343137356136323231666330333135343762646165643135643165336331386431
35396664643138383665366331306133646330633232333830353463353663613533306339343233
66393363363535656232333661346563623832626136353461663065653564343062643538366533
32383930646130643066373032393133646664626465653939343564643631326164333864386461
31323839653737396233646161316235666232613935356336653864636464663430313236613130
38363839626139366235626530616166383231333730356630616336646638666634396366383831
62353564343665393736383432633538366162303632656261383933656239383836306665623464
62303637306534636436386334356233663931633236376162363934636130356364326661663534
66616633646633336661306333343165363662336461393463643162653633353663633731343265
34613361643830376232616233643462336266643534326139653865346536366561393539636335
35663063313463353330653639326335336561663935353539366462306431353531626333633366
39316133323832363231346635623366323939653739393964643862373930613332663963623965
37616365363464666434326266656237623232633531386366633037343739343337636364383833
32613031383561313166643062353036373166633230646331366561383134313063623732333534
63383432393833313961646133656439656234353638336335373761396637613462646131363130
33613833326433643933316531396363313436366238636133633531626363396461313933386664
35363862346235633264386530376337313130376330626631353364363938653538363032633238
37363965353064373764363961343234626166336139303632633964363064373633393937626363
33643336373636613162373761343437626338343765313437323734626538643034636165383437
32636639313334306565343239653037643364396538653434373337336435623464613234633931
31363163323731363834323635643337633161363539626531626562343839323664636265306138
37313936323836386131376261346461343438356534616238386337313133663062316437326231
61613832663732653265633139353431356366363237333436393561623262613836

View file

@ -5,7 +5,6 @@ services_root: /opt/services
# IMPORTANT: pin each image to a specific version tag.
# Check Docker Hub for the latest stable release before updating.
traefik_image: "traefik:v3.3" # https://hub.docker.com/_/traefik/tags
vaultwarden_image: "vaultwarden/server:1.32.7" # https://hub.docker.com/r/vaultwarden/server/tags
forgejo_image: "codeberg.org/forgejo/forgejo:9"
forgejo_db_image: "postgres:16-alpine"
plane_frontend_image: "makeplane/plane-frontend:stable" # https://hub.docker.com/r/makeplane/plane-frontend/tags

View file

@ -31,7 +31,6 @@ networks:
internal: true
volumes:
vaultwarden_data:
forgejo_data:
forgejo_db_data:
plane_pgdata:
@ -80,31 +79,6 @@ services:
timeout: 5s
retries: 3
# ── Vaultwarden ────────────────────────────────────────────────────────────
vaultwarden:
image: {{ vaultwarden_image }}
container_name: vaultwarden
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- backend
volumes:
- vaultwarden_data:/data
environment:
- ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN}
- DOMAIN=https://{{ domain_vault }}
- SIGNUPS_ALLOWED=false
- INVITATIONS_ALLOWED=true
- LOG_LEVEL=warn
- EXTENDED_LOGGING=true
- TZ=UTC
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:80/"]
interval: 30s
timeout: 5s
retries: 3
# ── Forgejo ────────────────────────────────────────────────────────────────
forgejo:
image: {{ forgejo_image }}

View file

@ -23,14 +23,6 @@ http:
service: api@internal
middlewares: [authelia@docker, rate-limit-strict]
vaultwarden:
rule: "Host(`{{ domain_vault }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: vaultwarden
middlewares: [rate-limit-default]
forgejo:
rule: "Host(`{{ domain_git }}`)"
entrypoints: [websecure]
@ -114,20 +106,7 @@ http:
service: n8n
middlewares: [rate-limit-strict]
mail:
rule: "Host(`{{ domain_mail }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: mail
middlewares: [rate-limit-default]
services:
vaultwarden:
loadBalancer:
servers:
- url: "http://vaultwarden:80"
forgejo:
loadBalancer:
servers:
@ -179,11 +158,6 @@ http:
servers:
- url: "http://{{ ip_tools }}:5678"
mail:
loadBalancer:
servers:
- url: "http://{{ ip_tools }}:8888"
middlewares:
# ── Security Headers (applied globally via entrypoint) ─────────────────
security-headers:

View file

@ -4,5 +4,3 @@ outline_image: "outlinewiki/outline:0.80.2"
outline_db_image: "postgres:15-alpine"
outline_redis_image: "redis:7-alpine"
n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags
mailserver_image: "ghcr.io/docker-mailserver/docker-mailserver:14" # https://github.com/docker-mailserver/docker-mailserver/releases
snappymail_image: "djmaze/snappymail:latest" # https://hub.docker.com/r/djmaze/snappymail/tags

View file

@ -7,111 +7,6 @@
group: "{{ deploy_group }}"
mode: "0750"
- name: Create mailserver directories
ansible.builtin.file:
path: "{{ tools_root }}/mailserver/{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- mail-data
- mail-state
- mail-logs
- config
- name: Create snappymail data directory
ansible.builtin.file:
path: "{{ tools_root }}/snappymail/data"
state: directory
owner: "82" # www-data uid in Alpine (SnappyMail container user)
group: "82"
mode: "0755"
# ── TLS certificate for mail.csrx.ru (via certbot + Cloudflare DNS-01) ───────
- name: Install certbot and Cloudflare DNS plugin
ansible.builtin.apt:
name:
- certbot
- python3-certbot-dns-cloudflare
state: present
update_cache: false
- name: Deploy Cloudflare credentials for certbot
ansible.builtin.copy:
content: |
dns_cloudflare_api_token = {{ cloudflare_dns_api_token }}
dest: /etc/letsencrypt/cloudflare.ini
mode: "0600"
owner: root
group: root
- name: Obtain TLS certificate for mx.{{ domain_base }}
ansible.builtin.command: >
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
--email {{ acme_email }}
--agree-tos --no-eff-email
-d mx.{{ domain_base }}
--non-interactive
register: certbot_result
changed_when: "'Certificate not yet due for renewal' not in certbot_result.stdout"
failed_when:
- certbot_result.rc != 0
- "'Certificate not yet due for renewal' not in certbot_result.stdout"
- "'Certificate not yet due for renewal' not in certbot_result.stderr"
- name: Deploy certbot renewal deploy-hook (reload mailserver after cert renewal)
ansible.builtin.copy:
dest: /etc/letsencrypt/renewal-hooks/deploy/reload-mailserver.sh
mode: "0750"
owner: root
group: root
content: |
#!/usr/bin/env bash
# Triggered by certbot after successful cert renewal
# Reloads Postfix + Dovecot TLS without full restart
set -euo pipefail
if docker ps --format '{{ '{{' }}.Names{{ '}}' }}' | grep -q '^mailserver$'; then
docker exec mailserver supervisorctl restart postfix dovecot
echo "[$(date)] mailserver TLS reloaded after cert renewal"
fi
- name: Schedule certbot auto-renewal (twice daily, certbot renews only when needed)
ansible.builtin.cron:
name: "certbot renew"
minute: "15"
hour: "3,15"
job: "certbot renew --quiet --deploy-hooks 2>&1 | logger -t certbot"
user: root
state: present
# ── Open mail ports in UFW ────────────────────────────────────────────────────
- name: Allow SMTP inbound (port 25)
community.general.ufw:
rule: allow
port: "25"
proto: tcp
- name: Allow SMTP submission (port 587)
community.general.ufw:
rule: allow
port: "587"
proto: tcp
- name: Allow IMAPS (port 993)
community.general.ufw:
rule: allow
port: "993"
proto: tcp
- name: Allow SMTPS (port 465)
community.general.ufw:
rule: allow
port: "465"
proto: tcp
# ── Deploy configs and start stack ────────────────────────────────────────────
- name: Deploy docker-compose.yml
ansible.builtin.template:
@ -138,203 +33,9 @@
- "{{ outline_db_image }}"
- "{{ outline_redis_image }}"
- "{{ n8n_image }}"
- "{{ mailserver_image }}"
- "{{ snappymail_image }}"
- name: Start tools stack
community.docker.docker_compose_v2:
project_src: "{{ tools_root }}"
state: present
pull: missing
# ── SnappyMail admin password — write bcrypt hash directly to application.ini ──
# djmaze/snappymail does not reliably apply SNAPPYMAIL_ADMIN_PASSWORD env var;
# instead we verify and update the hash in the config file on every deploy.
- name: Ensure SnappyMail admin password is set correctly
ansible.builtin.shell: |
python3 << 'PYEOF'
import subprocess, re, sys
config_path = "{{ tools_root }}/snappymail/data/_data_/_default_/configs/application.ini"
password = "{{ snappymail_admin_password }}"
try:
with open(config_path) as f:
content = f.read()
except FileNotFoundError:
print("CONFIG_NOT_FOUND")
sys.exit(1)
# Extract current hash (bcrypt hashes start with $2y$)
m = re.search(r'^admin_password\s*=\s*"?(\$2y\$[^"\n]+)', content, re.M)
current_hash = m.group(1).strip() if m else ""
if current_hash:
r = subprocess.run(
["docker", "exec", "snappymail", "php", "-r",
f"echo password_verify('{password}', '{current_hash}') ? 'yes' : 'no';"],
capture_output=True, text=True
)
if r.stdout.strip() == "yes":
print("ALREADY_SET")
sys.exit(0)
# Generate a new bcrypt hash using PHP inside the container
r = subprocess.run(
["docker", "exec", "snappymail", "php", "-r",
f"echo password_hash('{password}', PASSWORD_BCRYPT);"],
capture_output=True, text=True
)
new_hash = r.stdout.strip()
if not new_hash.startswith("$2y$"):
print(f"HASH_ERROR: {r.stderr}")
sys.exit(1)
new_content = re.sub(
r'^admin_password\s*=.*$',
f'admin_password = "{new_hash}"',
content, flags=re.M
)
with open(config_path, "w") as f:
f.write(new_content)
print("UPDATED")
PYEOF
register: snappymail_pw_result
changed_when: "'UPDATED' in snappymail_pw_result.stdout"
failed_when: >
snappymail_pw_result.rc != 0 or
'CONFIG_NOT_FOUND' in snappymail_pw_result.stdout or
'HASH_ERROR' in snappymail_pw_result.stdout
- name: Restart SnappyMail after password update
ansible.builtin.command: docker restart snappymail
when: snappymail_pw_result.changed
changed_when: true
# ── SnappyMail domain config for csrx.ru ─────────────────────────────────────
# Points IMAP/SMTP to the mailserver container (shared `front` Docker network).
# type: 0=plain, 1=SSL, 2=STARTTLS
- name: Deploy SnappyMail domain config for {{ domain_base }}
ansible.builtin.copy:
content: |
{
"IMAP": {
"host": "mailserver",
"port": 993,
"type": 1,
"timeout": 300,
"shortLogin": false,
"lowerLogin": true,
"sasl": ["PLAIN", "LOGIN"],
"ssl": {
"verify_peer": false,
"verify_peer_name": false,
"allow_self_signed": true,
"SNI_enabled": true,
"disable_compression": true,
"security_level": 0
},
"disabled_capabilities": [],
"use_expunge_all_on_delete": false,
"fast_simple_search": true,
"force_select": false,
"message_all_headers": false,
"message_list_limit": 10000,
"search_filter": ""
},
"SMTP": {
"host": "mailserver",
"port": 587,
"type": 2,
"timeout": 60,
"shortLogin": false,
"lowerLogin": true,
"sasl": ["PLAIN", "LOGIN"],
"ssl": {
"verify_peer": false,
"verify_peer_name": false,
"allow_self_signed": true,
"SNI_enabled": true,
"disable_compression": true,
"security_level": 0
},
"useAuth": true,
"setSender": true,
"usePhpMail": false
},
"Sieve": {
"enabled": false,
"host": "mailserver",
"port": 4190,
"type": 0,
"timeout": 10,
"shortLogin": false,
"lowerLogin": true,
"sasl": ["PLAIN"],
"ssl": {
"verify_peer": false,
"verify_peer_name": false,
"allow_self_signed": true
}
},
"whiteList": ""
}
dest: "{{ tools_root }}/snappymail/data/_data_/_default_/domains/{{ domain_base }}.json"
owner: "82"
group: "82"
mode: "0640"
register: snappymail_domain_result
- name: Restart SnappyMail after domain config update
ansible.builtin.command: docker restart snappymail
when: snappymail_domain_result.changed
changed_when: true
# ── Mail accounts (idempotent: check host-side config file) ──────────────────
- name: Wait for mailserver to be ready
ansible.builtin.command: docker exec mailserver postfix status
register: postfix_status
changed_when: false
retries: 18
delay: 10
until: postfix_status.rc == 0
- name: Create mail accounts
ansible.builtin.command: >
docker exec mailserver setup email add {{ item.address }} {{ item.password }}
loop:
- { address: "noreply@{{ domain_base }}", password: "{{ mailserver_noreply_password }}" }
- { address: "admin@{{ domain_base }}", password: "{{ mailserver_admin_password }}" }
- { address: "jack@{{ domain_base }}", password: "{{ mailserver_jack_password }}" }
register: mail_account_result
changed_when: mail_account_result.rc == 0
failed_when: >
mail_account_result.rc != 0 and
'already exists' not in mail_account_result.stderr
# ── DKIM ─────────────────────────────────────────────────────────────────────
- name: Check if DKIM key exists
ansible.builtin.stat:
path: "{{ tools_root }}/mailserver/config/opendkim/keys/{{ domain_base }}/mail.private"
register: dkim_key
- name: Generate DKIM key
ansible.builtin.command: >
docker exec mailserver setup config dkim domain {{ domain_base }}
when: not dkim_key.stat.exists
register: dkim_generated
- name: Read DKIM DNS record
ansible.builtin.command: >
cat {{ tools_root }}/mailserver/config/opendkim/keys/{{ domain_base }}/mail.txt
when: dkim_generated is changed
register: dkim_record
- name: Print DKIM DNS instructions
ansible.builtin.debug:
msg: |
══════════════════════════════════════════════════════════════════
DKIM key generated! Add this TXT record to Cloudflare DNS:
{{ dkim_record.stdout }}
══════════════════════════════════════════════════════════════════
when: dkim_generated is changed

View file

@ -12,9 +12,6 @@ networks:
n8n-internal:
driver: bridge
internal: true
mail-internal:
driver: bridge
internal: true
volumes:
outline_db_data:
@ -31,7 +28,6 @@ services:
env_file: .env
networks:
- outline-internal
- mail-internal # send mail via mailserver
- front # needed for host port binding
ports:
# Exposed only to main Traefik (access controlled by UFW)
@ -130,68 +126,3 @@ services:
options:
max-size: "10m"
max-file: "3"
# ── Mail server (Postfix + Dovecot — send & receive for @csrx.ru) ───────────
mailserver:
image: {{ mailserver_image }}
container_name: mailserver
hostname: mx
domainname: {{ domain_base }}
restart: unless-stopped
networks:
- mail-internal # Outline → mailserver (internal, port 587 with auth)
- front # inbound/outbound internet SMTP
ports:
- "{{ ip_tools }}:25:25" # SMTP inbound (MX delivery from internet)
- "{{ ip_tools }}:587:587" # SMTP submission (mail clients, STARTTLS)
- "{{ ip_tools }}:993:993" # IMAPS (mail clients, TLS)
- "{{ ip_tools }}:465:465" # SMTPS (mail clients, implicit TLS)
environment:
- ENABLE_RSPAMD=1 # spam filter + DKIM signing
- ENABLE_CLAMAV=0 # no antivirus (saves RAM)
- ENABLE_FAIL2BAN=0 # host fail2ban already handles this
- POSTFIX_INET_PROTOCOLS=ipv4
- SSL_TYPE=letsencrypt # TLS certs from /etc/letsencrypt/live/mx.csrx.ru/
- LOG_LEVEL=warn
- OVERRIDE_HOSTNAME=mx.{{ domain_base }}
- POSTMASTER_ADDRESS=admin@{{ domain_base }}
- POSTFIX_MESSAGE_SIZE_LIMIT=26214400 # 25 MB max message size
volumes:
- {{ tools_root }}/mailserver/mail-data:/var/mail
- {{ tools_root }}/mailserver/mail-state:/var/mail-state
- {{ tools_root }}/mailserver/mail-logs:/var/log/mail
- {{ tools_root }}/mailserver/config:/tmp/docker-mailserver
- /etc/letsencrypt:/etc/letsencrypt:ro # TLS certs from certbot
stop_grace_period: 1m
cap_add:
- NET_ADMIN
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ── SnappyMail webmail ───────────────────────────────────────────────────────
snappymail:
image: {{ snappymail_image }}
container_name: snappymail
restart: unless-stopped
networks:
- mail-internal # reach mailserver for IMAP/SMTP
- front # expose port 8888 to host
ports:
- "{{ ip_tools }}:8888:8888"
volumes:
- {{ tools_root }}/snappymail/data:/var/lib/snappymail
environment:
- SNAPPYMAIL_ADMIN_PASSWORD={{ snappymail_admin_password }}
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8888"]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

View file

@ -27,14 +27,6 @@ FILE_STORAGE=s3
# Auth — local accounts (can add OIDC/Authelia later)
AUTH_PROVIDERS=email
# SMTP — local docker-mailserver container (same Docker network, port 587 with auth)
SMTP_HOST=mailserver
SMTP_PORT=587
SMTP_USERNAME=noreply@{{ domain_base }}
SMTP_PASSWORD={{ mailserver_noreply_password }}
SMTP_FROM_EMAIL=noreply@{{ domain_base }}
SMTP_SECURE=false
# Outline DB password (used in docker-compose)
OUTLINE_DB_PASSWORD={{ outline_db_password }}