refactor!: replace sys-systemctl with sys-service, add sys-daemon, and rename systemctl_* → system_service_* across repo

- Swap role includes: sys-systemctl → sys-service in all roles
- Rename variables everywhere: systemctl_* → system_service_* (incl. systemctl_id → system_service_id)
- Templates: ExecStart now uses {{ system_service_script_exec }}; add optional RuntimeMaxSec via SYS_SERVICE_DEFAULT_RUNTIME
- Move SYS_SERVICE defaults into roles/sys-service/defaults (remove SYS_SERVICE_ALL_ENABLED & SYS_SERVICE_DEFAULT_STATE from group_vars/07_services.yml)
- Tidy group_vars/all/08_timer.yml formatting
- Introduce roles/sys-daemon:
  - default manager timeouts (timeouts.conf)
  - optional purge of /etc/systemd/system.conf.d
  - validation via systemd-analyze verify
  - handlers for daemon-reload & daemon-reexec
- Refactor sys-timer to system_service_* variables (docs and templates updated)
- Move filter_plugins/filetype.py under sys-service
- Update meta/README to point to official systemd docs
- Touch many roles (backup/cleanup/health/repair/certs/nginx/csp/wireguard/ssd-hdd/keyboard/update-docker/alarm compose/email/telegram/etc.) to new naming

BREAKING CHANGE:
- Role path/name change: use `sys-service` instead of `sys-systemctl`
- All `systemctl_*` vars are now `system_service_*` (e.g., on_calendar, state, timer_enabled, script_exec, id)
- If you have custom templates, adopt RuntimeMaxSec and new variable names

Chat context: https://chatgpt.com/share/68a47568-312c-800f-af3f-e98575446327
This commit is contained in:
2025-08-19 15:00:44 +02:00
parent b49fdc509e
commit 26b392ea76
104 changed files with 377 additions and 334 deletions

View File

