luminare.infra_edge · v0.2.1

Configuration

Each role of the collection is configured through its own variables. Every variable starts with the role name (for example <role>_enabled), has its default in roles/<role>/defaults/main.yml and is described, with its type, in roles/<role>/meta/argument_specs.yml, which Ansible checks when the role starts. Each role gets a section on this page with a table of its options (variable, type, default, description), generated from that file with python3 tests/role_docs.py --write; a unit test fails when a table here or in a role README is out of date.

Playbook luminare.infra_edge.edge

luminare.infra_edge.edge runs the roles below in this order: base, cloudflared, nginx, mariadb and, when switched on, restic_backup. Its own variables are the host pattern and the switch for the backups; it passes nothing to the roles: every value comes from the inventory, as role variables. examples/edge/ is a complete inventory for one host, with the backups switched on (tested by the Molecule scenario edge); a unit test fails when it sets a variable that neither the playbook nor one of its roles has.

Variable Type Default Description
edge_hosts str edge Host pattern of the play: an inventory group, a host or any pattern, for example -e edge_hosts=web. --limit narrows it further.
edge_restic_backup_enabled bool false Also run restic_backup after the other roles. Off by default, so an inventory without backup settings sets up the same host as before; switching it off later leaves the installed backup job running (set restic_backup_timers_enabled: false and run the playbook once more with the switch on to stop it).

Each role’s tasks carry the role name as a tag, so --skip-tags mariadb runs the playbook without the database role. With --tags, the input checks of all roles that run still run (tag always), so their required values (SSH keys, tunnel token, database passwords, the backup repository and its password) must be set.

base

luminare.infra_edge.base hardens the host: time zone and locales, unattended security upgrades, the admin and automation users with SSH keys, the SSH server (through devsec.hardening.ssh_hardening), the nftables firewall, fail2ban and the journal limits. base_admin_ssh_keys, base_automation_ssh_keys and, with the default base_ssh_allow_mode, base_mgmt_cidrs must be set; the role stops before any change otherwise. Every other devsec ssh_* variable can be set in the inventory as well.

