diff --git a/README.md b/README.md index 21f31e1..6a7e2e4 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Available variables: | Name | Required | Description | | ----------------------- |:--------:| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `location` | yes | The location of the Backend. Currently, [Local](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#local), [SFTP](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#sftp), [S3](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#amazon-s3) and [B2](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#backblaze-b2) are supported | +| `location` | yes | The location of the Backend. Currently, [Local](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#local), [SFTP](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#sftp), [S3](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#amazon-s3), [B2](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#backblaze-b2) and [Azure](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#microsoft-azure-blob-storage) are supported | | `password` | yes | The password used to secure this repository | | `init` | no | Describes if the repository should be initialized or not. Use `false` if you are backuping to an already existing repo. | | `aws_access_key` | no | The access key for the S3 backend | @@ -99,6 +99,8 @@ Available variables: | `aws_default_region` | no | The desired region for the S3 backend | | `b2_account_id` | no | The account ID for Backblaze B2 backend | | `b2_account_key` | no | The account key for Backblaze B2 backend | +| `azure_account_name` | no | The account Name for Azure Storage backend | +| `azure_account_key` | no | The account key for Azure Storage backend | Example: ```yaml @@ -123,10 +125,13 @@ Available variables: | ------------------ |:-----------------------------:| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | yes | The name of this backup. Used together with pruning and scheduling and needs to be unique. | | `repo` | yes | The name of the repository to backup to. | -| `src` | yes | The source directory or file | +| `src` | yes | List of the source directories or files | | `stdin` | no | Is this backup created from a [stdin](https://restic.readthedocs.io/en/stable/040_backup.html#reading-data-from-stdin)? | | `stdin_cmd` | no (yes if `stdin` == `true`) | The command to produce the stdin. | | `stdin_filename` | no | The filename used in the repository. | +| `before_script` | no | Content for a script, that will be executed before the backup command. | +| `notify_script` | no | Content for a notify, that will be executed after the backup and forget command command. | +| `notify_zabbix` | no | Details to notify an Zabbix Server after each backup run. | | `tags` | no | Array of default tags | | `keep_last` | no | If set, only keeps the last n snapshots. | | `keep_hourly` | no | If set, only keeps the last n hourly snapshots. | @@ -142,6 +147,8 @@ Available variables: | `schedule_hour` | no (`*`) | Hour when the job is run. ( 0-23, *, */2, etc ) | | `schedule_weekday` | no (`*`) | Weekday when the job is run. ( 0-6 for Sunday-Saturday, *, etc ) | | `schedule_month` | no (`*`) | Month when the job is run. ( 1-12, *, */2, etc ) | +| `disable_logging` | no (`false`) | Allows you to enable/disable the logging. | +| `max_result_log_size` | no (`500000`) | Allows you to specify max result log file size. Default deletes the result log file if it is bigger than 500 KB. | | `exclude` | no (`{}`) | Allows you to specify files to exclude. See [Exclude](#exclude) for reference. | Example: @@ -150,7 +157,10 @@ restic_backups: data: name: data repo: remote - src: /path/to/data + src: + - /path/to/data + before_script: "{{ lookup('file', 'before-script.sh') }}" + notify_script: "{{ lookup('file', 'notify-cript.sh') }}" scheduled: true schedule_hour: 3 ``` @@ -177,6 +187,50 @@ exclude: Please refer to the use of the specific keys to the [documentation](https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files). +#### Before Script +The `before_script` key on a backup allows you to specify content for an script which will executed before all backup commands. +The content can be specified in an extra file an loaded like that: + `before_script: "{{ lookup('file', 'before_script.sh') }}"` + +This script can be used for example to create an db-dump before the backup. + +#### Notify Script +The `notifiy_script` key on a backup allows you to specify content for an script which will executed after all backup commands. +The content can be specified in an extra file an loaded like that: + `notifiy_script: "{{ lookup('file', 'notifiy_script.sh') }}"` + +The noify_script will be executed with an parameter which shows the status of the backup process. + +| Code | Description | +| ---- | ----------- | +| "NO_ERROR" | all good | +| "BEFORE_SCRIPT_FAILED" | before scirpt has failed, so backup and forget will not be executed | +| "BACKUP_FAILED" | restic backup command has failed, so forget will not be executed | +| "FORGET_FAILED" | restic forget command has failed | + +The passed status parameter can be handled like this: +```bash +#!/bin/sh +echo "afterScript" + +if [[ "$1" = "NO_ERROR" ]] +then + echo "No Error!" +else + echo "Errors:" $1 +fi +``` + +#### Notify Zabbix +The `notify_zabix` key on a backup allows you to specify details to notify an Zabbix Server after each backup run. You can specify the follwing keys: +```yaml +notify_zabbix: + serverHost: 192.168.1.5 + serverPort: 10051 + host: fooBar + key: fooBarBackupState +``` + ## Dependencies none ## Example Playbook diff --git a/tasks/configure.yml b/tasks/configure.yml index 9e09609..d881b21 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -12,6 +12,8 @@ AWS_DEFAULT_REGION: '{{ item.value.aws_default_region | default("") }}' B2_ACCOUNT_ID: '{{ item.value.b2_account_id | default("") }}' B2_ACCOUNT_KEY: '{{ item.value.b2_account_key | default("") }}' + AZURE_ACCOUNT_NAME: '{{ item.value.azure_account_name | default("") }}' + AZURE_ACCOUNT_KEY: '{{ item.value.azure_account_key | default("") }}' no_log: true register: restic_init changed_when: "'created restic repository' in restic_init.stdout" diff --git a/tasks/distribution/Linux.yml b/tasks/distribution/Linux.yml index 167e5aa..0baebc6 100644 --- a/tasks/distribution/Linux.yml +++ b/tasks/distribution/Linux.yml @@ -1,16 +1,10 @@ --- # tasks file for skeleton -- name: reformat dict if necessary - set_fact: - restic_backups: "{{ restic_backups|dict2items|json_query('[*].value') }}" - when: - - restic_backups | type_debug == "dict" - - name: Create backup credentials template: src: restic_access_Linux.j2 - dest: '{{ restic_script_dir }}/access-{{ item.name }}.sh' + dest: '{{ restic_script_dir }}/access-{{ item.repo }}.sh' mode: '0700' owner: '{{ restic_dir_owner }}' group: '{{ restic_dir_group }}' @@ -25,10 +19,12 @@ - name: Create backup script template: src: restic_script_Linux.j2 - dest: '{{ restic_script_dir }}/backup-{{ item.name }}.sh' + dest: '{{ restic_script_dir }}/{{ item.name }}-backup/backup.sh' mode: '0700' owner: '{{ restic_dir_owner }}' group: '{{ restic_dir_group }}' + lstrip_blocks: yes + trim_blocks: yes no_log: true with_items: '{{ restic_backups }}' when: @@ -37,10 +33,54 @@ - item.src is defined or item.stdin and item.stdin_cmd is defined - item.repo in restic_repos +- name: create before script + copy: + content: "{{ item.before_script }}" + dest: '{{ restic_script_dir }}/{{ item.name }}-backup/before.sh' + mode: '0700' + owner: '{{ restic_dir_owner }}' + group: '{{ restic_dir_group }}' + no_log: true + with_items: '{{ restic_backups }}' + when: + - item.name is defined + - item.before_script is defined + - item.repo in restic_repos + +- name: create notify script + copy: + content: "{{ item.notify_script }}" + dest: '{{ restic_script_dir }}/{{ item.name }}-backup/notify.sh' + mode: '0700' + owner: '{{ restic_dir_owner }}' + group: '{{ restic_dir_group }}' + no_log: true + with_items: '{{ restic_backups }}' + when: + - item.name is defined + - item.notify_script is defined + - item.repo in restic_repos + +- name: zabbix notification + include: '../zabbix.yml' + with_items: '{{ restic_backups }}' + no_log: true + when: + - item.notify_zabbix is defined + - item.repo in restic_repos + +- name: zabbix passive check + include: '../zabbix-passive.yml' + with_items: '{{ restic_backups }}' + no_log: true + when: + - item.passive_zabbix_check is defined + - item.repo in restic_repos + - name: Setup CRON jobs cron: name: 'arillso.restic backup {{ item.name }}' - job: 'CRON=true {{ restic_script_dir }}/backup-{{ item.name }}.sh' + job: 'CRON=true {{ restic_script_dir }}/{{ item.name }}-backup/backup.sh' minute: '{{ item.schedule_minute | default("*") }}' hour: '{{ item.schedule_hour | default("*") }}' weekday: '{{ item.schedule_weekday | default("*") }}' diff --git a/tasks/main.yml b/tasks/main.yml index 685812e..7018c19 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,6 +1,12 @@ --- # tasks file for skeleton +- name: reformat dict if necessary + set_fact: + restic_backups: "{{ restic_backups|dict2items|json_query('[*].value') }}" + when: + - restic_backups | type_debug == "dict" + - name: add OS specific variables include_vars: '{{ loop_vars }}' with_first_found: @@ -32,6 +38,16 @@ group: '{{ restic_dir_group }}' with_items: '{{ restic_create_paths }}' +- name: Ensure backup script directories exist + file: + state: 'directory' + path: '{{ restic_script_dir }}/{{ item.name }}-backup' + mode: '0755' + owner: '{{ restic_dir_owner }}' + group: '{{ restic_dir_group }}' + no_log: true + with_items: '{{ restic_backups }}' + - name: Check if downloaded binary is present stat: path: '{{ restic_download_path }}/bin/restic-{{ restic_version }}' diff --git a/tasks/zabbix-passive.yml b/tasks/zabbix-passive.yml new file mode 100644 index 0000000..3d865e2 --- /dev/null +++ b/tasks/zabbix-passive.yml @@ -0,0 +1,10 @@ +--- + +- name: create zabbix backups JSON + template: + src: zabbix_backups_Linux.j2 + dest: '{{ restic_script_dir }}/backups.json' + mode: '0704' + owner: '{{ restic_dir_owner }}' + group: '{{ restic_dir_group }}' + no_log: true diff --git a/tasks/zabbix.yml b/tasks/zabbix.yml new file mode 100644 index 0000000..00dbab7 --- /dev/null +++ b/tasks/zabbix.yml @@ -0,0 +1,15 @@ +--- + +- name: create zabbix-sender script + copy: + src: "templates/zabbix-sender.sh" + dest: '{{ restic_script_dir }}/zabbix-sender.sh' + mode: '0700' + owner: '{{ restic_dir_owner }}' + group: '{{ restic_dir_group }}' + no_log: true + +- name: install netcat + package: + name: netcat + state: present \ No newline at end of file diff --git a/templates/restic_access_Linux.j2 b/templates/restic_access_Linux.j2 index edcaf8e..bbb1903 100644 --- a/templates/restic_access_Linux.j2 +++ b/templates/restic_access_Linux.j2 @@ -20,7 +20,9 @@ export B2_ACCOUNT_ID={{ restic_repos[item.repo].b2_account_id }} {% if restic_repos[item.repo].b2_account_key is defined %} export B2_ACCOUNT_KEY={{ restic_repos[item.repo].b2_account_key }} {% endif %} -BACKUP_NAME={{ item.name }} -{% if item.src is defined %} -BACKUP_SOURCE={{ item.src }} +{% if restic_repos[item.repo].azure_account_name is defined %} +export AZURE_ACCOUNT_NAME={{ restic_repos[item.repo].azure_account_name }} +{% endif %} +{% if restic_repos[item.repo].azure_account_key is defined %} +export AZURE_ACCOUNT_KEY={{ restic_repos[item.repo].azure_account_key }} {% endif %} diff --git a/templates/restic_script_Linux.j2 b/templates/restic_script_Linux.j2 index 412f37e..b1695db 100644 --- a/templates/restic_script_Linux.j2 +++ b/templates/restic_script_Linux.j2 @@ -2,37 +2,33 @@ # {{ ansible_managed }} # Backup script for {{ item.src|default('stdin') }} # Use this file to create a Backup and prune existing data with one execution. - {% if item.disable_logging is defined and item.disable_logging %} -{% set backup_result_log, backup_output_log = "/dev/null", "/dev/null" %} -{% set forget_result_log, forget_output_log = "/dev/null", "/dev/null" %} +{% set backup_output_log = "/dev/null" %} +{% set forget_output_log = "/dev/null" %} +{% set before_output_log = "/dev/null" %} +{% set notify_output_log = "/dev/null" %} +{% set result_log = "/dev/null" %} +{% set state_log = "/dev/null" %} {% else %} -{% set backup_result_log, backup_output_log = restic_log_dir + "/" + item.name + "-backup-result.log", restic_log_dir + "/" + item.name + "-backup-output.log" %} -{% set forget_result_log, forget_output_log = restic_log_dir + "/" + item.name + "-forget-result.log", restic_log_dir + "/" + item.name + "-forget-output.log" %} +{% set backup_output_log = restic_log_dir + "/" + item.name + "-backup-output.log" %} +{% set forget_output_log = restic_log_dir + "/" + item.name + "-forget-output.log" %} +{% set before_output_log = restic_log_dir + "/" + item.name + "-before-output.log" %} +{% set notify_output_log = restic_log_dir + "/" + item.name + "-notify-output.log" %} +{% set result_log = restic_log_dir + "/" + item.name + "-result.log" %} +{% set state_log = restic_log_dir + "/" + item.name + ".state" %} {% endif %} +# go garbage collector optimized +export GOGC=20 + +{# + Include access file +#} +# Include access file +source {{ restic_script_dir }}/access-{{ item.repo }}.sh + +STATUS="NO_ERROR" +set -uo pipefail -export RESTIC_REPOSITORY={{ restic_repos[item.repo].location }} -export RESTIC_PASSWORD='{{ restic_repos[item.repo].password | regex_replace('\'', '\'\\\'\'') }}' -BACKUP_NAME={{ item.name }} -{% if restic_repos[item.repo].aws_access_key is defined %} -export AWS_ACCESS_KEY_ID={{ restic_repos[item.repo].aws_access_key }} -{% endif %} -{% if restic_repos[item.repo].aws_secret_access_key is defined %} -export AWS_SECRET_ACCESS_KEY='{{ restic_repos[item.repo].aws_secret_access_key | regex_replace('\'', '\'\\\'\'') }}' -{% endif %} -{% if restic_repos[item.repo].aws_default_region is defined %} -export AWS_DEFAULT_REGION={{ restic_repos[item.repo].aws_default_region }} -{% endif %} -{% if restic_repos[item.repo].b2_account_id is defined %} -export B2_ACCOUNT_ID={{ restic_repos[item.repo].b2_account_id }} -{% endif %} -{% if restic_repos[item.repo].b2_account_key is defined %} -export B2_ACCOUNT_KEY={{ restic_repos[item.repo].b2_account_key }} -{% endif %} -{% if item.src is defined %} -BACKUP_SOURCE={{ item.src }} -{% endif %} -set -uxo pipefail {# Define Tags #} @@ -58,86 +54,207 @@ set -uxo pipefail {% if n is defined %} --stdin-filename {{ n }}{% endif %} {%- endmacro %} {# - Define path + Define backup src list #} -{% macro path(repo) -%} - {% if repo.src is defined and repo.src != None and (repo.src|length>0) %}{{ repo.src }}{% else %}{{ repo.stdin_filename }}{% endif %} +{% macro backup_src_list(item) -%} + {% if item.src is defined -%} + {% for src_item in item.src -%} + {{ src_item + " " }} + {%- endfor %} + {%- endif %} +{%- endmacro %} +{# + Define pathAttribute +#} +{% macro path_attribute(repo) -%} + {% if repo.src is defined and repo.src != None and (repo.src|length>0) -%} + {% for src_item in repo.src -%} + --path {{ src_item + " " }} + {%- endfor %} + {% else %} + --path {{ repo.stdin_filename + " " }} + {% endif %} {%- endmacro %} {# Define retention pattern #} {% macro retention_pattern(repo) -%} - {% if repo.keep_last is defined and repo.keep_last != None %}--keep-last {{ item.keep_last }}{% endif %} \ - {% if repo.keep_hourly is defined and repo.keep_hourly != None %}--keep-hourly {{ item.keep_hourly }}{% endif %} \ - {% if repo.keep_daily is defined and repo.keep_daily != None %}--keep-daily {{ item.keep_daily }}{% endif %} \ - {% if repo.keep_weekly is defined and repo.keep_weekly != None %}--keep-weekly {{ item.keep_weekly }}{% endif %} \ - {% if repo.keep_monthly is defined and repo.keep_monthly != None %}--keep-monthly {{ item.keep_monthly }}{% endif %} \ - {% if repo.keep_yearly is defined and repo.keep_yearly != None %}--keep-yearly {{ item.keep_yearly }}{% endif %} \ - {% if repo.keep_within is defined and repo.keep_within != None %}--keep-within {{ item.keep_within }}{% endif %} \ + {% if repo.keep_last is defined and repo.keep_last != None %}--keep-last {{ item.keep_last }} \{% endif %} + {% if repo.keep_hourly is defined and repo.keep_hourly != None %}--keep-hourly {{ item.keep_hourly }} \{% endif %} + {% if repo.keep_daily is defined and repo.keep_daily != None %}--keep-daily {{ item.keep_daily }} \{% endif %} + {% if repo.keep_weekly is defined and repo.keep_weekly != None %}--keep-weekly {{ item.keep_weekly }} \{% endif %} + {% if repo.keep_monthly is defined and repo.keep_monthly != None %}--keep-monthly {{ item.keep_monthly }} \{% endif %} + {% if repo.keep_yearly is defined and repo.keep_yearly != None %}--keep-yearly {{ item.keep_yearly }} \{% endif %} + {% if repo.keep_within is defined and repo.keep_within != None %}--keep-within {{ item.keep_within }} \{% endif %} {% if repo.keep_tag is defined and (repo.keep_tag|length>0) %}{{ keep_tags(repo.keep_tag) }}{% endif %} {%- endmacro %} - -{% macro exclude(exclude) %} +{# + Define path +#} +{% macro path(repo) -%} + {% if repo.src is defined and repo.src != None and (repo.src|length>0) %}{{ repo.src }}{% else %}{{ repo.stdin_filename }}{% endif %} +{%- endmacro %} +{# + Define exclude macro +#} +{% macro exclude(exclude) -%} {% if exclude.exclude_cache is defined and exclude.exclude_cache == true %} - --exclude-cache \ + --exclude-cache \ {% endif %} {% if exclude.exclude is defined %} {% for path in exclude.exclude %} - --exclude {{ path }} \ + --exclude={{ path }} \ {% endfor %} {% endif %} {% if exclude.iexclude is defined %} {% for path in exclude.iexclude %} - --iexclude {{ path }} \ + --iexclude={{ path }} \ {% endfor %} {% endif %} {% if exclude.exclude_file is defined %} {% for path in exclude.exclude_file %} - --exclude-file {{ path }} \ + --exclude-file={{ path }} \ {% endfor %} {% endif %} {% if exclude.exclude_if_present is defined %} {% for path in exclude.exclude_if_present %} - --exclude-if-present {{ path }} \ + --exclude-if-present={{ path }} \ {% endfor %} {% endif %} -{% endmacro %} +{%- endmacro %} {# - Define backup commands + deletes log file if to big #} -if [[ -z ${CRON+x} ]]; then - MODE_TAG="--tag manual" -else - MODE_TAG="--tag cron" +{% if item.disable_logging is not defined or not item.disable_logging -%} +# deletes log file if to big (default 500 KB max) +printf "################## cleanup result log ##################\n" +{% if item.max_result_log_size is defined -%} +MAX_LOGSIZE={{ item.max_result_log_size }} +{% else -%} +MAX_LOGSIZE=500000 +{% endif %} +LOGSIZE=$(stat -c%s {{ result_log }}) +echo "Size of result log is $LOGSIZE bytes." +if [[ "$LOGSIZE" -gt "$MAX_LOGSIZE" ]] +then + echo "Result log will be deleted." + rm {{ result_log}} fi -{% if item.stdin is defined and item.stdin == true %} - {{ item.stdin_cmd }} | {{ restic_install_path }}/restic backup \ - --stdin $MODE_TAG \ - {{ tags(item.tags) }} \ - {{ stdin_filename(item.stdin_filename) }} \ - {% if item.exclude is defined %}{{ exclude(item.exclude) }}{% endif %} \ - $@ \ -{% else %} - {{ restic_install_path }}/restic backup $BACKUP_SOURCE $MODE_TAG \ - {{ tags(item.tags) }} \ - {% if item.exclude is defined %}{{ exclude(item.exclude) }}{% endif %} \ - $@ \ -{% endif %} | tee {{ backup_output_log }} -if [[ $? -eq 0 ]] +{% endif -%} +{# + Define beforeScript execution +#} +{% if item.before_script is defined -%} +# before script +printf "\n\n################## before script ##################\n" +bash {{ restic_script_dir }}/{{ item.name }}-backup/before.sh |& tee {{ before_output_log }} +if [ $? -eq 0 ] then - echo "$(date -u '+%Y-%m-%d %H:%M:%S') OK" >> {{ backup_result_log }} + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') before script:\t OK" >> {{ result_log }} else - echo "$(date -u '+%Y-%m-%d %H:%M:%S') ERROR" >> {{ backup_result_log }} + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') before script:\t ERROR" >> {{ result_log }} + STATUS="BEFORE_SCRIPT_FAILED" +fi + +{% endif -%} +{# + Define backup commands +#} +# backup command +if [[ "$STATUS" = "NO_ERROR" ]] +then +printf "\n\n################## backup command ##################\n" + if [[ -z ${CRON+x} ]]; then + MODE_TAG="--tag manual" + else + MODE_TAG="--tag cron" + fi + + {% if item.stdin is defined and item.stdin == true %} + {{ item.stdin_cmd }} | {{ restic_install_path }}/restic backup \ + --stdin $MODE_TAG \ + {{ tags(item.tags) }} \ + {{ stdin_filename(item.stdin_filename) }} \ + {% if item.exclude is defined -%} + {{ exclude(item.exclude) }} \ + {% endif %} + $@ \ + {% else %} + {{ restic_install_path }}/restic backup {{ backup_src_list(item) }}\ + $MODE_TAG \ + {% if item.tags is defined %} + {{ tags(item.tags) }} \ + {% endif %} + {% if item.one_file_system is defined and item.one_file_system %} + --one-file-system \ + {% endif %} + {% if item.exclude is defined -%}{{ exclude(item.exclude) }}{% endif %} + $@ \ + {% endif %} + |& tee {{ backup_output_log }} + + if [ $? -eq 0 ] + then + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') backup command:\t OK" >> {{ result_log }} + else + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') backup command:\t ERROR" >> {{ result_log }} + STATUS="BACKUP_FAILED" + fi fi {# Define stdin forget commands #} -{{ restic_install_path }}/restic forget --path {{ path(item) }} {{ retention_pattern(item) }} {% if item.prune is defined and item.prune == true %}--prune{% endif %} | tee {{ forget_output_log }} -if [[ $? -eq 0 ]] +# forget command +if [[ "$STATUS" = "NO_ERROR" ]] then - echo "$(date -u '+%Y-%m-%d %H:%M:%S') OK" >> {{ forget_result_log }} +printf "\n\n################## forget command ##################\n" + {{ restic_install_path }}/restic forget {{ path_attribute(item) }}\ + {{ retention_pattern(item) }} + {% if item.prune is defined and item.prune == true %} + --prune \ + {% endif %} + |& tee {{ forget_output_log }} + if [ $? -eq 0 ] + then + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') forget command:\t OK" >> {{ result_log }} + else + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') forget command:\t ERROR" >> {{ result_log }} + STATUS="FORGET_FAILED" + fi +fi + +{# + passiv zabbix check +#} +{% if item.passive_zabbix_check is defined %} +# passive zabbix check +printf "\n\n################## passive zabbix check ##################\n" +echo -e "$STATUS" |& tee {{ state_log }} +{% endif %} + +{# + Define notifyScript execution +#} +{% if item.notify_script is defined %} +# notify script +printf "\n\n################## notify script ##################\n" +bash {{ restic_script_dir }}/{{ item.name }}-backup/notify.sh $STATUS |& tee {{ notify_output_log }} +if [ $? -eq 0 ] +then + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') notify script:\t OK" >> {{ result_log }} else - echo "$(date -u '+%Y-%m-%d %H:%M:%S') ERROR" >> {{ forget_result_log }} + echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') notify script:\t ERROR" >> {{ result_log }} fi +{% endif %} + +{# + Define notify zabbix execution +#} +{% if item.notify_zabbix is defined %} +# notify zabbix +printf "\n\n################## notify zabbix ##################\n" +bash {{ restic_script_dir }}/zabbix-sender.sh -z {{ item.notify_zabbix.serverHost }} -p {{ item.notify_zabbix.serverPort }} -s {{ item.notify_zabbix.host }} -k {{ item.notify_zabbix.key }} -o $STATUS +echo -e "$(date -u '+%Y-%m-%d %H:%M:%S') zabbix req done" >> {{ result_log }} +{% endif %} diff --git a/templates/zabbix-sender.sh b/templates/zabbix-sender.sh new file mode 100644 index 0000000..262e140 --- /dev/null +++ b/templates/zabbix-sender.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +### Zabbix sender implementation in bourne shell +# +# Requires: nc, cut + +usage() { +echo -n "Usage: + zabbix_sender.sh -z server [-p port] -s host -k key -o value + zabbix_sender.sh -z server [-p port] -i input-file +" +exit 1 +} + +# Check if nc is available +if ! which nc >/dev/null; then + echo "nc is not avaiable, cannot continue" + exit 1 +fi + +# Check first if any arguments were provided +if [ $# -eq 0 ]; then + usage +fi + +# Put the arguments in variables +while [ $# -gt 0 ]; do +case "$1" in + -z|--zabbix-server) + serverHost="$2" + shift 2 + ;; + -p|--port) + serverPort="$2" + shift 2 + ;; + -s|--host) + clientHost="$2" + shift 2 + ;; + -k|--key) + itemKey="$2" + shift 2 + ;; + -o|--value) + itemValue="$2" + shift 2 + ;; + -i|--input-file) + inputFile="$2" + shift 2 + ;; + *) + echo "Unknown parameter: $1" + exit 1 + ;; +esac +done + +# Check if server was specified +if [ -z "$serverHost" ]; then + echo "Server hostname or IP must be specified" + echo "" + usage +fi + +# Check if port was specified, if not, set to default +if [ -z "$serverPort" ]; then + serverPort="10051" +fi + +# Check if input file or key/value pair was specified +if [ -n "$inputFile" ]; then + # Begin JSON data + data='{"request":"sender data","data":[' + # Iterate over all lines in $inputFile and add the items to the JSON data + while read line; do + data=$data'{"host":'\"$(echo $line | cut -d' ' -f1)\"',"key":'\"$(echo $line | cut -d' ' -f2)\"',"value":'\"$(echo $line | cut -d' ' -f3-)\"'},' + done < $inputFile + # Remove last , and end JSON + data=${data%?}']}' + # Build header - https://www.zabbix.com/documentation/4.0/manual/appendix/protocols/header_datalen + length=$(printf '%016x' "${#data}") + pack="" + for (( i=14; i>=0; i-=2 )); do pack="$pack\\x${length:$i:2}"; done + echo "Server query: $(printf "ZBXD\\1$pack%s" "$data")" + # Send it to the server + response=$(printf "ZBXD\\1$pack%s" "$data" | nc $serverHost $serverPort) + echo "Server response: $response" +elif [ -n "$itemKey" ] && [ -n "$itemValue" ]; then + # Build JSON data + data='{"request":"sender data","data":[{"host":'\"$clientHost\"',"key":'\"$itemKey\"',"value":'\"$itemValue\"'}]}' + # Build header - https://www.zabbix.com/documentation/4.0/manual/appendix/protocols/header_datalen + length=$(printf '%016x' "${#data}") + pack="" + for (( i=14; i>=0; i-=2 )); do pack="$pack\\x${length:$i:2}"; done + echo "Server query: $(printf "ZBXD\\1$pack%s" "$data")" + # Send it to the server + response=$(printf "ZBXD\\1$pack%s" "$data" | nc $serverHost $serverPort) + echo "Server response: $response" +else + echo "Either input file or key/value pair must be specified" + echo "" + usage +fi diff --git a/templates/zabbix_backups_Linux.j2 b/templates/zabbix_backups_Linux.j2 new file mode 100644 index 0000000..bcb0edf --- /dev/null +++ b/templates/zabbix_backups_Linux.j2 @@ -0,0 +1,11 @@ +[ +{% for item in restic_backups %} +{% if item.passive_zabbix_check is defined and item.passive_zabbix_check %} + {% raw %}{ "{#BACKUP}": "{% endraw %}{{ item.name }}{% raw %}" }{% endraw %} +{% if not loop.last %} +, +{% endif %} +{% endif %} +{% endfor %} + +]