From 77b2e50b241c4be59f58758091d21e86b0284193 Mon Sep 17 00:00:00 2001 From: Nis Wechselberg Date: Wed, 26 Jul 2023 20:58:07 +0200 Subject: [PATCH] Initial version of duplicity collection --- .gitignore | 179 +++++++++++++++++++++++ README.md | 13 ++ galaxy.yml | 70 +++++++++ meta/runtime.yml | 52 +++++++ plugins/README.md | 31 ++++ roles/duplicity-client/defaults/main.yml | 2 + roles/duplicity-client/handlers/main.yml | 2 + roles/duplicity-client/tasks/main.yml | 2 + roles/duplicity-client/tests/inventory | 2 + roles/duplicity-client/tests/test.yml | 5 + roles/duplicity-client/vars/main.yml | 2 + roles/duplicity-server/defaults/main.yml | 2 + roles/duplicity-server/handlers/main.yml | 2 + roles/duplicity-server/tasks/main.yml | 144 ++++++++++++++++++ roles/duplicity-server/tests/inventory | 2 + roles/duplicity-server/tests/test.yml | 5 + roles/duplicity-server/vars/main.yml | 2 + 17 files changed, 517 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 galaxy.yml create mode 100644 meta/runtime.yml create mode 100644 plugins/README.md create mode 100644 roles/duplicity-client/defaults/main.yml create mode 100644 roles/duplicity-client/handlers/main.yml create mode 100644 roles/duplicity-client/tasks/main.yml create mode 100644 roles/duplicity-client/tests/inventory create mode 100644 roles/duplicity-client/tests/test.yml create mode 100644 roles/duplicity-client/vars/main.yml create mode 100644 roles/duplicity-server/defaults/main.yml create mode 100644 roles/duplicity-server/handlers/main.yml create mode 100644 roles/duplicity-server/tasks/main.yml create mode 100644 roles/duplicity-server/tests/inventory create mode 100644 roles/duplicity-server/tests/test.yml create mode 100644 roles/duplicity-server/vars/main.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c01f181 --- /dev/null +++ b/.gitignore @@ -0,0 +1,179 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,ansible +# Edit at https://www.toptal.com/developers/gitignore?templates=python,ansible + +### Ansible ### +*.retry + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python,ansible diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c1c807 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Ansible Collection - de_enbewe.duplicity + +Collection containing rules to implement a "pull-style" backup with duplicity. + +## Role - duplicity-server + +This role configures the host to act as a storage server for backups. +This server accesses the duplicity clients through SSH and mounts the data that should be backed up. It then runs a duplicity backup over these directories. + +## Role - duplicity-client + +This role configures the host to have directories backed up by the duplicity servers. + diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..915ee2c --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,70 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: de_enbewe + +# The name of the collection. Has the same character restrictions as 'namespace' +name: duplicity + +# The version of the collection. Must be compatible with semantic versioning +version: 1.0.0 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: +- eNBeWe + + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: Roles to configure backups through duplicity + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: +- MIT + +# The path to the license file for the collection. This path is relative to the root of the collection. This key is +# mutually exclusive with 'license' +license_file: '' + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: + - duplicity + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: {} + +# The URL of the originating SCM repository +repository: https://git.enbewe.de/Coding/ansible-collection-duplicity.git + +# The URL to any online docs +documentation: https://git.enbewe.de/Coding/ansible-collection-duplicity/wiki + +# The URL to the homepage of the collection/project +homepage: https://git.enbewe.de/Coding/ansible-collection-duplicity + +# The URL to the collection issue tracker +issues: https://git.enbewe.de/Coding/ansible-collection-duplicity/issues + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This +# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', +# and '.git' are always filtered. Mutually exclusive with 'manifest' +build_ignore: [] + +# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a +# list of MANIFEST.in style +# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key +# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive +# with 'build_ignore' +# manifest: null + diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..4a78ebd --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,52 @@ +--- +# Collections must specify a minimum required ansible version to upload +# to galaxy +requires_ansible: '>=2.15.1' + +# Content that Ansible needs to load from another location or that has +# been deprecated/removed +# plugin_routing: +# action: +# redirected_plugin_name: +# redirect: ns.col.new_location +# deprecated_plugin_name: +# deprecation: +# removal_version: "4.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# removed_plugin_name: +# tombstone: +# removal_version: "2.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# become: +# cache: +# callback: +# cliconf: +# connection: +# doc_fragments: +# filter: +# httpapi: +# inventory: +# lookup: +# module_utils: +# modules: +# netconf: +# shell: +# strategy: +# terminal: +# test: +# vars: + +# Python import statements that Ansible needs to load from another location +# import_redirection: +# ansible_collections.ns.col.plugins.module_utils.old_location: +# redirect: ansible_collections.ns.col.plugins.module_utils.new_location + +# Groups of actions/modules that take a common set of options +# action_groups: +# group_name: +# - module1 +# - module2 diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6260634 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.15/plugins/plugins.html). diff --git a/roles/duplicity-client/defaults/main.yml b/roles/duplicity-client/defaults/main.yml new file mode 100644 index 0000000..7264489 --- /dev/null +++ b/roles/duplicity-client/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for duplicity-client diff --git a/roles/duplicity-client/handlers/main.yml b/roles/duplicity-client/handlers/main.yml new file mode 100644 index 0000000..0ec2670 --- /dev/null +++ b/roles/duplicity-client/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for duplicity-client diff --git a/roles/duplicity-client/tasks/main.yml b/roles/duplicity-client/tasks/main.yml new file mode 100644 index 0000000..c3679cb --- /dev/null +++ b/roles/duplicity-client/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# tasks file for duplicity-client diff --git a/roles/duplicity-client/tests/inventory b/roles/duplicity-client/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/roles/duplicity-client/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/roles/duplicity-client/tests/test.yml b/roles/duplicity-client/tests/test.yml new file mode 100644 index 0000000..37ba019 --- /dev/null +++ b/roles/duplicity-client/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - duplicity-client diff --git a/roles/duplicity-client/vars/main.yml b/roles/duplicity-client/vars/main.yml new file mode 100644 index 0000000..604728b --- /dev/null +++ b/roles/duplicity-client/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for duplicity-client diff --git a/roles/duplicity-server/defaults/main.yml b/roles/duplicity-server/defaults/main.yml new file mode 100644 index 0000000..1c91512 --- /dev/null +++ b/roles/duplicity-server/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for duplicity-server diff --git a/roles/duplicity-server/handlers/main.yml b/roles/duplicity-server/handlers/main.yml new file mode 100644 index 0000000..31eda2c --- /dev/null +++ b/roles/duplicity-server/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for duplicity-server diff --git a/roles/duplicity-server/tasks/main.yml b/roles/duplicity-server/tasks/main.yml new file mode 100644 index 0000000..69ad448 --- /dev/null +++ b/roles/duplicity-server/tasks/main.yml @@ -0,0 +1,144 @@ +--- +# - name: Install required software on servers +# become: true +# ansible.builtin.package: +# name: "{{ item }}" +# state: present +# with_items: +# - duplicity +# - sshfs +# - python3-packaging +# - acl + +- name: Create backup user on servers + become: true + ansible.builtin.user: + name: "{{ duplicity_server_user }}" + generate_ssh_key: true + ssh_key_type: ed25519 + +- name: Fetch server keys to local system + become: true + become_user: "{{ duplicity_server_user }}" + ansible.builtin.slurp: + src: ~/.ssh/id_ed25519.pub + register: duplicity_server_key + changed_when: false + +# - name: "Deploy server ssh keys to clients" +# when: +# - duplicity_client +# - hostvars[item].duplicity_server is defined and hostvars[item].duplicity_server +# become: true +# ansible.posix.authorized_key: +# user: "{{ duplicity_client_user }}" +# state: "present" +# key: "{{ lookup('file', 'buffer/{{item}}-id_ed25519.pub') }}" +# loop: "{{ groups['duplicity'] }}" + +# - name: "Fetch sshd fingerprints from clients" +# when: duplicity_client +# ansible.builtin.fetch: +# src: "/etc/ssh/ssh_host_ecdsa_key.pub" +# dest: "buffer/{{ ansible_host }}-ssh_host_ecdsa_key.pub" +# flat: true +# changed_when: false + +# - name: "Register client host keys in server" +# when: +# - duplicity_server +# - hostvars[item].duplicity_client is defined and hostvars[item].duplicity_client +# become: true +# become_user: "{{ duplicity_server_user }}" +# ansible.builtin.known_hosts: +# name: "{{ item }}" +# key: "{{ item }} {{ lookup('file', 'buffer/{{item}}-ssh_host_ecdsa_key.pub') }}" +# loop: "{{ groups['duplicity'] }}" + +# - name: "Test ssh connection from server to client" +# when: +# - duplicity_server +# - hostvars[item].duplicity_client is defined and hostvars[item].duplicity_client +# become: true +# become_user: "{{ duplicity_server_user }}" +# ansible.builtin.command: "ssh -o 'BatchMode yes' {{ duplicity_client_user }}@{{ item }} 'echo success'" +# changed_when: false +# loop: "{{ groups['duplicity'] }}" + +# - name: "Set default ACLs on backup data" +# when: duplicity_client +# become: true +# ansible.posix.acl: +# path: "{{ item }}" +# entity: "{{ duplicity_client_user }}" +# etype: "user" +# permissions: r-X +# default: true +# state: present +# recursive: true +# loop: "{{ duplicity_client_backup_paths }}" + +# - name: "Set read ACLs on existing backup data" +# when: duplicity_client +# become: true +# ansible.posix.acl: +# path: "{{ item }}" +# entity: "{{ duplicity_client_user }}" +# etype: "user" +# permissions: r-X +# state: present +# recursive: true +# loop: "{{ duplicity_client_backup_paths }}" + +# - name: "Ensure gnupg config dir" +# when: duplicity_server +# become: true +# become_user: "{{ duplicity_server_user }}" +# ansible.builtin.command: +# cmd: "gpg --list-keys" +# creates: "/home/{{ duplicity_server_user }}/.gnupg" + + +# - name: "Install encryption key for backups" +# when: duplicity_server +# become: true +# gpg_key: +# fpr: "C05AD49B790BAC8E3B573B697B25171F921B9E57" +# keyserver: "hkps://keys.openpgp.org" +# trust: "5" +# homedir: "/home/{{ duplicity_server_user }}/.gnupg" + +# - name: "Create backup script path" +# when: duplicity_server +# become: true +# ansible.builtin.file: +# path: "{{ duplicity_server_scriptdir }}" +# state: "directory" +# owner: "{{ duplicity_server_user }}" +# group: "{{ duplicity_server_user }}" +# mode: "u=rwx,g=rx,o=rx" + +# - name: "Create backup scripts for clients" +# when: +# - duplicity_server +# - hostvars[item].duplicity_client is defined and hostvars[item].duplicity_client +# become: true +# become_user: "{{ duplicity_server_user }}" +# ansible.builtin.template: +# src: "backup-script.j2" +# dest: "{{ duplicity_server_scriptdir }}/backup-{{ item }}.sh" +# mode: "u=rwx,g=rx,o=rx" +# loop: "{{ groups['duplicity'] }}" + +# - name: "Register cronjob for clients" +# when: +# - duplicity_server +# - hostvars[item].duplicity_client is defined and hostvars[item].duplicity_client +# become: true +# ansible.builtin.cron: +# name: "backup-{{ item }}" +# user: "{{ duplicity_server_user }}" +# job: "{{ duplicity_server_scriptdir }}/backup-{{ item }}.sh" +# minute: "{{ hostvars[item].duplicity_client_backup_minute }}" +# hour: "{{ hostvars[item].duplicity_client_backup_hour }}" +# loop: "{{ groups['duplicity'] }}" diff --git a/roles/duplicity-server/tests/inventory b/roles/duplicity-server/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/roles/duplicity-server/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/roles/duplicity-server/tests/test.yml b/roles/duplicity-server/tests/test.yml new file mode 100644 index 0000000..1039d0c --- /dev/null +++ b/roles/duplicity-server/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - duplicity-server diff --git a/roles/duplicity-server/vars/main.yml b/roles/duplicity-server/vars/main.yml new file mode 100644 index 0000000..e751025 --- /dev/null +++ b/roles/duplicity-server/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for duplicity-server