Variable Type Default Description
base_timezone str Europe/Vienna Time zone of the host, a name from the tz database.
base_locales list of str ["en_US.UTF-8"] Locales to generate.
base_default_locale str en_US.UTF-8 System-wide LANG in /etc/default/locale; must be one of base_locales.
base_hostname str {{ inventory_hostname }} Host name to set, and to map to 127.0.1.1 in /etc/hosts. An empty string leaves both unchanged. Must be a valid host name (RFC 1123: letters, digits and hyphens in dot-separated labels, at most 64 characters) and not an IP address; the role stops before any change otherwise. Set it, or set it to “”, when the inventory names hosts by IP address or with underscores.
base_manage_hosts_file bool true Keep a 127.0.1.1 line for base_hostname in /etc/hosts.
base_timesyncd_enabled bool true Install systemd-timesyncd for time synchronisation. Set false on hosts that use chrony or ntpsec, which the package would replace.
base_qemu_guest_agent bool false Install qemu-guest-agent (for virtual machines on a QEMU/KVM hypervisor).
base_unattended_upgrades_enabled bool true Install security updates every day with unattended-upgrades. False keeps the package but turns the daily run off.
base_unattended_upgrades_origins list of str [] Origins-Pattern entries that unattended-upgrades may install from. Empty means the security archive of the distribution only.
base_unattended_reboot bool false Reboot automatically when an update requires it.
base_unattended_reboot_time str 04:30 Time of day (HH:MM) for the automatic reboot when base_unattended_reboot is true.
base_admin_user str admin Name of the personal admin account. It may use sudo with its password.
base_admin_ssh_keys list of str [] Public SSH keys of the admin user. Required (at least one), because password logins are off; the list replaces all other keys of the user.
base_admin_password_hash str "" SHA-512 crypt hash (openssl passwd -6) of the admin password, needed for sudo and the console. Empty leaves the password unchanged; a new user then has a locked password. Keep the value in a vault.
base_automation_user str ansible Name of the account that Ansible and other automation log in with.
base_automation_ssh_keys list of str [] Public SSH keys of the automation user. Required (at least one); the list replaces all other keys of the user.
base_automation_sudo_nopasswd bool true Let the automation user use sudo without a password. False makes sudo ask for the user’s password, which this role does not set (a new account keeps a locked password), so sudo stays unusable for it until a password is set by other means.
base_ssh_group str sshusers Group whose members may log in over SSH (AllowGroups). The admin and the automation user are added to it; other roles add their own users.
base_ssh_hardening_enabled bool true Harden the SSH server with devsec.hardening.ssh_hardening (key-only login, no root login, AllowGroups, MaxAuthTries).
base_ssh_port int 22 Port of the SSH server, used by the SSH server, the firewall rule and the fail2ban jail.
base_ssh_listen_addresses list of str ["0.0.0.0", "::"] Addresses the SSH server listens on (ListenAddress). The default covers IPv4 and IPv6; an address family the host does not have is skipped by the SSH server.
base_sshd_max_auth_tries int 3 MaxAuthTries of the SSH server.
base_os_hardening_enabled bool false Also apply devsec.hardening.os_hardening (sysctl, PAM, login.defs, file permissions). Opt-in; read the role README first, it turns IP forwarding off.
base_firewall_enabled bool true Write and load the nftables firewall (input and forward policy drop) with the drop-in directory /etc/nftables.d. Removes ufw.
base_ssh_allow_mode str: mgmt_cidrs, loopback, any mgmt_cidrs Where the firewall accepts SSH from. mgmt_cidrs means only from base_mgmt_cidrs, loopback means no SSH rule (only local or tunnelled access), any means from everywhere.
base_mgmt_cidrs list of str [] IPv4 and IPv6 networks (CIDR) of the management hosts. Required when base_ssh_allow_mode is mgmt_cidrs; fail2ban never bans them.
base_fail2ban_enabled bool true Install fail2ban with an SSH jail (systemd journal backend, nftables ban action). Also installs nftables for the ban action when base_firewall_enabled is false; fail2ban then adds its own nftables table and leaves the other rules of the host alone.
base_fail2ban_maxretry int 5 Failed SSH logins within base_fail2ban_findtime that cause a ban.
base_fail2ban_findtime str 10m Time window for counting failed SSH logins (fail2ban time format).
base_fail2ban_bantime str 1h How long an address stays banned (fail2ban time format).
base_journald_persistent bool true Keep the journal on disk (Storage=persistent) instead of in memory.
base_journald_system_max_use str 500M Disk space limit of the journal (SystemMaxUse).
base_journald_max_retention str 14day Maximum age of journal entries (MaxRetentionSec).

cloudflared

luminare.infra_edge.cloudflared runs the connector of a remotely managed tunnel: a pinned cloudflared version (package file checked against its SHA-256, held with an apt pin), the tunnel token in a file that only the service user can read, and a sandboxed systemd service with its metrics listener on 127.0.0.1:2000. cloudflared_token must be set; by default it is read from the environment variable TUNNEL_TOKEN of the control node. cloudflared_version and cloudflared_deb_sha256 change together.

