mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-29 23:08:06 +02:00
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:
27
roles/sys-service/README.md
Normal file
27
roles/sys-service/README.md
Normal 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)
|
3
roles/sys-service/defaults/main.yml
Normal file
3
roles/sys-service/defaults/main.yml
Normal 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
|
35
roles/sys-service/filter_plugins/filetype.py
Normal file
35
roles/sys-service/filter_plugins/filetype.py
Normal 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
|
||||
}
|
8
roles/sys-service/handlers/main.yml
Normal file
8
roles/sys-service/handlers/main.yml
Normal 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 }}"
|
24
roles/sys-service/meta/main.yml
Normal file
24
roles/sys-service/meta/main.yml
Normal 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: []
|
8
roles/sys-service/tasks/01_core.yml
Normal file
8
roles/sys-service/tasks/01_core.yml
Normal 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
|
10
roles/sys-service/tasks/02_reset.yml
Normal file
10
roles/sys-service/tasks/02_reset.yml
Normal 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"
|
||||
|
29
roles/sys-service/tasks/03_base.yml
Normal file
29
roles/sys-service/tasks/03_base.yml
Normal 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
|
23
roles/sys-service/tasks/04_files.yml
Normal file
23
roles/sys-service/tasks/04_files.yml
Normal 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
|
47
roles/sys-service/tasks/05_service.yml
Normal file
47
roles/sys-service/tasks/05_service.yml
Normal 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 it’s 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
|
13
roles/sys-service/tasks/06_timer.yml
Normal file
13
roles/sys-service/tasks/06_timer.yml
Normal 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
|
14
roles/sys-service/tasks/main.yml
Normal file
14
roles/sys-service/tasks/main.yml
Normal 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
|
10
roles/sys-service/templates/systemctl.service.j2
Normal file
10
roles/sys-service/templates/systemctl.service.j2
Normal 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 %}
|
23
roles/sys-service/vars/main.yml
Normal file
23
roles/sys-service/vars/main.yml
Normal 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 }}"
|
Reference in New Issue
Block a user