diff --git a/roles/tools/tasks/main.yml b/roles/tools/tasks/main.yml index 056b23a..e5d0391 100644 --- a/roles/tools/tasks/main.yml +++ b/roles/tools/tasks/main.yml @@ -147,11 +147,68 @@ state: present pull: missing -# ── SnappyMail: force restart so entrypoint applies SNAPPYMAIL_ADMIN_PASSWORD ── -# The env var is processed by the container entrypoint on every start. -# Explicit restart ensures the password is always written to config correctly. -- name: Restart SnappyMail to apply admin password from env +# ── 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 # ── Mail accounts (idempotent: check host-side config file) ──────────────────