Variable Type Default Description
cloudflared_version str 2026.10.2 cloudflared version to install and hold with an apt pin. Change it together with cloudflared_deb_sha256.
cloudflared_deb_sha256 dict {"amd64": "30a9a127c24ea8365fa8e11b13684128f34dd8a1137599aa00b4bebda1a0a083", "arm64": "2e5335ea0ef2fa98e5354357e77aaf453f3756d1f9023cf5b063409e1fb3baca"} SHA-256 of the package file of cloudflared_version per Debian architecture (the checksums of cloudflared-linux-amd64.deb and cloudflared-linux-arm64.deb in the GitHub release). The role stops on a host whose architecture has no entry, and when the downloaded file does not match.
cloudflared_repository_url str https://pkg.cloudflare.com/cloudflared Cloudflare’s apt repository. The package file of the pinned version comes from its pool (pool/main/c/cloudflared/), and the apt source points to it.
cloudflared_apt_repository_enabled bool true Add the signed apt repository (key in /usr/share/keyrings/cloudflare-main.gpg, source in /etc/apt/sources.list.d/cloudflared.list), so that apt policy cloudflared shows newer versions; the apt pin keeps the installed one. False leaves the apt sources unchanged.
cloudflared_apt_key_url str https://pkg.cloudflare.com/cloudflare-main.gpg Signing key of the apt repository. It is downloaded on every run and replaced when it changed, so a new key from Cloudflare reaches the host with the next run.
cloudflared_token str {{ lookup('ansible.builtin.env', 'TUNNEL_TOKEN') }} Token of the remotely managed tunnel (from the tunnel’s install command in the Cloudflare dashboard or from the API). Required. The default reads the environment variable TUNNEL_TOKEN of the control node; never put the token into the inventory unencrypted. It is written to /etc/cloudflared/token (mode 0600, owner cloudflared_user) and never appears on a command line, in the unit or in Ansible output.
cloudflared_user str cloudflared System user and group the connector runs as. Created without a home directory or login shell.
cloudflared_metrics_address str 127.0.0.1:2000 host:port of the metrics and readiness listener (/metrics, /ready). Keep it on the loopback address or a private address; the role refuses an all-addresses listener.
cloudflared_service_enabled bool true Enable and start the connector. With true, a start or restart waits until the tunnel has a connection (systemd Type=notify) and fails after 30 seconds without one. False installs and configures everything but keeps the service stopped and disabled.
cloudflared_extra_args list of str [] Extra options of “cloudflared tunnel”, placed before “run” (for example –protocol http2, –edge-ip-version 4 or –loglevel debug). Only letters, digits and . _ : / = @ , + - are allowed; –token and –metrics are refused (the role sets the token file and the metrics address).

nginx

luminare.infra_edge.nginx runs nginx as the reverse proxy behind the tunnel: one site per application on a loopback listener, the client address from the connector’s CF-Connecting-IP header (trusted only from 127.0.0.1), a JSON access log, no version in the Server header, security headers, websockets and, optionally, TCP and UDP relays with the stream module. Each item of nginx_sites needs name, server_name and upstream; listen defaults to 127.0.0.1:8080 and must stay on a loopback address.

