Skip to content

zabbix_server: add new role for Zabbix Server, database and frontend - #2748

Draft
scibi wants to merge 13 commits into
debops:masterfrom
scibi:feature/zabbix-server-role
Draft

scibi wants to merge 13 commits into
debops:masterfrom
scibi:feature/zabbix-server-role

Conversation

@scibi

@scibi scibi commented Aug 24, 2026

Copy link
Copy Markdown
Member

This adds a new role that installs and configures Zabbix Server from the official upstream APT repository, imports the PostgreSQL database schema idempotently by checking the dbversion table rather than a marker file, and configures the PHP-FPM/nginx web frontend without the interactive setup wizard, following the existing phpipam/netbox pattern of PostgreSQL-backed PHP applications.

The role also bootstraps the Zabbix API on a fresh install: it changes the default Admin password to one generated into the DebOps secret directory, creates a long-lived API token, and then uses that token to manage media types, the built-in Report Problems action (disabled by default in the database schema, which silently drops all notifications), user groups, users and custom templates through idempotent JSON-RPC calls. A bundled LXC container template and host item overrides complement the cgroup metrics feature added to zabbix_agent in a companion pull request, letting a fresh installation reach a fully working, notification-capable state without any manual steps through the web interface.

@scibi
scibi force-pushed the feature/zabbix-server-role branch from 68cf208 to 909baeb Compare August 28, 2026 14:54
@scibi
scibi force-pushed the feature/zabbix-server-role branch from 909baeb to 04109b0 Compare September 16, 2026 12:44
@scibi
scibi force-pushed the feature/zabbix-server-role branch 3 times, most recently from 5dc7e20 to 4e4b146 Compare September 29, 2026 23:07
scibi added 13 commits October 6, 2026 16:12
Add a new role that installs and configures Zabbix Server from the
official upstream APT repository, imports the PostgreSQL database
schema idempotently (checked against the 'dbversion' table, not
a marker file so it survives database restores), and configures the
PHP-FPM/nginx web frontend without the interactive setup wizard.

The role also bootstraps the Zabbix API: on a fresh install it changes
the default Admin password to one generated into the DebOps 'secret/'
directory and creates a long-lived API token, then uses that token to
manage media types, the built-in "Report problems" action (disabled by
default in the database schema, which silently drops all
notifications), user groups, users and custom templates
(a bundled "LXC container" template used together with the
'zabbix_agent__cgroup_metrics' feature) through idempotent
create/update JSON-RPC calls. Host item overrides let a template
disable host-wide items made misleading by a more specific one, for
example disabling 'system.cpu.*' on hosts where the LXC template's
own cgroup-based item is linked instead.

Add the 'service/zabbix_server.yml' playbook (following the phpipam
pattern: keyring, apt_preferences, python, php, ferm, nginx,
postgresql, then the role itself) and wire it into 'layer/app.yml',
plus role-index.rst and CHANGELOG.rst entries and the role
documentation (getting-started, defaults-detailed, man pages).

Generated-By: LLM (Claude)
Reformat the bundled 'LXC container' template JSON with
pretty-format-json (matching the project's pre-commit hook config)
and drop a mention of a private inventory group name from its
description, replacing it with a reference to the zabbix_agent
role feature that actually provides the UserParameters.

Generated-By: LLM (Claude)
Stop the 'zabbix-server' systemd service before purging the
'zabbix-server-pgsql'/'zabbix-server-mysql' package on removal,
since the package provides the unit file the earlier unconditional
'stopped' task relied on -- running it afterwards failed with "Could
not find the requested service".

Use the actual configuration-test flag: 'zabbix_server' does not
support '--print', only '-T'/'--test-config', so the config-check
handler could never succeed before restarting the service.

Make 'zabbix_server__trapper_port' actually take effect: 'ListenPort'
was always rendered as a comment, and the ferm rule opened the
'zabbix-trapper' /etc/services entry instead of the configured port,
so a non-default value silently had no effect on the server or the
firewall while the frontend and local facts still advertised it.

Fix the Admin password bootstrap on a fresh install: Zabbix 7.0's
'user.update' expects 'passwd' for the new password, not 'password',
so the JSON-RPC call failed validation and aborted the bootstrap
before a token could ever be created.

Add 'no_log' to the four 'host_item_override.yml' API requests, which
sent the bearer token in the 'Authorization' header without it, and
switch the 'key_regex' filter from Ansible's 'match' test (anchored
to the start of the string) to 'search', matching the documented
Python 're.search()' semantics and avoiding missed, still-enabled
items for any pattern not anchored at position zero.

Wrap the initial PostgreSQL schema import in a single transaction
('psql --single-transaction'), so a failure partway through cannot
leave 'public.dbversion' committed with the rest of the schema
missing -- which would make the next run skip the import entirely
and start the server against a half-imported database.

Set the local facts file mode to 0644: at 0755 'ansible.builtin.setup'
treated the shebang-less rendered JSON as an executable script.

