ansible-role-listmonk

Deploys listmonk v6.2.0, the eom.dev newsletter, to Kubernetes.

It fits in with the rest of eom.dev:

  • Login goes through Dex (listmonk static client) with the same "eom.dev account" button as the other services. Only e-mail addresses listed in listmonk_oidc_admins can sign in, so being a member is not enough to get into the admin UI. A password login for the admin user is kept as a fallback.
  • Mail goes out through postfix.eom.dev:587 as the LDAP account news, the same way Gitea, Nextcloud and PeerTube send mail.
  • Look: the public pages (subscribe, archive, preferences, unsubscribe) use the Catppuccin Mocha/Latte palette from dex.eom.dev, along with the eom.dev logo and favicon.
  • Cluster conventions: ca-issuer TLS, nfs-client volumes, and anti-affinity that keeps the pods off the ingress-nginx node.

What it creates

Object Purpose
Secret/listmonk DB password and bootstrap admin credentials
StatefulSet/listmonk-db PostgreSQL 17 (alpine, uid 70) on nfs-client
Deployment/listmonk Runs --install --idempotent, then --upgrade, then serves on :9000
PVC/listmonk-uploads Media uploaded for campaigns
Ingress/listmonk https://listmonk.eom.dev

listmonk keeps most of its configuration in the settings table, not in a config file. The role writes those settings with an idempotent SQL script (templates/configure.sql.j2) that it runs in the database pod. The script covers SMTP, Dex, theme, captcha, privacy, the Dex admin users and the lists in listmonk_lists. On the first run it also removes listmonk's sample data. listmonk is restarted only when a row actually changes.

Settings the role manages are reset on every run. Change them in defaults/main.yml or listmonk_settings_extra, not in the UI. Everything else, including templates, campaigns and subscribers, stays under the UI's control.

Required variables (Vault)

Variable Notes
listmonk_db_password 16+ chars. Only used when the volume is first initialised
listmonk_admin_password 8+ chars, for the admin fallback login
listmonk_smtp_password Password of the news LDAP account
listmonk_oidc_client_secret Must match the listmonk client in Dex

The eom.dev playbook maps them from the Vault names listmonk_postgresql_password, listmonk_admin_password, listmonk_smtp_password and listmonk_dex_client_secret.

One-time setup outside this role

  1. In LDAP, create the posix account news with mail: news@eom.dev and add it to cn=Postfix Users,ou=Postfix,ou=Services,dc=eom,dc=dev. Its password is listmonk_smtp_password.
  2. Add the listmonk client to Dex (ericomeehan.dex/playbooks/deploy-eom.yml) and run that play.

API users

User Can do Token
membership Read and manage subscribers on eom.dev (signup opt-in, profile toggle) listmonk_api_token (Vault); same value as LISTMONK_API_TOKEN in the www membership Secret
newsletter Create, edit, test-send and schedule campaigns to eom.dev; no subscriber data or settings Generated by the role, stored in Secret/listmonk-newsletter-api (url, username, token)

Neither user can read settings or other lists. To rotate the newsletter token, delete its Secret and run the play again.

Embedding a signup form

listmonk accepts a plain HTML POST, so any eom.dev page can include a form:

<form method="post" action="https://listmonk.eom.dev/subscription/form">
  <input type="hidden" name="nonce">
  <input type="email" name="email" required placeholder="you@example.com">
  <input type="hidden" name="l" value="LIST-UUID">
  <button type="submit">Subscribe</button>
</form>

Copy the list UUID from Lists → eom.dev → Forms in the admin UI. The form uses double opt-in, so subscribers have to confirm by e-mail. The page at https://listmonk.eom.dev/subscription/form adds an Altcha proof-of-work captcha.

Testing

ansible-playbook -i tests/inventory tests/render.yml   # renders into .render/

This version was tested against listmonk/listmonk:v6.2.0 and postgres:17.11-alpine. The SQL changes 12 settings, 1 user, 1 list and the sample data on the first run, and nothing on the second.

License

MIT-0

S
Description
Ansible role to deploy Listmonk to Kubernetes
Readme MIT-0
80 KiB
Languages
Jinja 87.3%
CSS 9.4%
JavaScript 3.3%