Variable Type Default Description
nginx_package str nginx Package that provides nginx (Debian 13 ships 1.26, Ubuntu 24.04 ships 1.24).
nginx_sites list of dict [] Reverse proxy sites, one per application. Keys: name (file name in /etc/nginx/sites-available, lower-case letters, digits, - and _), server_name (one name or several separated by spaces), listen (a loopback address with a port, default 127.0.0.1:8080; other addresses are refused), upstream (http:// URL of the application) and websocket (true passes Upgrade requests through, default false). A site that the role wrote earlier and that is no longer in the list is removed.
nginx_remove_default_site bool true Remove the package’s default site (/etc/nginx/sites-enabled/default), which listens on port 80 of every address.
nginx_real_ip_from list of str ["127.0.0.1"] Addresses or networks whose nginx_real_ip_header is trusted as the client address. Only the tunnel connector on the same host belongs here; a request from any other address keeps its own address, whatever the header says. The same list decides whose X-Forwarded-Proto is passed on.
nginx_real_ip_header str CF-Connecting-IP Request header that holds the client address (set by the tunnel connector).
nginx_server_tokens str: off, on off “off” sends “Server: nginx” without the version, also on error pages. Quote the value in YAML.
nginx_client_max_body_size str 16m Largest request body nginx accepts (client_max_body_size, for example 16m or 1g).
nginx_gzip bool true Compress text, JSON, JavaScript, XML and SVG responses of at least 256 bytes, also proxied ones.
nginx_json_access_log bool true Write /var/log/nginx/access.log (and stream-access.log) as one JSON object per line. False uses nginx’s combined format. The log holds client addresses; logrotate keeps 14 days.
nginx_websocket_read_timeout str 1h How long a proxied websocket may stay idle on sites with websocket true (proxy_read_timeout); other sites keep nginx’s 60 seconds.
nginx_security_headers dict {"X-Content-Type-Options": "nosniff", "X-Frame-Options": "SAMEORIGIN", "Referrer-Policy": "strict-origin-when-cross-origin"} Response headers (name: value) added to every response that does not already carry the header from the application, error pages included. No Strict-Transport-Security: TLS ends at the tunnel’s edge, which sets it. Values must not contain double quotes, backslashes or $.
nginx_stream_enabled bool false Install the stream module and relay the ports in nginx_stream_upstreams. False removes the relays from the configuration (the module package stays installed).
nginx_stream_upstreams list of dict [] TCP and UDP relays of the stream module, for example to a game server on another host. Keys: name (lower-case letters, digits and _), listen (a port, or address:port), upstream (host:port) and protocols (list of tcp and udp, default [tcp]). A relay listens on a public address, so the role opens its ports in the firewall drop-in.
nginx_firewall_manage bool false false writes /etc/nftables.d/20-nginx.nft only when /etc/nftables.d exists (the firewall of the base role); true also creates the directory (nftables and /etc/nftables.conf must exist). The file holds accept rules for the relay ports and is removed when there is no relay; a running nftables is restarted after a change. Sites listen on loopback addresses and need no rule.
nginx_firewall_sources list of str ["0.0.0.0/0", "::/0"] Source networks that may reach the relay ports (IPv4 and IPv6 CIDRs). 0.0.0.0/0 and ::/0 together mean everyone. An empty list writes no accept rule.

mariadb

luminare.infra_edge.mariadb runs the distribution’s MariaDB server: root on unix_socket login without a password, anonymous accounts, the test database and remote root accounts removed, a tuning file (bind address 127.0.0.1 by default, character set, InnoDB sizes, connections, slow query log), the databases and accounts of mariadb_databases, nightly zstd dumps per database into /var/backups/mariadb with a retention in days, and optionally port 3306 for application hosts in the firewall drop-in directory of the base role. An item of mariadb_databases with a user also needs its password (from Ansible Vault or SOPS).

Variable Type Default Description
mariadb_packages list of str ["mariadb-server", "python3-pymysql", "zstd"] Packages to install: the server (MariaDB 11.8 on Debian 13, 10.11 on Ubuntu 24.04), the Python driver that the ansible.mariadb modules need on the host, and zstd for the dumps. Add packages, do not remove these.
mariadb_bind_address str 127.0.0.1 The one IPv4 or IPv6 address the server listens on (port 3306). The default keeps it on the host; set a private address for application hosts and list them in mariadb_allowed_app_hosts. The socket /run/mysqld/mysqld.sock always works.
mariadb_databases list of dict [] Databases and their accounts. Keys: name (database; letters, digits and _), user and password (optional account; both or neither), host (where the account may connect from, default localhost, the socket) and priv (privileges in the ansible.mariadb.mariadb_user format, default all privileges on its own database, for example app.*:ALL for the database app). A changed password or priv is applied on the next run. Databases and accounts that leave the list are not dropped.
mariadb_innodb_buffer_pool_ratio float 0.25 Share of the host’s memory for the InnoDB buffer pool, used by the default of mariadb_innodb_buffer_pool_size (0.25 when the host also runs applications; 0.5 to 0.7 on a host for the database only; at most 0.9).
mariadb_innodb_buffer_pool_size str {{ (ansible_facts['memtotal_mb'] * mariadb_innodb_buffer_pool_ratio) | int }}M Size of the InnoDB buffer pool (a number with K, M or G). The default is the host’s memory times mariadb_innodb_buffer_pool_ratio.
mariadb_innodb_log_file_size str 256M Size of the InnoDB redo log (a number with K, M or G).
mariadb_max_connections int 100 Most client connections at the same time.
mariadb_character_set_server str utf8mb4 Character set of new databases and tables.
mariadb_collation_server str "" Collation of new databases and tables. Empty keeps the package’s utf8mb4 collation (utf8mb4_uca1400_ai_ci on Debian 13, utf8mb4_general_ci on Ubuntu 24.04); another character set needs a collation of that set here.
mariadb_slow_query_log bool true Log statements that run longer than mariadb_long_query_time to /var/log/mysql/mariadb-slow.log (the statements’ text; rotated by the package’s logrotate rule).
mariadb_long_query_time float 1 Seconds after which a statement counts as slow.
mariadb_dump_enabled bool true Run the dump timer. False stops and disables it; the script and the service stay, so a dump can still be started by hand, and existing dumps are kept.
mariadb_dump_dir str /var/backups/mariadb Folder of the dumps (owner root, mode 0700). Every file in it that ends in .sql.zst counts as a dump for the retention.
mariadb_dump_oncalendar str *-*-* 03:15:00 When the dumps run (systemd OnCalendar expression, local time of the host). A run missed while the host was off follows at the next boot.
mariadb_dump_retention_days int 2 Days a dump is kept. After a successful run, dumps older than this are deleted; a failed run deletes nothing.
mariadb_allowed_app_hosts list of str [] IPv4 and IPv6 addresses or CIDRs of the application hosts that may reach port 3306 through the firewall of the base role (never /0). The server must also listen on an address they reach (mariadb_bind_address), and their accounts need a matching host.
mariadb_firewall_manage bool false false writes /etc/nftables.d/30-mariadb.nft only when /etc/nftables.d exists (the firewall of the base role); true also creates the directory (nftables and /etc/nftables.conf must exist). The file holds accept rules for port 3306 from mariadb_allowed_app_hosts and is removed when the list is empty; a running nftables is restarted after a change.

restic_backup

luminare.infra_edge.restic_backup backs up the host every night with restic: the distribution’s package (or a pinned upstream release checked against its SHA-256), the repository, its password and the backend credentials in root-only files under /etc/restic, the repository created if it does not exist, a sandboxed backup service with a timer at 03:45 (after the dumps of mariadb), forget --prune with a retention for the snapshots of the host, a monthly restic check, the time of the last successful run in a status file and optionally as a node_exporter metric, and a restore test. restic_backup_repository and restic_backup_password must be set; the role stops before any change otherwise. When the repository exists but the password does not open it, or the backend refuses the settings, the role stops before it writes them, so the settings files of a working backup stay as they are.

Variable Type Default Description
restic_backup_install_method str: package, binary package package installs the distribution’s restic (0.18 on Debian 13, 0.16 on Ubuntu 24.04, with the distribution’s security updates); binary installs the upstream release restic_backup_version as /usr/local/bin/restic after checking its SHA-256 (restic_backup_binary_sha256). An installed package stays installed with binary; the jobs use the binary of the chosen method.
restic_backup_version str 0.19.1 Upstream release for restic_backup_install_method binary (x.y.z). Change it together with restic_backup_binary_sha256.
restic_backup_binary_sha256 dict {"amd64": "f415415624dcc452f2a02b8c33641791a8c6d6d3b65bbb3543fcf9a25151585c", "arm64": "a5f64aaab53d51e311fa3829124c5b703f2d14cf187d8640b6be3b2b49376465"} SHA-256 of the release file per architecture (amd64, arm64; for example restic_0.19.1_linux_amd64.bz2), as listed in the signed SHA256SUMS file of the release. Used only with the binary method; the role stops on a host whose architecture has no entry, and when the downloaded file does not match.
restic_backup_repository str "" Required. The restic repository of this host, for example s3:https://s3.example.com/backups/edge-01, sftp:backup@example.com:/srv/restic/edge-01, rest:https://backup.example.com/edge-01/ or a local path such as /srv/restic-repo (a local path is for tests or a second disk, not an off-host backup). Give every host its own repository.
restic_backup_password str "" Required. Password of the repository, written to /etc/restic/password (root only). Without it the snapshots cannot be read: keep a copy outside the host and the repository. Keep it encrypted (Ansible Vault, SOPS); no line break and no spaces at either end.
restic_backup_env dict {} Environment variables for the backend, written to /etc/restic/env (root only), for example AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for an S3 bucket. Names in capitals; not the RESTIC_REPOSITORY*, RESTIC_PASSWORD* and RESTIC_CACHE_DIR variables, which the role sets itself. Keep the values encrypted (Ansible Vault, SOPS).
restic_backup_paths list of str ["/etc", "/var/backups"] Absolute paths to back up (the default includes /var/backups/mariadb, the dumps of the mariadb role). Every path must exist when the backup runs: a missing path fails the run instead of being skipped. Folders tagged as caches (CACHEDIR.TAG) are left out.
restic_backup_exclude list of str [] restic exclude patterns, one per line of /etc/restic/excludes, for example /var/backups/*.tmp or *.log. restic replaces $NAME with the environment variable NAME; write $$ for a dollar sign.
restic_backup_keep dict {"daily": 7, "weekly": 4, "monthly": 6} Retention of restic forget –prune after every backup, applied to the snapshots of this host only: how many of the newest snapshots to keep per kind (last, hourly, daily, weekly, monthly, yearly; at least one kind). The default keeps one snapshot per day for 7 days, per week for 4 weeks and per month for 6 months.
restic_backup_timers_enabled bool true Run the backup and check timers. False stops and disables both; the services stay, so a backup can still be started by hand, and the repository is kept.
restic_backup_oncalendar str *-*-* 03:45:00 When the backup runs (systemd OnCalendar expression, local time of the host); the default follows the dumps of the mariadb role at 03:15. A run missed while the host was off follows at the next boot.
restic_backup_check_oncalendar str monthly When restic check runs (systemd OnCalendar expression; monthly is the first day of each month at 00:00). A run missed while the host was off follows at the next boot.
restic_backup_check_read_data_subset str "" Part of the stored data that the monthly check also reads and verifies (restic check –read-data-subset), as a percentage (for example 5%, a random part on each run) or a part n/t (for example 1/12). Empty checks the structure of the repository without reading the data.
restic_backup_textfile_dir str "" Folder of the node_exporter textfile collector (for example /var/lib/prometheus/node-exporter of Debian’s prometheus-node-exporter package). When set, every successful run writes restic_backup.prom with the metric restic_backup_last_success_timestamp_seconds there; the role creates the folder if it is missing. Empty writes no metric.
restic_backup_max_age_hours int 26 The restore test fails when the newest snapshot of the host is older than this many hours. The default allows one nightly run plus two hours; an alert on the metric can use the same limit.
restic_backup_restore_test_include list of str [] Paths that the restore test restores and compares, each inside one of restic_backup_paths. Empty restores the whole snapshot, which needs that much free space in restic_backup_restore_test_dir.
restic_backup_restore_test_dir str /var/tmp Existing folder in which the restore test creates its temporary folder (mode 0700); the folder is removed after the test.

Secrets

Secrets never go into this repository, an issue or a log. A role takes a secret only as a variable (from Ansible Vault, a SOPS-encrypted vars file or an environment lookup in your own inventory), never as a default value, and every task that handles a secret sets no_log: true. CI scans every commit with gitleaks; a leaked secret is rotated first and removed from history second.

docs/configuration.md on GitHub

Licensed under Apache-2.0. Source: luminare-dev/ansible-collection-infra_edge at v0.2.1.