@@ -0,0 +1,27 @@
# sys-service
## Description
Role to manage **systemd service units** for Infinito.Nexus software stacks.
It installs or removes unit files, configures runtime behavior, and ensures services are properly deployed.
## Overview
- Resets service units by removing old or obsolete definitions.
- Deploys new service unit files and service scripts.
- Optionally sets up timers linked to the services.
- Ensures correct reload/restart behavior across the stack.
## Features
- **Unit Cleanup:** Automated removal of old service units.
- **Custom Templates:** Supports both `systemctl.service.j2` and `systemctl@.service.j2`.
- **Timers:** Integrates with `sys-timer` for scheduled execution.
- **Runtime Limits:** Configurable `RuntimeMaxSec` per service.
- **Handlers:** Automatic reload/restart of services when definitions change.
## Further Resources
- [systemd - Service Units](https://www.freedesktop.org/software/systemd/man/systemd.service.html)
- [systemd - Timer Units](https://www.freedesktop.org/software/systemd/man/systemd.timer.html)
- [systemctl](https://www.freedesktop.org/software/systemd/man/systemctl.html)

View File

@@ -0,0 +1,3 @@
SYS_SERVICE_ALL_ENABLED: "{{ not MODE_DEBUG }}"
SYS_SERVICE_DEFAULT_STATE: "{{ 'restarted' if MODE_DEBUG else omit }}"
SYS_SERVICE_DEFAULT_RUNTIME: "86400s" # Maximum total runtime a service is allowed to run before being stopped

View File

@@ -0,0 +1,35 @@
import os
def filetype(path, full=False):
"""
Extract file type (extension) from a given path.
:param path: Path or filename
:param full: If True, return the full extension (e.g., 'sh.j2'),
else only the last extension (e.g., 'sh').
:return: Extension string without leading dot, or empty string if none.
"""
if not path or not isinstance(path, str):
return ""
basename = os.path.basename(path)
if full:
# Full extension chain (e.g., "script.sh.j2" -> "sh.j2")
parts = basename.split('.', 1)
if len(parts) == 2:
return parts[1]
return ""
else:
# Last extension only (e.g., "script.sh.j2" -> "j2", "script.py" -> "py")
_, ext = os.path.splitext(basename)
return ext[1:] if ext else ""
class FilterModule(object):
""" Custom Jinja2 filters for Ansible """
def filters(self):
return {
"filetype": filetype
}

View File

@@ -0,0 +1,8 @@
- name: "refresh systemctl service"
systemd:
name: "{{ system_service_id | get_service_name(SOFTWARE_NAME) }}"
daemon_reload: yes
enabled: yes
state: "{{ system_service_state }}"
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"

View File

@@ -0,0 +1,24 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Role to manage systemd service units, including cleanup, deployment, and runtime configuration."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
min_ansible_version: "2.9"
platforms:
- name: Linux
versions:
- all
galaxy_tags:
- systemd
- services
- automation
- infinito
repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://www.freedesktop.org/software/systemd/man/systemd.service.html"
dependencies: []

View File

@@ -0,0 +1,8 @@
- name: Include dependency 'sys-daemon'
include_role:
name: sys-daemon
when: run_once_sys_daemon is not defined
- name: "reset (if enabled)"
include_tasks: 02_reset.yml
when: MODE_RESET | bool

View File

@@ -0,0 +1,10 @@
- name: "pkgmgr install '{{ UNIT_SUFFIX_REMOVER_PACKAGE }}'"
include_role:
name: pkgmgr-install
vars:
package_name: "{{ UNIT_SUFFIX_REMOVER_PACKAGE }}"
- name: Remove all '{{ SYS_SERVICE_SUFFIX }}' files with '{{ UNIT_SUFFIX_REMOVER_PACKAGE }}'
command: "{{ UNIT_SUFFIX_REMOVER_PACKAGE }} -s '{{ SOFTWARE_NAME }}'"
notify: "reload system daemon"

View File

@@ -0,0 +1,29 @@
- name: "find best matching source for service script"
set_fact:
system_service_script_src: >-
{{ lookup('first_found',
{
'files': [
'templates/script.sh.j2',
'templates/script.py.j2',
'files/script.sh',
'files/script.py'
],
'paths': [ system_service_role_dir ]
},
errors='strict'
) }}
when: system_service_copy_files | bool
- name: "Load file logic for '{{ system_service_id }}'"
include_tasks: 04_files.yml
when:
- system_service_copy_files | bool
- system_service_script_src
- name: "Load systemctl logic for '{{ system_service_id }}'"
include_tasks: 05_service.yml
- name: "Load timer logic for '{{ system_service_id }}'"
include_tasks: 06_timer.yml
when: system_service_timer_enabled | bool

View File

@@ -0,0 +1,23 @@
- name: "create {{ system_service_script_dir }}"
file:
path: "{{ system_service_script_dir }}"
state: directory
mode: "0755"
- name: "template or copy script"
block:
- name: "render template"
template:
src: "{{ system_service_script_src }}"
dest: "{{ [system_service_script_dir, (system_service_script_src | basename | regex_replace('\\.j2$', ''))] | path_join }}"
mode: "0755"
when: system_service_script_src.endswith('.j2')
- name: "copy raw file"
copy:
src: "{{ system_service_script_src }}"
dest: "{{ [system_service_script_dir, (system_service_script_src | basename)] | path_join }}"
mode: "0755"
when: not system_service_script_src.endswith('.j2')
when: system_service_copy_files | bool

View File

@@ -0,0 +1,47 @@
# 1) Find the template (prefer target role, then fall back to this role)
- name: Resolve systemctl template source
set_fact:
system_service_template_src: >-
{{ lookup(
'first_found',
{
'files': [
'templates/systemctl@.service.j2',
'templates/systemctl.service.j2'
],
'paths': [
system_service_role_dir,
role_path
]
},
errors='strict'
) }}
# Optional: sanity check with a clear error if truly nothing found
- name: Ensure a systemctl template was found
assert:
that: system_service_template_src | length > 0
fail_msg: >-
Could not resolve any systemctl template. Looked in:
{{ system_service_role_dir }}/templates/ and {{ role_path }}/templates/.
# 2) Now we may safely derive whether its the “@” variant
- name: Flag whether @-template is used
set_fact:
system_service_uses_at: "{{ (system_service_template_src | basename) is search('@\\.service\\.j2$') }}"
# 3) Use it
- name: "setup systemctl '{{ system_service_id }}'"
template:
src: "{{ system_service_template_src }}"
dest: "{{ [ PATH_SYSTEM_SERVICE_DIR, system_service_id | get_service_name(SOFTWARE_NAME) ] | path_join }}"
notify: "{{ 'reload system daemon' if system_service_uses_at else 'refresh systemctl service' }}"
- name: refresh systemctl service when SYS_SERVICE_ALL_ENABLED
command: /bin/true
notify:
- reload system daemon
- refresh systemctl service
when:
- SYS_SERVICE_ALL_ENABLED | bool
- not system_service_uses_at

View File

@@ -0,0 +1,13 @@
- name: Fail if system_service_id contains "@"
assert:
that:
- "'@' not in system_service_id"
fail_msg: "Invalid system_service_id '{{ system_service_id }}' → must not contain '@'."
- name: "Make '{{ system_service_id }}' available for sys-timer"
set_fact:
system_service_timer_service: "{{ system_service_id }}"
- name: "include role for sys-timer for {{ system_service_timer_service }}"
include_role:
name: sys-timer

View File

@@ -0,0 +1,14 @@
- block:
- include_tasks: 01_core.yml
- include_tasks: utils/run_once.yml
when: run_once_sys_service is not defined
- name: "Execute service routines for '{{ system_service_id }}'"
block:
- name: "Load base routine for '{{ system_service_id }}'"
include_tasks: 03_base.yml
- include_tasks: utils/run_once.yml
vars:
# Necessary to flush after every service which uses an 'system_service_id' otherwise wrong one will be used
flush_handlers: true
when: system_service_id is defined

View File

@@ -0,0 +1,10 @@
[Unit]
Description={{ SOFTWARE_NAME }} - Service for role '{{ system_service_id }}'
OnFailure={{ system_service_tpl_on_failure }}
[Service]
Type={{ system_service_tpl_type }}
ExecStart={{ system_service_tpl_exec_start }}
{% if system_service_tpl_runtime |length > 0 %}
RuntimeMaxSec={{ system_service_tpl_runtime }}
{% endif %}

View File

@@ -0,0 +1,23 @@
UNIT_SUFFIX_REMOVER_PACKAGE: "unsure"
## Paths
system_service_role_name: "{{ system_service_id | regex_replace('@','') }}"
system_service_role_dir: "{{ [ playbook_dir, 'roles', system_service_role_name ] | path_join }}"
system_service_script_dir: "{{ [ PATH_SYSTEMCTL_SCRIPTS, system_service_id ] | path_join }}"
## Settings
system_service_copy_files: true # When set to false file copying will be skipped
system_service_timer_enabled: false # When set to true timer will be loaded
system_service_state: "{{ SYS_SERVICE_DEFAULT_STATE }}"
# Dynamic Loaded ( Just available when dependencies are loaded )
system_service_script_base: "{{ system_service_script_src | basename | regex_replace('\\.j2$', '') }}"
system_service_script_type: "{{ system_service_script_base | filetype }}"
system_service_script_inter: "/bin/{{ 'bash' if system_service_script_type == 'sh' else 'python3'}}"
system_service_script_exec: "{{ system_service_script_inter }} {{ system_service_id | get_service_script_path( system_service_script_type ) }}"
# Service template
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_type: "oneshot"
system_service_tpl_exec_start: "{{ system_service_script_exec }}"
system_service_tpl_runtime: "{{ SYS_SERVICE_DEFAULT_RUNTIME }}"