#!/usr/bin/python3 import pathlib from typing import List, Union try: from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable except ImportError: from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable @installable class Module(SystemdUnitModule, SystemdReloadMixin): """Creates System Services units""" name = "system_service" module_spec = dict( argument_spec=dict( name=Types.str(required=True, help="Name of the service"), serviceuser=Types.str(help="Username of under which the commands run at.", default="root"), servicegroup=Types.str(help="Group of under which the commands run at.", default="root"), type=Types.str( choices=("simple", "exec", "forking", "oneshot", "dbus", "notify", "notify-reload", "idle"), default="simple", help="Type of the systemd service.\n" "simple and exec start long running services that run in the same process over the whole time, exec is waiting until the process was started completly.\n" "forking does some things in the foreground, starts an background process and then exits to leave the work to the background process.\n" "oneshot processes are started by systemd, do their work and then exit, similar to cronjobs.\n" "dbus services will be considered started up once they aquire the specified dbus bus" "notify and notify-reload notify systemd about the start up via sd_notify. notify-reload needs also inform systemd on reloads and when it is ready again after an reload.\n" "idle is similar to simple, but it can delay the start up by a few seconds.", ), pre=Types.list(elements=str, help="command or list of commands that are started before the main command"), start=Types.list( elements=str, required=True, help="command or list of commands that are started as main programm. Multiple commands are only allowed in a oneshot command", ), post=Types.list(str, help="Command or list of commands that are started after the main command(s) stopped without problems."), environmentfile=Types.list( elements=str, help="List of file that are containing environment variables. They are evaluated before each pre/start/post command", ), environment=Types.list( elements=Types.dict( name=Types.str(help="name of the Environment variable", required=True), value=Types.str(help="value of the Environment variable", required=True), ), help="List of environment variables that are set to each command before they run", ), workingdirectory=Types.str( help="The Directory that is used for the processes as current working directory", ), rwpath=Types.list( elements=Types.path(), help="Path(s) that are readable and writable (if permission allow)", ), ropath=Types.list( elements=Types.path(), help="Path(s) that are read only", ), notreadablepath=Types.list( elements=Types.path(), help="Path(s) that are not accessible by the applications", ), execpath=Types.list( elements=Types.path(), help="Path(s) where executable files are", ), noexecpath=Types.list( elements=Types.path(), help="Path(s) which are never executable (uploaded files, user accessible paths)", ), protecthome=Types.str( help="if true makes user specific directories (/home, /root, /run/user) inaccessible. read-only makes them read only and tmpfs is useful to create binds in it", choices=("true", "false", "read-only", "tmpfs"), ), protectsystem=Types.str( help="makes the system read only. if true /usr, /boot and /efi are read only, if full additionally /etc and if strict all except /proc, /sys and /dev", choices=("true", "false", "full", "strict"), ), nonewprivileges=Types.bool( help="disables the ability to get new capabilities for processes than already granted ones", ), statedirectory=Types.str( help="creates an unit specific state directory in /var/lib and sets the env var STATE_DIRECTORY with the path to it. Its cleaned up after the unit is stopped" ), ), ) def prepare(self): self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".service") self.__unit = None if self.get("type", "simple") != "oneshot" and len(self.get("start")) > 1: self.module.fail_json("only oneshot services are allowed to have multiple start commands", **self.result) def service(self): section = "[Service]\n" params = [] if self.get("environment", False): for env in self.get("environment"): params.append(f"Environment={env['name']}={env['value']}\n") params.extend( self.map_param( type="Type", pre="ExecStartPre", start="ExecStart", post="ExecStartPost", serviceuser="User", servicegroup="Group", workingdirectory="WorkingDirectory", environmentfile="EnvironmentFile", protecthome="ProtectHome", protectsystem="ProtectSystem", rwpath="ReadWritePaths", ropath="ReadOnlyPaths", notreadablepath="InaccessiblePaths", execpath="ExecPaths", noexecpath="NoExecPaths", statedirectory="StateDirectory", nonewprivileges="NoNewPriviledges", ) ) section += "".join(params) return section def unit(self) -> str: if self.__unit is None: self.__unit = "\n".join( ( self.header(), self.service(), self.install(), ) ) return self.__unit DOCUMENTATION = """--- description: - Creates System Services units module: system_service options: after: default: [] description: - list of units that this unit wants to be started after this unit elements: str required: false type: list before: default: [] description: - list of units that this unit needs to be started before this unit. elements: str required: false type: list description: description: - An description for programs that access systemd required: false type: str documentation: default: [] description: - Paths where documentation can be found elements: str required: false type: list environment: default: [] description: - List of environment variables that are set to each command before they run elements: dict options: name: description: - name of the Environment variable required: true type: str value: description: - value of the Environment variable required: true type: str required: false type: list environmentfile: default: [] description: - List of file that are containing environment variables. They are evaluated before each pre/start/post command elements: str required: false type: list execpath: default: [] description: - Path(s) where executable files are elements: path required: false type: list name: description: - Name of the service required: true type: str noexecpath: default: [] description: - Path(s) which are never executable (uploaded files, user accessible paths) elements: path required: false type: list nonewprivileges: description: - disables the ability to get new capabilities for processes than already granted ones required: false type: bool notreadablepath: default: [] description: - Path(s) that are not accessible by the applications elements: path required: false type: list partof: default: [] description: - list of units that this unit is part of. - If the restart this unit does it too, but if this restarts it does not affect the other units. elements: str required: false type: list post: default: [] description: - Command or list of commands that are started after the main command(s) stopped without problems. elements: str required: false type: list pre: default: [] description: - command or list of commands that are started before the main command(Types.str) elements: str required: false type: list protecthome: choices: - 'true' - 'false' - read-only - tmpfs description: - if true makes user specific directories (/home, /root, /run/user) inaccessible. read-only makes them read only and tmpfs is useful to create binds in it required: false type: str protectsystem: choices: - 'true' - 'false' - full - strict description: - makes the system read only. if true /usr, /boot and /efi are read only, if full additionally /etc and if strict all except /proc, /sys and /dev required: false type: str required_by: default: [] description: - systemd units that require this mount elements: str required: false type: list requires: default: [] description: - list of units that this unit requires. If it fails or can't be started this unit fails. without before/after this is started at the same time elements: str required: false type: list ropath: default: [] description: - Path(s) that are read only elements: path required: false type: list rwpath: default: [] description: - Path(s) that are readable and writable (if permission allow) elements: path required: false type: list servicegroup: default: root description: - Group of under which the commands run at. required: false type: str serviceuser: default: root description: - Username of under which the commands run at. required: false type: str start: description: - command or list of commands that are started as main programm. Multiple commands are only allowed in a oneshot command elements: str required: true type: list statedirectory: description: - creates an unit specific state directory in /var/lib and sets the env var STATE_DIRECTORY with the path to it. Its cleaned up after the unit is stopped required: false type: str type: choices: - simple - exec - forking - oneshot - dbus - notify - notify-reload - idle default: simple description: - Type of the systemd service. - simple and exec start long running services that run in the same process over the whole time, exec is waiting until the process was started completly. - forking does some things in the foreground, starts an background process and then exits to leave the work to the background process. - oneshot processes are started by systemd, do their work and then exit, similar to cronjobs. - dbus services will be considered started up once they aquire the specified dbus busnotify and notify-reload notify systemd about the start up via sd_notify. notify-reload needs also inform systemd on reloads and when it is ready again after an reload. - idle is similar to simple, but it can delay the start up by a few seconds. required: false type: str wanted_by: default: [] description: - systemd units that want the mount, but not explicitly require it. Commonly used for target if not service explicitly require it. elements: str required: false type: list wants: default: [] description: - list of units that this unit wants. If it fails or can't be started it does not affect this unit elements: str required: false type: list workingdirectory: description: - The Directory that is used for the processes as current working directory required: false type: str short_description: Creates System Services units """ if __name__ == "__main__": Module()()