Keep the web frontend converging even when only the frontend itself
is disabled (not the whole role): both the 'php' and 'nginx' role
invocations in the playbook, and the 'zabbix_server' role's own
frontend task file, were skipped entirely whenever
'zabbix_server__frontend_enabled' was False, even though the
underlying 'dependent_pools'/'dependent_upstreams'/'dependent_servers'
variables already encode the desired absent/disabled state -- so
disabling the frontend after enabling it left a stale PHP-FPM pool,
nginx vhost and 'zabbix.conf.php' behind instead of removing them.
Also add fail-fast assertions for the two other ways an enabled
frontend can end up broken at runtime instead of during the run: a
non-PostgreSQL database (the bundled frontend template is PostgreSQL
only) and an invalid 'zabbix_server__frontend_image_format', which is
interpolated directly into a PHP constant name.

Finally, fix the 'zabbix_server__users' example in the getting
started guide, which used 'omit' for 'usrgrpid' and the display name
'Email' for 'mediatypeid' -- both are passed to the Zabbix API as-is
without any name-to-id lookup, so the example would fail as written.

Generated-By: LLM (Claude)
Add the author's copyright line to every file that only listed the
DebOps project, since this is a brand new role and none of its files
had a prior copyright holder to build on.

Generated-By: LLM (Claude)
Reset 'zabbix_server__fact_override_itemids' unconditionally in
'host_item_override.yml': when an override's template does not exist,
or is not linked to any host, the previous 'set_fact' was skipped by
its own 'when', leaving the previous override's item IDs in place --
"Disable matching items" could then act on a completely unrelated
override's items. Also add 'failed_when' to the template/host/item
lookups so a real API error (e.g. an expired token) fails loudly
instead of being treated as "nothing found".

Keep the API token file in 'secret/' on the Ansible Controller: the
'stat' and 'copy' tasks that check for and write it in
'api_bootstrap.yml' were delegated to 'zabbix_server__api_delegate_to',
while the 'lookup(\"file\", ...)' calls that read it always run on the
Controller regardless. With a non-default delegate host this checked
and wrote the token on the wrong host, while every read still looked
on the Controller. Delegate those two file tasks to 'localhost'
explicitly, matching where 'secret/' actually lives.

Generated-By: LLM (Claude)
On a fresh install, user.update of the Admin password invalidates the
session obtained with the stock credentials. Re-login with the new
password before token.create / token.generate so bootstrap does not
fail before the token is written to secret/.

Look up the configured token name before creating a new one, and
generate the secret string against the existing tokenid when a
previous run created the object but then failed to persist it. That
avoids leaving a second enabled, non-expiring token behind on retry.

Derive the frontend $DB['TYPE'] from zabbix_server__database (already
guarded by the PostgreSQL-only frontend assert) instead of hardcoding
POSTGRESQL.

Generated-By: LLM (Claude)
Zabbix 7.0 allows the same token name on different accounts.
token.get filtered only by name, so a missing secret/ file could
regenerate another user's token and persist it as the bootstrap
credential. Resolve the Admin userid on every no-token path and
pass it as userids.

Generated-By: LLM (Grok)
The defaults-detailed note referred to zabbix_server__api_token_content,
which is not a role variable. Point custom lookup tasks at the token
file in secret/ (zabbix_server__api_token_path) and mention the
same-play register fact used by the role tasks.

Generated-By: LLM (Grok)
Hide the template-import API call with no_log so the bearer token and
template source do not leak on failure or verbose output. Always purge
the frontend package on deploy_state=absent, even if the frontend is
currently disabled. Document that the PHP-FPM/nginx step is optional
(enabled by default) and that user group / media type lists
create-or-update by name, while the example IDs stay stable because
they match built-in objects.

Generated-By: LLM (Grok)
The configuration drop-in notifies a restart, and flushing handlers
ran that restart before the schema import. On an empty database
zabbix-server exits immediately and systemd fails the unit. Import
the schema first, and stop the service while the initial import runs
so a crash-loop cannot touch the database.

Generated-By: LLM (Grok 4.7 via Cursor)
configuration.import requires source to be a string, and a templated
YAML body turned the rule booleans into "True"/"False". Send one
to_json document. The method returns true even when nothing changed,
so store a SHA1 of the source that was sent and treat a later run as
unchanged when that checksum is already on the host.

Generated-By: LLM (Grok 4.7 via Cursor)
A token file left over from a destroyed database is rejected with
"Not authorized." Probe the stored token and bootstrap a new one in
that case. Connection failures and any other API error keep the file
and fail the play, so a password changed in the UI is not reset while
the token is still valid.

Generated-By: LLM (Grok 4.7 via Cursor)
The man page translator emits defaults/main.rst for every role. List it
in the toctree, before defaults-detailed, so sphinx-build -n -W accepts
the page.

Generated-By: LLM (Cursor Grok 4.7)
@scibi
scibi force-pushed the feature/zabbix-server-role branch from 4e4b146 to 4695b99 Compare October 6, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant