diff --git a/.github/workflows/check_eol.yml b/.github/workflows/check_eol.yml
index b2289976..fb371f27 100644
--- a/.github/workflows/check_eol.yml
+++ b/.github/workflows/check_eol.yml
@@ -7,6 +7,7 @@ on:
permissions:
contents: read
+ issues: write
jobs:
check-eol:
@@ -61,14 +62,13 @@ jobs:
}
echo "=== Checking OS distributions ==="
- check_eol "rocky-linux" "8" "Rocky Linux 8"
check_eol "rocky-linux" "9" "Rocky Linux 9"
check_eol "rocky-linux" "10" "Rocky Linux 10"
- check_eol "debian" "11" "Debian 11 (Bullseye)"
check_eol "debian" "12" "Debian 12 (Bookworm)"
check_eol "debian" "13" "Debian 13 (Trixie)"
check_eol "ubuntu" "22.04" "Ubuntu 22.04 (Jammy)"
check_eol "ubuntu" "24.04" "Ubuntu 24.04 (Noble)"
+ check_eol "ubuntu" "26.04" "Ubuntu 26.04 (Resolute Raccoon)"
echo ""
echo "=== Checking Python versions ==="
diff --git a/.github/workflows/test_elasticsearch_custom_certs.yml b/.github/workflows/test_elasticsearch_custom_certs.yml
index 7dcc9e42..30c9f729 100644
--- a/.github/workflows/test_elasticsearch_custom_certs.yml
+++ b/.github/workflows/test_elasticsearch_custom_certs.yml
@@ -42,5 +42,5 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["elasticsearch_custom_certs","elasticsearch_custom_certs_minimal"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
secrets: inherit
diff --git a/.github/workflows/test_elasticsearch_modules.yml b/.github/workflows/test_elasticsearch_modules.yml
index 4bf49846..bc844792 100644
--- a/.github/workflows/test_elasticsearch_modules.yml
+++ b/.github/workflows/test_elasticsearch_modules.yml
@@ -42,6 +42,6 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["elasticsearch_test_modules"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
timeout: 20
secrets: inherit
diff --git a/.github/workflows/test_full_stack.yml b/.github/workflows/test_full_stack.yml
index 0fcae443..f64d1d5b 100644
--- a/.github/workflows/test_full_stack.yml
+++ b/.github/workflows/test_full_stack.yml
@@ -12,6 +12,10 @@ on:
- info
- warning
- debug
+ distros:
+ description: 'JSON array of distros (empty = all)'
+ required: false
+ type: string
pull_request:
merge_group:
schedule:
@@ -80,7 +84,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- distro: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && fromJSON('["rockylinux9","debian13"]') || fromJSON('["rockylinux9","ubuntu2204","ubuntu2404","debian12","debian13"]') }}
+ distro: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && fromJSON('["rockylinux9","debian13"]') || (inputs.distros != '' && fromJSON(inputs.distros)) || fromJSON('["rockylinux9","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]') }}
scenario:
- elasticstack_default
- es_kibana
diff --git a/.github/workflows/test_role_beats.yml b/.github/workflows/test_role_beats.yml
index c2ffa9f9..0c09230b 100644
--- a/.github/workflows/test_role_beats.yml
+++ b/.github/workflows/test_role_beats.yml
@@ -42,5 +42,5 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["beats_default","beats_peculiar","beats_advanced","beats_security"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
secrets: inherit
diff --git a/.github/workflows/test_role_elasticsearch.yml b/.github/workflows/test_role_elasticsearch.yml
index 7f4655e4..48b2e087 100644
--- a/.github/workflows/test_role_elasticsearch.yml
+++ b/.github/workflows/test_role_elasticsearch.yml
@@ -42,7 +42,7 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["elasticsearch_default","elasticsearch_roles_calculation","elasticsearch_custom","elasticsearch_no-security","elasticsearch_cert_content"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
secrets: inherit
molecule_elasticsearch_diagnostics:
diff --git a/.github/workflows/test_role_kibana.yml b/.github/workflows/test_role_kibana.yml
index 825b60ba..bd064b85 100644
--- a/.github/workflows/test_role_kibana.yml
+++ b/.github/workflows/test_role_kibana.yml
@@ -42,5 +42,5 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["kibana_default","kibana_custom","kibana_custom_certs"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
secrets: inherit
diff --git a/.github/workflows/test_role_logstash.yml b/.github/workflows/test_role_logstash.yml
index 77697285..bf6c31ef 100644
--- a/.github/workflows/test_role_logstash.yml
+++ b/.github/workflows/test_role_logstash.yml
@@ -42,5 +42,5 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["logstash_default","logstash_ssl","logstash_custom_pipeline","logstash_advanced","logstash_standalone_certs","logstash_centralized_pipelines"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
secrets: inherit
diff --git a/.github/workflows/test_role_repos.yml b/.github/workflows/test_role_repos.yml
index 6e447566..cf0c3a47 100644
--- a/.github/workflows/test_role_repos.yml
+++ b/.github/workflows/test_role_repos.yml
@@ -42,6 +42,6 @@ jobs:
uses: ./.github/workflows/molecule.yml
with:
scenarios: '["repos_default"]'
- distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","debian12","debian13"]' }}
+ distros: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '["rockylinux10","debian13"]' || '["rockylinux9","rockylinux10","ubuntu2204","ubuntu2404","ubuntu2604","debian12","debian13"]' }}
timeout: 30
secrets: inherit
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..be546595
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,52 @@
+# Changelog
+
+## 0.1.0 (unreleased)
+
+First release of the `oddly.elasticstack` collection, forked from
+[NETWAYS/ansible-collection-elasticstack](https://github.com/NETWAYS/ansible-collection-elasticstack).
+
+### Highlights
+
+- Full Elastic Stack 9.x support alongside 8.x, with version-aware templates
+ that switch configuration syntax automatically.
+- Rolling upgrades from 8.x to 9.x with upgrade path validation, per-node
+ orchestration, and shard allocation management.
+- Complete TLS PKI: CA generation, per-node certificates, automatic renewal,
+ and support for external certificates (PEM and PKCS12) with format
+ auto-detection.
+- Inline PEM content variables for Vault / secrets manager integration
+ (Elasticsearch, Kibana, Beats).
+- Separate transport and HTTP layer certificates on Elasticsearch.
+- Certificate expiry warnings and tag-driven renewal (`--tags certificates`,
+ `--tags renew_es_cert`, `--tags renew_ca`).
+- Logstash standalone certificate mode for independent deployments.
+- Beats Filebeat `filestream` input type for 9.x (replacing deprecated `log`).
+- Logstash `elastic_agent` input plugin support.
+- Elasticsearch `cluster_settings` for runtime cluster configuration via API.
+- LogsDB support with automatic enablement on fresh 9.x installs.
+- Elasticsearch filesystem snapshot repository configuration.
+- Cgroup-aware JVM heap auto-calculation for containerised deployments.
+- Container workarounds: systemd `Type=exec` override, lenient disk watermarks.
+- `elasticsearch_extra_config`, `kibana_extra_config`, `logstash_extra_config`
+ for arbitrary YAML settings without dedicated variables.
+- `elasticstack_cert_pass` shortcut to set all TLS key passphrases at once.
+- MkDocs Material documentation site with task flow diagrams, TLS guide, and
+ recipes.
+
+### Supported platforms
+
+- Debian 12 (bookworm), 13 (trixie)
+- Ubuntu 22.04 (jammy), 24.04 (noble), 26.04 (resolute)
+- Rocky Linux / RHEL 9, 10
+- Elastic Stack 8.x and 9.x
+- Ansible 2.18+
+
+### Breaking changes from netways.elasticstack
+
+- Namespace changed from `netways` to `oddly`.
+- Minimum Ansible version raised to 2.18 (was 2.9).
+- Debian 10/11 and Ubuntu 20.04 dropped from supported platforms.
+- `elasticsearch_security: false` no longer permitted on ES 8.x+ (Elastic
+ upstream requirement).
+- Logstash `logstash_beats_tls` renamed to `logstash_input_beats_ssl` (old name
+ still accepted for backwards compatibility).
diff --git a/README.md b/README.md
index 00b80099..ce8e3f81 100644
--- a/README.md
+++ b/README.md
@@ -99,9 +99,9 @@ See the **[getting started guide](https://oddly.github.io/elasticstack/getting-s
| | Versions |
|-|----------|
-| Debian | 11, 12, 13 |
-| Ubuntu | 22.04, 24.04 |
-| Rocky Linux / RHEL | 8, 9, 10 |
+| Debian | 12, 13 |
+| Ubuntu | 22.04, 24.04, 26.04 |
+| Rocky Linux / RHEL | 9, 10 |
| Elastic Stack | 8.x, 9.x |
| Ansible | 2.18+ |
diff --git a/docs/index.md b/docs/index.md
index d8120a71..a82d83df 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -23,7 +23,7 @@ ansible-galaxy collection install oddly.elasticstack
- **Automatic TLS** — generates and distributes certificates for inter-node and HTTP encryption
- **Security bootstrapping** — creates users, roles, and passwords on first run
- **Rolling upgrades** — 8.x to 9.x with zero-downtime node-by-node upgrades
-- **Multi-platform** — Debian 11-13, Ubuntu 22.04/24.04, Rocky Linux/RHEL 8-10
+- **Multi-platform** — Debian 12-13, Ubuntu 22.04/24.04/26.04, Rocky Linux/RHEL 9-10
diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md
index ba0277b2..9a52f473 100644
--- a/docs/introduction/getting-started.md
+++ b/docs/introduction/getting-started.md
@@ -3,7 +3,7 @@
## Prerequisites
- Ansible 2.18 or later
-- Target hosts running Debian 11/12/13, Ubuntu 22.04/24.04, or Rocky Linux/RHEL 8/9/10
+- Target hosts running Debian 12/13, Ubuntu 22.04/24.04/26.04, or Rocky Linux/RHEL 9/10
- SSH access to target hosts with root or sudo privileges
- Minimum 4 GB RAM per Elasticsearch node (8 GB recommended)
- Python 3 on all target hosts (with `python3-apt` on Debian/Ubuntu)
diff --git a/molecule/shared/create.yml b/molecule/shared/create.yml
index 1f826b68..9a5ff360 100644
--- a/molecule/shared/create.yml
+++ b/molecule/shared/create.yml
@@ -52,6 +52,7 @@
rockylinux10: "molecule-rockylinux-10"
ubuntu2204: "molecule-ubuntu-22.04"
ubuntu2404: "molecule-ubuntu-24.04"
+ ubuntu2604: "molecule-ubuntu-26.04"
tasks:
- name: Delete any existing containers with same names
diff --git a/roles/beats/defaults/main.yml b/roles/beats/defaults/main.yml
index 57d28622..73d1275f 100644
--- a/roles/beats/defaults/main.yml
+++ b/roles/beats/defaults/main.yml
@@ -200,9 +200,6 @@ beats_metricbeat_loadbalance: true
beats_cert_validity_period: 1095
# @var beats_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal
beats_cert_expiration_buffer: 30
-# @var beats_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually
-beats_cert_will_expire_soon: false
-
# @var beats_cert_source:description: Where to get TLS certificates. 'elasticsearch_ca' (default) auto-generates from the built-in CA; 'external' uses user-provided files
beats_cert_source: elasticsearch_ca
diff --git a/roles/beats/meta/main.yml b/roles/beats/meta/main.yml
index 02e51807..b44eab8d 100644
--- a/roles/beats/meta/main.yml
+++ b/roles/beats/meta/main.yml
@@ -10,7 +10,6 @@ galaxy_info:
platforms:
- name: EL
versions:
- - "8"
- "9"
- "10"
- name: Debian
@@ -21,6 +20,7 @@ galaxy_info:
versions:
- jammy
- noble
+ - resolute
galaxy_tags:
- elk
- beats
diff --git a/roles/beats/tasks/beats-security.yml b/roles/beats/tasks/beats-security.yml
index 4ecf94ad..bc4ed2b4 100644
--- a/roles/beats/tasks/beats-security.yml
+++ b/roles/beats/tasks/beats-security.yml
@@ -10,42 +10,20 @@
# -- Detect content mode vs file mode --
- name: beats-security | Detect content mode for Beats
- ansible.builtin.set_fact:
- _beats_content_mode: "{{ beats_tls_certificate_content | default('', true) | length > 0 }}"
-
- - name: beats-security | Validate certificate is provided
- ansible.builtin.assert:
- that:
- - (beats_tls_certificate_file | length > 0) or
- (beats_tls_certificate_content | default('', true) | length > 0)
- fail_msg: >-
- External cert mode requires at minimum:
- beats_tls_certificate_file or beats_tls_certificate_content
-
- # -- Content mode: set format to PEM --
- - name: beats-security | Set format facts for content mode
- ansible.builtin.set_fact:
- _beats_cert_format: pem
- _beats_ca_extracted: false
- when: _beats_content_mode | bool
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/../elasticstack/tasks/certs/cert_detect_content_mode.yml"
+ vars:
+ _detect_cert_content_var: "{{ beats_tls_certificate_content | default('', true) }}"
+ _detect_key_content_var: "{{ beats_tls_key_content | default('', true) }}"
+ _detect_cert_file_var: "{{ beats_tls_certificate_file }}"
+ _detect_service_name: Beats
+ _detect_key_var_name: beats_tls_key_content
- - name: beats-security | Check for CA chain in certificate content
+ - name: beats-security | Map detection results to Beats facts
ansible.builtin.set_fact:
- _beats_ca_extracted: true
- when:
- - _beats_content_mode | bool
- - >-
- beats_tls_certificate_content | default('')
- | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1
-
- - name: beats-security | Validate key content is provided (content mode)
- ansible.builtin.assert:
- that:
- - beats_tls_key_content | default('', true) | length > 0
- fail_msg: >-
- Content mode requires both certificate and key content.
- Set beats_tls_key_content.
- when: _beats_content_mode | bool
+ _beats_content_mode: "{{ _detect_content_mode }}"
+ _beats_cert_format: "{{ _detect_cert_format | default(omit) }}"
+ _beats_ca_extracted: "{{ _detect_ca_extracted | default(false) }}"
# -- File mode: validate and detect format --
- name: beats-security | Validate Beats certificate (file mode)
diff --git a/roles/beats/templates/_beats_logging.j2 b/roles/beats/templates/_beats_logging.j2
new file mode 100644
index 00000000..2bf0fa81
--- /dev/null
+++ b/roles/beats/templates/_beats_logging.j2
@@ -0,0 +1,9 @@
+{% if beats_logging == "file" %}
+logging.level: {{ beats_loglevel }}
+logging.to_files: true
+logging.files:
+ path: {{ beats_logpath }}
+ name: {{ _beat_name }}
+ keepfiles: {{ beats_logging_keepfiles }}
+ permissions: {{ beats_logging_permissions }}
+{% endif %}
diff --git a/roles/beats/templates/_beats_output.j2 b/roles/beats/templates/_beats_output.j2
new file mode 100644
index 00000000..04b93e74
--- /dev/null
+++ b/roles/beats/templates/_beats_output.j2
@@ -0,0 +1,38 @@
+{% if _beat_output == "elasticsearch" %}
+output.elasticsearch:
+{% if beats_security | bool %}
+{% if elasticstack_full_stack | bool %}
+ hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% else %}
+ hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% endif %}
+ username: "elastic"
+ password: "{{ beats_writer_password.stdout }}"
+ ssl.enabled: true
+ ssl.verification_mode: {{ beats_ssl_verification_mode }}
+ ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"]
+{% else %}
+{% if elasticstack_full_stack | bool %}
+ hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% else %}
+ hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% endif %}
+{% endif %}
+{% endif %}
+{% if _beat_output == "logstash" %}
+output.logstash:
+{% if elasticstack_full_stack | bool %}
+ hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% else %}
+ hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+{% endif %}
+ loadbalance: {{ _beat_loadbalance }}
+{% if beats_security | bool %}
+ ssl.enabled: true
+ ssl.certificate_authorities: ["{{ beats_tls_cacert }}"]
+ ssl.verification_mode: full
+ ssl.key: {{ beats_tls_key }}
+ ssl.key_passphrase: {{ beats_tls_key_passphrase }}
+ ssl.certificate: {{ beats_tls_cert }}
+{% endif %}
+{% endif %}
diff --git a/roles/beats/templates/auditbeat.yml.j2 b/roles/beats/templates/auditbeat.yml.j2
index ed995d0c..44cfcaed 100644
--- a/roles/beats/templates/auditbeat.yml.j2
+++ b/roles/beats/templates/auditbeat.yml.j2
@@ -34,54 +34,12 @@ setup.kibana:
{% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %}
host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}"
{% endif %}
-{% if beats_auditbeat_output == "elasticsearch" %}
-output.elasticsearch:
-{% if beats_security | bool %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- username: "elastic"
- password: "{{ beats_writer_password.stdout }}"
- ssl.enabled: true
- ssl.verification_mode: {{ beats_ssl_verification_mode }}
- ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"]
-{% else %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
-{% endif %}
-{% endif %}
-{% if beats_auditbeat_output == "logstash" %}
-output.logstash:
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- loadbalance: {{ beats_auditbeat_loadbalance }}
-{% if beats_security | bool %}
- ssl.enabled: true
- ssl.certificate_authorities: ["{{ beats_tls_cacert }}"]
- ssl.verification_mode: full
- ssl.key: {{ beats_tls_key }}
- ssl.key_passphrase: {{ beats_tls_key_passphrase }}
- ssl.certificate: {{ beats_tls_cert }}
-{% endif %}
-{% endif %}
+{% set _beat_output = beats_auditbeat_output %}
+{% set _beat_loadbalance = beats_auditbeat_loadbalance %}
+{% set _beat_name = "auditbeat" %}
+{% include '_beats_output.j2' %}
-{% if beats_logging == "file" %}
-logging.level: {{ beats_loglevel }}
-logging.to_files: true
-logging.files:
- path: {{ beats_logpath }}
- name: auditbeat
- keepfiles: {{ beats_logging_keepfiles }}
- permissions: {{ beats_logging_permissions }}
-{% endif %}
+{% include '_beats_logging.j2' %}
processors:
- add_host_metadata: ~
diff --git a/roles/beats/templates/filebeat.yml.j2 b/roles/beats/templates/filebeat.yml.j2
index 1320f911..e05be379 100644
--- a/roles/beats/templates/filebeat.yml.j2
+++ b/roles/beats/templates/filebeat.yml.j2
@@ -145,59 +145,17 @@ setup.kibana:
{% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %}
host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}"
{% endif %}
-{% if beats_filebeat_output == "elasticsearch" %}
-output.elasticsearch:
-{% if beats_security | bool %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- username: "elastic"
- password: "{{ beats_writer_password.stdout }}"
- ssl.enabled: true
- ssl.verification_mode: {{ beats_ssl_verification_mode }}
- ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"]
-{% else %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
-{% endif %}
-{% endif %}
-{% if beats_filebeat_output == "logstash" %}
-output.logstash:
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- loadbalance: {{ beats_filebeat_loadbalance }}
-{% if beats_security | bool %}
- ssl.enabled: true
- ssl.certificate_authorities: ["{{ beats_tls_cacert }}"]
- ssl.verification_mode: full
- ssl.key: {{ beats_tls_key }}
- ssl.key_passphrase: {{ beats_tls_key_passphrase }}
- ssl.certificate: {{ beats_tls_cert }}
-{% endif %}
-{% endif %}
+{% set _beat_output = beats_filebeat_output %}
+{% set _beat_loadbalance = beats_filebeat_loadbalance %}
+{% set _beat_name = "filebeat" %}
+{% include '_beats_output.j2' %}
{% if beats_filebeat_output == "file" %}
output.file:
path: {{ beats_filebeat_output_file_path | default("/tmp/filebeat-output") }}
filename: filebeat
{% endif %}
-{% if beats_logging == "file" %}
-logging.level: {{ beats_loglevel }}
-logging.to_files: true
-logging.files:
- path: {{ beats_logpath }}
- name: filebeat
- keepfiles: {{ beats_logging_keepfiles }}
- permissions: {{ beats_logging_permissions }}
-{% endif %}
+{% include '_beats_logging.j2' %}
queue.{{ beats_queue_type }}:
{% if beats_queue_type == "disk" %}
diff --git a/roles/beats/templates/metricbeat.yml.j2 b/roles/beats/templates/metricbeat.yml.j2
index 49984c6a..3cf548c8 100644
--- a/roles/beats/templates/metricbeat.yml.j2
+++ b/roles/beats/templates/metricbeat.yml.j2
@@ -11,54 +11,12 @@ setup.kibana:
{% if elasticstack_full_stack | default(false) | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %}
host: "http://{{ groups[elasticstack_kibana_group_name][0] }}:{{ elasticstack_kibana_port }}"
{% endif %}
-{% if beats_metricbeat_output == "elasticsearch" %}
-output.elasticsearch:
-{% if beats_security | bool %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"https://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- username: "elastic"
- password: "{{ beats_writer_password.stdout }}"
- ssl.enabled: true
- ssl.verification_mode: {{ beats_ssl_verification_mode }}
- ssl.certificate_authorities: ["/etc/beats/certs/ca.crt"]
-{% else %}
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_elasticsearch_group_name] %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"http://{{ host }}:{{ elasticstack_elasticsearch_http_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
-{% endif %}
-{% endif %}
-{% if beats_metricbeat_output == "logstash" %}
-output.logstash:
-{% if elasticstack_full_stack | bool %}
- hosts: [ {% for host in groups[elasticstack_logstash_group_name] %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% else %}
- hosts: [ {% for host in beats_target_hosts %}"{{ host }}:{{ elasticstack_beats_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
-{% endif %}
- loadbalance: {{ beats_metricbeat_loadbalance }}
-{% if beats_security | bool %}
- ssl.enabled: true
- ssl.certificate_authorities: ["{{ beats_tls_cacert }}"]
- ssl.verification_mode: full
- ssl.key: {{ beats_tls_key }}
- ssl.key_passphrase: {{ beats_tls_key_passphrase }}
- ssl.certificate: {{ beats_tls_cert }}
-{% endif %}
-{% endif %}
+{% set _beat_output = beats_metricbeat_output %}
+{% set _beat_loadbalance = beats_metricbeat_loadbalance %}
+{% set _beat_name = "metricbeat" %}
+{% include '_beats_output.j2' %}
-{% if beats_logging == "file" %}
-logging.level: {{ beats_loglevel }}
-logging.to_files: true
-logging.files:
- path: {{ beats_logpath }}
- name: metricbeat
- keepfiles: {{ beats_logging_keepfiles }}
- permissions: {{ beats_logging_permissions }}
-{% endif %}
+{% include '_beats_logging.j2' %}
processors:
- add_host_metadata: ~
diff --git a/roles/beats/vars/main.yml b/roles/beats/vars/main.yml
new file mode 100644
index 00000000..fa515fb3
--- /dev/null
+++ b/roles/beats/vars/main.yml
@@ -0,0 +1,3 @@
+---
+# Internal state — do not set manually
+beats_cert_will_expire_soon: false
diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml
index 67b9af64..a86fcda0 100644
--- a/roles/elasticsearch/defaults/main.yml
+++ b/roles/elasticsearch/defaults/main.yml
@@ -116,8 +116,6 @@ elasticsearch_tls_key_passphrase: PleaseChangeMeIndividually
elasticsearch_cert_validity_period: 1095
# @var elasticsearch_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal
elasticsearch_cert_expiration_buffer: 30
-# @var elasticsearch_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually
-elasticsearch_cert_will_expire_soon: false
# @var elasticsearch_ssl_verification_mode:description: TLS verification mode for inter-node communication (full, certificate, or none)
elasticsearch_ssl_verification_mode: full
diff --git a/roles/elasticsearch/meta/main.yml b/roles/elasticsearch/meta/main.yml
index 1eaa741c..b2c4409e 100644
--- a/roles/elasticsearch/meta/main.yml
+++ b/roles/elasticsearch/meta/main.yml
@@ -9,7 +9,6 @@ galaxy_info:
platforms:
- name: EL
versions:
- - "8"
- "9"
- "10"
- name: Debian
@@ -20,5 +19,6 @@ galaxy_info:
versions:
- jammy
- noble
+ - resolute
galaxy_tags: []
dependencies: []
diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml
index 0960d76a..78df6188 100644
--- a/roles/elasticsearch/tasks/main.yml
+++ b/roles/elasticsearch/tasks/main.yml
@@ -44,7 +44,7 @@
name: oddly.elasticstack.elasticstack
when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false)
-- name: main | Propagate stack-level security setting
+- name: Main | Propagate stack-level security setting
ansible.builtin.set_fact:
elasticsearch_security: "{{ elasticstack_security | bool }}"
when:
diff --git a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml
index 5b7e42d9..564a639b 100644
--- a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml
+++ b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml
@@ -1,38 +1,8 @@
---
- name: restart_and_verify_elasticsearch | Restart and verify Elasticsearch
- block:
- - name: restart_and_verify_elasticsearch | Reset Elasticsearch failed state before restart
- ansible.builtin.command:
- cmd: systemctl reset-failed elasticsearch.service
- changed_when: false
- failed_when: false
-
- - name: restart_and_verify_elasticsearch | Restart Elasticsearch service
- ansible.builtin.service:
- name: elasticsearch
- state: restarted
- daemon_reload: true
-
- - name: restart_and_verify_elasticsearch | Verify Elasticsearch is running
- ansible.builtin.systemd:
- name: elasticsearch
- register: _elasticsearch_service_state
- until: _elasticsearch_service_state.status.ActiveState == 'active'
- retries: 5
- delay: 3
-
- rescue:
- - name: restart_and_verify_elasticsearch | Get recent Elasticsearch journal output
- ansible.builtin.command:
- cmd: journalctl -u elasticsearch --no-pager -n 50
- register: _elasticsearch_journal
- changed_when: false
-
- - name: restart_and_verify_elasticsearch | Fail with Elasticsearch startup diagnostics
- ansible.builtin.fail:
- msg: |
- Elasticsearch failed to start.
-
- Recent log output:
- {{ _elasticsearch_journal.stdout }}
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/../elasticstack/tasks/restart_and_verify_service.yml"
+ vars:
+ _service_name: elasticsearch
+ _daemon_reload: true
diff --git a/roles/elasticsearch/vars/main.yml b/roles/elasticsearch/vars/main.yml
index af01504e..01b0b90a 100644
--- a/roles/elasticsearch/vars/main.yml
+++ b/roles/elasticsearch/vars/main.yml
@@ -39,6 +39,9 @@ _elasticsearch_managed_keys:
# Internal sentinel values — prevent handler errors on first run.
# These are overwritten by task register output; users must never set them.
+# Internal state — do not set manually
+elasticsearch_cert_will_expire_soon: false
+
_elasticsearch_freshstart:
changed: false
_elasticsearch_freshstart_security:
diff --git a/roles/elasticstack/defaults/main.yml b/roles/elasticstack/defaults/main.yml
index 1a2d9f02..5889811a 100644
--- a/roles/elasticstack/defaults/main.yml
+++ b/roles/elasticstack/defaults/main.yml
@@ -45,9 +45,6 @@ elasticstack_ca_pass: PleaseChangeMe
# elasticstack_cert_pass:
# @var elasticstack_ca_validity_period:description: Validity period in days for the CA certificate
elasticstack_ca_validity_period: 1095
-# @var elasticstack_ca_will_expire_soon:description: Internal flag set when the CA certificate is within the expiration buffer. Do not set manually
-elasticstack_ca_will_expire_soon: false
-
# === General Settings ===
# @var elasticstack_enable_repos:description: Let the repos role manage Elastic APT/YUM repositories
diff --git a/roles/elasticstack/meta/main.yml b/roles/elasticstack/meta/main.yml
new file mode 100644
index 00000000..37aa4a13
--- /dev/null
+++ b/roles/elasticstack/meta/main.yml
@@ -0,0 +1,29 @@
+---
+galaxy_info:
+ role_name: elasticstack
+ namespace: oddly
+ author: Netways GmbH
+ description: Shared defaults for the oddly.elasticstack collection
+ company: Netways GmbH
+ license: GPL-3.0-or-later
+ min_ansible_version: "2.18"
+ platforms:
+ - name: EL
+ versions:
+ - "9"
+ - "10"
+ - name: Debian
+ versions:
+ - bookworm
+ - trixie
+ - name: Ubuntu
+ versions:
+ - jammy
+ - noble
+ - resolute
+ galaxy_tags:
+ - elk
+ - elastic
+ - logging
+ - system
+dependencies: []
diff --git a/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml b/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml
new file mode 100644
index 00000000..3f68616a
--- /dev/null
+++ b/roles/elasticstack/tasks/certs/cert_detect_content_mode.yml
@@ -0,0 +1,56 @@
+---
+# Detect whether external certificates are provided as inline PEM content
+# or as file paths, and set format/CA-extraction facts accordingly.
+#
+# Required variables:
+# _detect_cert_content_var – the certificate content variable value
+# (e.g. beats_tls_certificate_content)
+# _detect_key_content_var – the key content variable value
+# (e.g. beats_tls_key_content)
+# _detect_cert_file_var – the certificate file path variable value
+# (e.g. beats_tls_certificate_file)
+# _detect_service_name – human-readable service name for error messages
+# _detect_key_var_name – variable name string for the fail_msg hint
+# (e.g. "beats_tls_key_content")
+#
+# Output facts (set on the host):
+# _detect_content_mode – "True"/"False" string (use with | bool)
+# _detect_cert_format – "pem" when content mode, undefined otherwise
+# _detect_ca_extracted – whether the PEM bundle contains a CA chain
+
+- name: "cert_detect_content_mode | Detect content mode for {{ _detect_service_name }}" # noqa: name[template] var-naming[no-role-prefix]
+ ansible.builtin.set_fact:
+ _detect_content_mode: "{{ _detect_cert_content_var | default('', true) | length > 0 }}"
+
+- name: "cert_detect_content_mode | Validate certificate is provided for {{ _detect_service_name }}" # noqa: name[template]
+ ansible.builtin.assert:
+ that:
+ - (_detect_cert_file_var | length > 0) or
+ (_detect_cert_content_var | default('', true) | length > 0)
+ fail_msg: >-
+ External cert mode requires at minimum a certificate file or content
+ for {{ _detect_service_name }}.
+
+- name: cert_detect_content_mode | Set format facts for content mode # noqa: var-naming[no-role-prefix]
+ ansible.builtin.set_fact:
+ _detect_cert_format: pem
+ _detect_ca_extracted: false
+ when: _detect_content_mode | bool
+
+- name: cert_detect_content_mode | Check for CA chain in certificate content # noqa: var-naming[no-role-prefix]
+ ansible.builtin.set_fact:
+ _detect_ca_extracted: true
+ when:
+ - _detect_content_mode | bool
+ - >-
+ _detect_cert_content_var | default('')
+ | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1
+
+- name: "cert_detect_content_mode | Validate key content is provided for {{ _detect_service_name }}" # noqa: name[template]
+ ansible.builtin.assert:
+ that:
+ - _detect_key_content_var | default('', true) | length > 0
+ fail_msg: >-
+ Content mode requires both certificate and key content.
+ Set {{ _detect_key_var_name }}.
+ when: _detect_content_mode | bool
diff --git a/roles/elasticstack/tasks/restart_and_verify_service.yml b/roles/elasticstack/tasks/restart_and_verify_service.yml
new file mode 100644
index 00000000..b7e95c68
--- /dev/null
+++ b/roles/elasticstack/tasks/restart_and_verify_service.yml
@@ -0,0 +1,45 @@
+---
+# Shared restart-and-verify pattern for systemd services.
+#
+# Required variables:
+# _service_name – systemd unit name (without .service suffix)
+#
+# Optional variables:
+# _daemon_reload – whether to daemon-reload before restarting (default: false)
+
+- name: "restart_and_verify_service | Restart and verify {{ _service_name }}" # noqa: name[template]
+ block:
+ - name: "restart_and_verify_service | Reset {{ _service_name }} failed state before restart" # noqa: name[template]
+ ansible.builtin.command:
+ cmd: "systemctl reset-failed {{ _service_name }}.service"
+ changed_when: false
+ failed_when: false
+
+ - name: "restart_and_verify_service | Restart {{ _service_name }} service" # noqa: name[template]
+ ansible.builtin.service:
+ name: "{{ _service_name }}"
+ state: restarted
+ daemon_reload: "{{ _daemon_reload | default(false) | bool }}"
+
+ - name: "restart_and_verify_service | Verify {{ _service_name }} is running" # noqa: name[template] var-naming[no-role-prefix]
+ ansible.builtin.systemd:
+ name: "{{ _service_name }}"
+ register: _service_state
+ until: _service_state.status.ActiveState == 'active'
+ retries: 5
+ delay: 3
+
+ rescue:
+ - name: "restart_and_verify_service | Get recent {{ _service_name }} journal output" # noqa: name[template] var-naming[no-role-prefix]
+ ansible.builtin.command:
+ cmd: "journalctl -u {{ _service_name }} --no-pager -n 50"
+ register: _service_journal
+ changed_when: false
+
+ - name: "restart_and_verify_service | Fail with {{ _service_name }} startup diagnostics" # noqa: name[template]
+ ansible.builtin.fail:
+ msg: |
+ {{ _service_name }} failed to start.
+
+ Recent log output:
+ {{ _service_journal.stdout }}
diff --git a/roles/elasticstack/vars/main.yml b/roles/elasticstack/vars/main.yml
index ed456934..20998a99 100644
--- a/roles/elasticstack/vars/main.yml
+++ b/roles/elasticstack/vars/main.yml
@@ -1,2 +1,3 @@
---
-# vars file for logstash
+# Internal state — do not set manually
+elasticstack_ca_will_expire_soon: false
diff --git a/roles/kibana/defaults/main.yml b/roles/kibana/defaults/main.yml
index 92255046..12ad5d86 100644
--- a/roles/kibana/defaults/main.yml
+++ b/roles/kibana/defaults/main.yml
@@ -41,9 +41,6 @@ kibana_keystore_password: ""
kibana_cert_expiration_buffer: 30
# @var kibana_cert_validity_period:description: Validity period in days for generated TLS certificates
kibana_cert_validity_period: 1095
-# @var kibana_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually
-kibana_cert_will_expire_soon: false
-
# @var kibana_cert_source:description: Where to get TLS certificates. 'elasticsearch_ca' (default) auto-generates from the built-in CA; 'external' uses user-provided files
kibana_cert_source: elasticsearch_ca
diff --git a/roles/kibana/meta/main.yml b/roles/kibana/meta/main.yml
index 1432001d..600c3d11 100644
--- a/roles/kibana/meta/main.yml
+++ b/roles/kibana/meta/main.yml
@@ -9,7 +9,6 @@ galaxy_info:
platforms:
- name: EL
versions:
- - "8"
- "9"
- "10"
- name: Debian
@@ -20,6 +19,7 @@ galaxy_info:
versions:
- jammy
- noble
+ - resolute
galaxy_tags: []
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
diff --git a/roles/kibana/tasks/kibana-security.yml b/roles/kibana/tasks/kibana-security.yml
index f050ca83..1cb18f31 100644
--- a/roles/kibana/tasks/kibana-security.yml
+++ b/roles/kibana/tasks/kibana-security.yml
@@ -15,42 +15,20 @@
block:
# -- Detect content mode vs file mode --
- name: kibana-security | Detect content mode for Kibana
- ansible.builtin.set_fact:
- _kibana_content_mode: "{{ kibana_tls_certificate_content | default('', true) | length > 0 }}"
-
- - name: kibana-security | Validate certificate is provided
- ansible.builtin.assert:
- that:
- - (kibana_tls_certificate_file | length > 0) or
- (kibana_tls_certificate_content | default('', true) | length > 0)
- fail_msg: >-
- External cert mode with kibana_tls requires at minimum:
- kibana_tls_certificate_file or kibana_tls_certificate_content
-
- # -- Content mode: set format to PEM --
- - name: kibana-security | Set format facts for content mode
- ansible.builtin.set_fact:
- _kibana_cert_format: pem
- _kibana_ca_extracted: false
- when: _kibana_content_mode | bool
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/../elasticstack/tasks/certs/cert_detect_content_mode.yml"
+ vars:
+ _detect_cert_content_var: "{{ kibana_tls_certificate_content | default('', true) }}"
+ _detect_key_content_var: "{{ kibana_tls_key_content | default('', true) }}"
+ _detect_cert_file_var: "{{ kibana_tls_certificate_file }}"
+ _detect_service_name: Kibana
+ _detect_key_var_name: kibana_tls_key_content
- - name: kibana-security | Check for CA chain in certificate content
+ - name: kibana-security | Map detection results to Kibana facts
ansible.builtin.set_fact:
- _kibana_ca_extracted: true
- when:
- - _kibana_content_mode | bool
- - >-
- kibana_tls_certificate_content | default('')
- | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1
-
- - name: kibana-security | Validate key content is provided (content mode)
- ansible.builtin.assert:
- that:
- - kibana_tls_key_content | default('', true) | length > 0
- fail_msg: >-
- Content mode requires both certificate and key content.
- Set kibana_tls_key_content.
- when: _kibana_content_mode | bool
+ _kibana_content_mode: "{{ _detect_content_mode }}"
+ _kibana_cert_format: "{{ _detect_cert_format | default(omit) }}"
+ _kibana_ca_extracted: "{{ _detect_ca_extracted | default(false) }}"
# -- File mode: validate and detect format --
- name: kibana-security | Validate Kibana certificate (file mode)
diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml
index 92e7f536..d5ad60da 100644
--- a/roles/kibana/tasks/main.yml
+++ b/roles/kibana/tasks/main.yml
@@ -5,7 +5,7 @@
name: oddly.elasticstack.elasticstack
when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false)
-- name: main | Propagate stack-level security setting
+- name: Main | Propagate stack-level security setting
ansible.builtin.set_fact:
kibana_security: "{{ elasticstack_security | bool }}"
when:
diff --git a/roles/kibana/vars/main.yml b/roles/kibana/vars/main.yml
index cad325d0..914722fe 100644
--- a/roles/kibana/vars/main.yml
+++ b/roles/kibana/vars/main.yml
@@ -1,4 +1,7 @@
---
+# Internal state — do not set manually
+kibana_cert_will_expire_soon: false
+
# Internal sentinel values — prevent handler errors on first run.
# These are overwritten by task register output; users must never set them.
_kibana_freshstart:
diff --git a/roles/logstash/defaults/main.yml b/roles/logstash/defaults/main.yml
index 27ce0426..b6a2c253 100644
--- a/roles/logstash/defaults/main.yml
+++ b/roles/logstash/defaults/main.yml
@@ -207,8 +207,6 @@ logstash_certs_dir: /etc/logstash/certs
logstash_cert_validity_period: 1095
# @var logstash_cert_expiration_buffer:description: Days before certificate expiry to trigger renewal
logstash_cert_expiration_buffer: 30
-# @var logstash_cert_will_expire_soon:description: Internal flag set when certificates are within the expiration buffer. Do not set manually
-logstash_cert_will_expire_soon: false
# @var logstash_cert_force_regenerate:description: Force TLS certificate regeneration even if current certificates are valid
logstash_cert_force_regenerate: false
# @var logstash_tls_key_passphrase:description: Passphrase for the Logstash TLS private key
diff --git a/roles/logstash/meta/main.yml b/roles/logstash/meta/main.yml
index e3a2f610..93c24f85 100644
--- a/roles/logstash/meta/main.yml
+++ b/roles/logstash/meta/main.yml
@@ -9,7 +9,6 @@ galaxy_info:
platforms:
- name: EL
versions:
- - "8"
- "9"
- "10"
- name: Debian
@@ -20,6 +19,7 @@ galaxy_info:
versions:
- jammy
- noble
+ - resolute
galaxy_tags:
- elk
- logstash
diff --git a/roles/logstash/tasks/restart_and_verify_logstash.yml b/roles/logstash/tasks/restart_and_verify_logstash.yml
index 65b41ccf..8ba03d43 100644
--- a/roles/logstash/tasks/restart_and_verify_logstash.yml
+++ b/roles/logstash/tasks/restart_and_verify_logstash.yml
@@ -1,38 +1,8 @@
---
- name: restart_and_verify_logstash | Restart and verify Logstash
- block:
- - name: restart_and_verify_logstash | Reset Logstash failed state before restart
- ansible.builtin.command:
- cmd: systemctl reset-failed logstash.service
- changed_when: false
- failed_when: false
-
- - name: restart_and_verify_logstash | Restart Logstash service
- ansible.builtin.service:
- name: logstash
- state: restarted
- daemon_reload: true
-
- - name: restart_and_verify_logstash | Verify Logstash is running
- ansible.builtin.systemd:
- name: logstash
- register: _logstash_service_state
- until: _logstash_service_state.status.ActiveState == 'active'
- retries: 5
- delay: 3
-
- rescue:
- - name: restart_and_verify_logstash | Get recent Logstash journal output
- ansible.builtin.command:
- cmd: journalctl -u logstash --no-pager -n 50
- register: _logstash_journal
- changed_when: false
-
- - name: restart_and_verify_logstash | Fail with Logstash startup diagnostics
- ansible.builtin.fail:
- msg: |
- Logstash failed to start.
-
- Recent log output:
- {{ _logstash_journal.stdout }}
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/../elasticstack/tasks/restart_and_verify_service.yml"
+ vars:
+ _service_name: logstash
+ _daemon_reload: true
diff --git a/roles/logstash/vars/main.yml b/roles/logstash/vars/main.yml
index 2031f6a3..c5aa6191 100644
--- a/roles/logstash/vars/main.yml
+++ b/roles/logstash/vars/main.yml
@@ -1,4 +1,7 @@
---
+# Internal state — do not set manually
+logstash_cert_will_expire_soon: false
+
# Internal sentinel values — prevent handler errors on first run.
# These are overwritten by task register output; users must never set them.
_logstash_freshstart:
diff --git a/roles/repos/meta/main.yml b/roles/repos/meta/main.yml
index 3693d9c5..c7c41f21 100644
--- a/roles/repos/meta/main.yml
+++ b/roles/repos/meta/main.yml
@@ -10,7 +10,6 @@ galaxy_info:
platforms:
- name: EL
versions:
- - "8"
- "9"
- "10"
- name: Debian
@@ -21,6 +20,7 @@ galaxy_info:
versions:
- jammy
- noble
+ - resolute
galaxy_tags:
- logmanagement
- elasticstack