From 0a5b3fc26f179dc07732c4d3c0c899e69fb2cdb5 Mon Sep 17 00:00:00 2001 From: Felix Stupp Date: Tue, 5 Jan 2021 18:10:03 +0100 Subject: [PATCH] Added new role misc/tg_monitor_cmd --- group_vars/all/vars.yml | 2 + roles/misc/tg_monitor_cmd/defaults/main.yml | 26 +++++++ roles/misc/tg_monitor_cmd/meta/main.yml | 8 +++ roles/misc/tg_monitor_cmd/tasks/main.yml | 69 +++++++++++++++++++ .../misc/tg_monitor_cmd/templates/monitor.py | 64 +++++++++++++++++ .../tg_monitor_cmd/templates/monitor.service | 23 +++++++ .../tg_monitor_cmd/templates/monitor.timer | 8 +++ 7 files changed, 200 insertions(+) create mode 100644 roles/misc/tg_monitor_cmd/defaults/main.yml create mode 100644 roles/misc/tg_monitor_cmd/meta/main.yml create mode 100644 roles/misc/tg_monitor_cmd/tasks/main.yml create mode 100644 roles/misc/tg_monitor_cmd/templates/monitor.py create mode 100644 roles/misc/tg_monitor_cmd/templates/monitor.service create mode 100644 roles/misc/tg_monitor_cmd/templates/monitor.timer diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml index cb92679..a5463cd 100644 --- a/group_vars/all/vars.yml +++ b/group_vars/all/vars.yml @@ -12,6 +12,8 @@ ansible_user: "{{ global_username }}" ansible_become: yes ansible_become_pass: "{{ zocker_password }}" +default_tg_monitor_recipient_id: "{{ zocker_telegram_id }}" + zocker_authorized_keys_url: "https://git.banananet.work/zocker.keys" update_scripts_directory: "/root/update" diff --git a/roles/misc/tg_monitor_cmd/defaults/main.yml b/roles/misc/tg_monitor_cmd/defaults/main.yml new file mode 100644 index 0000000..b13a770 --- /dev/null +++ b/roles/misc/tg_monitor_cmd/defaults/main.yml @@ -0,0 +1,26 @@ +--- + +# monitor_name: "echo-output-check" +instance_name: "tg-monitor-cmd-{{ monitor_name }}" +description: "{{ monitor_name }}" # should be human fancy + +# command: "/bin/echo Hello" # or command_str +command_str: "{{ command | map('quote') | join(' ') }}" +use_shell: no # read https://docs.python.org/3/library/subprocess.html#security-considerations before using use_shell=yes + +system_user: tg-monitor-cmd + +recipient_id: "{{ default_tg_monitor_recipient_id }}" +bot_key: "{{ global_telegram_server_bot_key }}" +telegram_timeout: 10 + +calendar_spec: "*:0:0" # once every hour +service_description: | + Telegram Monitor Command of {{ description }} + +# paths + +monitoring_directory: "{{ global_deployment_directory }}/tg-monitor-cmd" +instance_directory: "{{ monitoring_directory }}/{{ monitor_name }}" +script_path: "{{ instance_directory }}/script.py" +data_path: "{{ instance_directory }}/data" diff --git a/roles/misc/tg_monitor_cmd/meta/main.yml b/roles/misc/tg_monitor_cmd/meta/main.yml new file mode 100644 index 0000000..ed1f4df --- /dev/null +++ b/roles/misc/tg_monitor_cmd/meta/main.yml @@ -0,0 +1,8 @@ +--- + +allow_duplicates: yes + +dependencies: + - role: misc/system_user + # system_user + user_directory: "{{ monitoring_directory }}" diff --git a/roles/misc/tg_monitor_cmd/tasks/main.yml b/roles/misc/tg_monitor_cmd/tasks/main.yml new file mode 100644 index 0000000..c367516 --- /dev/null +++ b/roles/misc/tg_monitor_cmd/tasks/main.yml @@ -0,0 +1,69 @@ +--- + +- name: Create directory for monitoring scripts and data + file: + state: directory + path: "{{ instance_directory }}" + owner: root + group: "{{ system_user }}" + mode: u=rwx,g=rx,o= + +- name: Deploy script + template: + src: monitor.py + dest: "{{ script_path }}" + owner: root + group: "{{ system_user }}" + mode: u=rwx,g=rx,o= + register: script_task + +- name: Create empty data file + copy: + content: "" + dest: "{{ data_path }}" + force: no # do not overwrite + +- name: Ensure permissions on data file + file: + state: file + path: "{{ data_path }}" + owner: root + group: "{{ system_user }}" + mode: u=rw,g=rw,o= + +- name: Register service for monitor + template: + src: monitor.service + dest: "{{ global_systemd_configuration_directory }}/{{ instance_name }}.service" + owner: root + group: root + mode: u=rw,g=r,o= + register: service_task + +- name: Run service for initial test + systemd: + state: started + daemon_reload: yes + name: "{{ instance_name }}.service" + when: script_task.changed or service_task.changed + +- name: Register timer for monitor service + template: + src: monitor.timer + dest: "{{ global_systemd_configuration_directory }}/{{ instance_name }}.timer" + owner: root + group: root + mode: u=rw,g=r,o= + register: timer_task + +- name: Restart timer for monitor + systemd: + state: restarted + daemon_reload: yes + name: "{{ instance_name }}.timer" + when: timer_task.changed + +- name: Enable timer for monitor + systemd: + name: "{{ instance_name }}.timer" + enabled: yes diff --git a/roles/misc/tg_monitor_cmd/templates/monitor.py b/roles/misc/tg_monitor_cmd/templates/monitor.py new file mode 100644 index 0000000..2539691 --- /dev/null +++ b/roles/misc/tg_monitor_cmd/templates/monitor.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +# imports + +from pathlib import Path +from hashlib import sha256 +import shlex +import subprocess +import sys + +import requests + +# config + +MONITOR_DESC = """{{ description }}""" +MONITOR_COMMAND = """{{ command_str }}""" +USE_SHELL = {{ use_shell | ternary('True', 'False') }} + +DATA_PATH = Path("""{{ data_path }}""") + +TG_ENDPOINT = "https://api.telegram.org" +TG_KEY = """{{ bot_key }}""" +TG_RECIPIENT = """{{ recipient_id }}""" + +# helpers + +def print_error(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +def tg_msg(msg: str) -> None: + print(f"Sending message using telegram:\n{msg}") + ret = requests.post(f"{TG_ENDPOINT}/bot{TG_KEY}/sendMessage", data={ + "chat_id": TG_RECIPIENT, + "disable_web_page_preview": 1, + "parse_mode": "Markdown", + "text": msg, + }) + if 400 <= ret.status_code: + raise Exception(f"Sending telegram message failed: {ret.status_code} {ret.text}") + +def run_cmd(cmd: list, **kwargs) -> str: + return subprocess.run(cmd, capture_output=True, check=True, text=True, **kwargs).stdout + +def hash_data(data: str) -> bool: + return sha256(data.encode("utf-8")).hexdigest() + +def check_cmd(cmd_str: str, use_shell: bool, data_file: Path) -> str: + cmd = shlex.split(cmd_str) if not use_shell else cmd_str + old_hash = data_file.read_text() if data_file.exists() else None + new_data = run_cmd(cmd, shell=use_shell) + new_hash = hash_data(new_data) + if old_hash == new_hash: + return None + data_file.write_text(new_hash) + return new_data + +if __name__ == "__main__": + try: + data = check_cmd(MONITOR_COMMAND, USE_SHELL, DATA_PATH) + if data: + tg_msg(f"{MONITOR_DESC} changed to:\n```\n{data}\n```") + except Exception as e: + tg_msg(f"Got exception while running command of {MONITOR_DESC}: {str(e)}") + raise e diff --git a/roles/misc/tg_monitor_cmd/templates/monitor.service b/roles/misc/tg_monitor_cmd/templates/monitor.service new file mode 100644 index 0000000..6e9cc69 --- /dev/null +++ b/roles/misc/tg_monitor_cmd/templates/monitor.service @@ -0,0 +1,23 @@ +[Unit] +Description={{ service_description }} + +[Service] +Type=simple +ExecStart={{ script_path | quote }} +User={{ system_user }} +Group={{ system_user }} + +UMask=007 +PrivateTmp=yes +PrivateDevices=yes +ProtectHome=yes +ReadOnlyPaths=/ +ReadWritePaths=-{{ data_path }} + +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectControlGroups=true +RestrictRealtime=true +RestrictNamespaces=true + +ProtectSystem=full diff --git a/roles/misc/tg_monitor_cmd/templates/monitor.timer b/roles/misc/tg_monitor_cmd/templates/monitor.timer new file mode 100644 index 0000000..be7a2ef --- /dev/null +++ b/roles/misc/tg_monitor_cmd/templates/monitor.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Timer of {{ service_description }} + +[Timer] +OnCalendar={{ calendar_spec }} + +[Install] +WantedBy=multi-user.target