├── examples
├── ansible.cfg
├── Vagrantfile
└── provision.yml
├── vars
├── archlinux.yml
├── redhat.yml
├── debian.yml
└── main.yml
├── requirements.yml
├── .style.yapf
├── tasks
├── install_archlinux.yml
├── configure_snapshots.yml
├── manage_pools.yml
├── configure_zfs.yml
├── manage_volume.yml
├── manage_filesystem.yml
├── install_debian.yml
├── install_redhat.yml
├── main.yml
├── manage_pool.yml
└── manage_services.yml
├── templates
└── etc
│ ├── systemd
│ └── system
│ │ ├── systemd-journald.service.d
│ │ └── after-zfs-mount.service.j2
│ │ ├── zpool-trim@.timer.j2
│ │ ├── zpool-scrub@.timer.j2
│ │ ├── zpool-trim@.service.j2
│ │ └── zpool-scrub@.service.j2
│ ├── zrepl
│ └── zrepl.yml.j2
│ ├── modprobe.d
│ └── zfs.conf.j2
│ ├── yum.repos.d
│ ├── zrepl.repo.j2
│ └── zfs.repo.j2
│ ├── udev
│ └── rules.d
│ │ └── 90-zfs-io-scheduler.rules.j2
│ ├── cron.d
│ └── zfsnap.j2
│ └── default
│ └── zfs.j2
├── .bumpversion.cfg
├── .gitignore
├── .ansible-lint
├── .pre-commit-config.yaml
├── tox.ini
├── requirements.txt
├── .github
└── workflows
│ ├── publish.yml
│ └── release.yml
├── .editorconfig
├── handlers
└── main.yml
├── molecule
└── default
│ ├── molecule.yml
│ ├── prepare.yml
│ ├── verify.yml
│ └── converge.yml
├── .yamllint
├── meta
└── main.yml
├── .chglog
├── config.yml
└── CHANGELOG.tpl.md
├── test_plugins
└── list.py
├── LICENSE
├── files
├── centos
│ ├── RPM-GPG-KEY-zfsonlinux
│ └── RPM-GPG-KEY-zrepl
└── debian
│ └── zrepl-apt-key.asc
├── filter_plugins
├── split.py
└── selectattr2.py
├── CHANGELOG.md
├── library
└── prepare_zfs_properties.py
├── defaults
└── main.yml
├── CODE_OF_CONDUCT.md
└── README.md
/examples/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | roles_path = ../../
3 |
--------------------------------------------------------------------------------
/vars/archlinux.yml:
--------------------------------------------------------------------------------
1 | ---
2 | __zfs_zedletdir: /usr/lib/zfs/zed.d
3 |
--------------------------------------------------------------------------------
/vars/redhat.yml:
--------------------------------------------------------------------------------
1 | ---
2 | __zfs_zedletdir: /usr/libexec/zfs/zed.d
3 |
--------------------------------------------------------------------------------
/vars/debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 | __zfs_zedletdir: /usr/lib/zfs-linux/zed.d
3 |
--------------------------------------------------------------------------------
/requirements.yml:
--------------------------------------------------------------------------------
1 | ---
2 | roles: []
3 | collections:
4 | - community.general
5 |
--------------------------------------------------------------------------------
/.style.yapf:
--------------------------------------------------------------------------------
1 | [style]
2 | based_on_style = google
3 | column_limit = 120
4 | indent_width = 4
5 |
--------------------------------------------------------------------------------
/tasks/install_archlinux.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: fail Arch Linux installation
3 | ansible.builtin.fail:
4 | msg: Arch Linux is not yet supported by this role
5 |
--------------------------------------------------------------------------------
/templates/etc/systemd/system/systemd-journald.service.d/after-zfs-mount.service.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [Unit]
4 | After=zfs-mount.service
5 |
--------------------------------------------------------------------------------
/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | __zfs_zrepl_config_defaults:
3 | global:
4 | logging:
5 | - type: syslog
6 | level: warn
7 | format: human
8 |
--------------------------------------------------------------------------------
/templates/etc/zrepl/zrepl.yml.j2:
--------------------------------------------------------------------------------
1 | #jinja2: trim_blocks: "true", lstrip_blocks: "true"
2 | {{ ansible_managed | comment }}
3 |
4 | {{ cfg | to_nice_yaml(indent=2) }}
5 |
--------------------------------------------------------------------------------
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 3.3.1
3 | commit = true
4 | message = bump: version {current_version} → {new_version}
5 | tag = true
6 | tag_name = v{new_version}
7 | sign_tags = true
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ansible
2 | *.retry
3 | .cache
4 | .molecule
5 |
6 | # Python: Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 | .eggs/
11 |
12 | # Testing
13 | .tox/
14 | .vagrant/
15 | pytestdebug.log
16 |
--------------------------------------------------------------------------------
/templates/etc/modprobe.d/zfs.conf.j2:
--------------------------------------------------------------------------------
1 | #jinja2: trim_blocks: "true", lstrip_blocks: "true"
2 | {{ ansible_managed | comment }}
3 |
4 | {% for key, value in (zfs_kernel_module_parameters | dictsort()) %}
5 | options zfs {{ key }}={{ value }}
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/templates/etc/systemd/system/zpool-trim@.timer.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [Unit]
4 | Description=Trim ZFS Pool ({{ zfs_trim_schedule }})
5 |
6 | [Timer]
7 | OnCalendar={{ zfs_trim_schedule }}
8 | Persistent=true
9 |
10 | [Install]
11 | WantedBy=timers.target
12 |
--------------------------------------------------------------------------------
/templates/etc/systemd/system/zpool-scrub@.timer.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [Unit]
4 | Description=Scrub ZFS Pool ({{ zfs_scrub_schedule }})
5 |
6 | [Timer]
7 | OnCalendar={{ zfs_scrub_schedule }}
8 | Persistent=true
9 |
10 | [Install]
11 | WantedBy=timers.target
12 |
--------------------------------------------------------------------------------
/templates/etc/systemd/system/zpool-trim@.service.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [Unit]
4 | Description=Trim ZFS Pool %i
5 | Requires=zfs.target
6 | After=zfs.target
7 |
8 | [Service]
9 | Type=oneshot
10 | ExecStart=/sbin/zpool trim %i
11 | Nice=19
12 | IOSchedulingClass=idle
13 | KillSignal=SIGINT
14 |
--------------------------------------------------------------------------------
/templates/etc/systemd/system/zpool-scrub@.service.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [Unit]
4 | Description=Scrub ZFS Pool %i
5 | Requires=zfs.target
6 | After=zfs.target
7 |
8 | [Service]
9 | Type=oneshot
10 | ExecStart=/sbin/zpool scrub %i
11 | Nice=19
12 | IOSchedulingClass=idle
13 | KillSignal=SIGINT
14 |
--------------------------------------------------------------------------------
/.ansible-lint:
--------------------------------------------------------------------------------
1 | # skip the specified linter rules
2 | # see: https://ansible-lint.readthedocs.io/en/latest/default_rules.html
3 | skip_list:
4 | - name[casing]
5 | - name[missing]
6 | - name[template]
7 |
8 | # paths to be excluded from linting
9 | exclude_paths:
10 | - .cache/
11 | - .chglog/
12 | - .github/
13 | - .tox/
14 |
--------------------------------------------------------------------------------
/templates/etc/yum.repos.d/zrepl.repo.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [zrepl]
4 | name = zrepl
5 | baseurl = {{ zfs_zrepl_redhat_repo_url }}
6 | metadata_expire = 7d
7 | gpgcheck = 1
8 | gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
9 | {% if zfs_redhat_repo_proxy | default('') %}
10 | proxy = {{ zfs_redhat_repo_proxy }}
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/tasks/configure_snapshots.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: configure zrepl
3 | ansible.builtin.template:
4 | src: etc/zrepl/zrepl.yml.j2
5 | dest: /etc/zrepl/zrepl.yml
6 | owner: root
7 | group: root
8 | mode: "0644"
9 | validate: zrepl configcheck --config %s
10 | notify: restart zrepl
11 | when: zfs_zrepl_enabled | bool
12 | vars:
13 | cfg: "{{ __zfs_zrepl_config_defaults | combine(zfs_zrepl_config, recursive=True) }}"
14 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/pre-commit/pre-commit-hooks
4 | rev: v4.0.1
5 | hooks:
6 | - id: check-merge-conflict
7 | - id: check-added-large-files
8 | - id: end-of-file-fixer
9 | - id: mixed-line-ending
10 | - id: trailing-whitespace
11 |
12 | - repo: https://github.com/ansible-community/ansible-lint.git
13 | rev: v5.2.0
14 | hooks:
15 | - id: ansible-lint
16 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 4.0
3 | envlist = py{311}-ansible{216,217,218}
4 | skipsdist = true
5 |
6 | [testenv]
7 | passenv = *
8 | deps =
9 | ansible216: ansible-core == 2.16.*
10 | ansible217: ansible-core == 2.17.*
11 | ansible218: ansible-core == 2.18.*
12 | molecule
13 | molecule-vagrant
14 | ansible-lint
15 | commands = molecule test
16 | setenv =
17 | TOX_ENVNAME={envname}
18 | PY_COLORS=1
19 | ANSIBLE_FORCE_COLOR=1
20 | ANSIBLE_ROLES_PATH=../
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # PIP Requirements File
2 | #
3 | # The listed versions of Ansible and Jinja were tested to work with the role.
4 | # Any previous versions might not be compatible and are therefore not supported.
5 | # To install the PIP requirements run the following command:
6 | #
7 | # `python -m pip --user install -r requirements.txt`
8 | #
9 | # See the PIP requirements file documentation for details:
10 | # https://pip.pypa.io/en/stable/user_guide/#requirements-files
11 |
12 | ansible>=2.10
13 | jinja2>=2.11.2
14 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish on Ansible Galaxy
2 |
3 | on:
4 | workflow_run:
5 | workflows: ["Create GitHub Release"]
6 | types:
7 | - completed
8 |
9 | jobs:
10 | publish-on-ansible-galaxy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 |
16 | - name: Publish
17 | uses: robertdebock/galaxy-action@1.2.0
18 | with:
19 | galaxy_api_key: ${{ secrets.GALAXY_API_KEY }}
20 | git_branch: main
21 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps maintain consistent coding styles for multiple developers
2 | # working on the same project across various editors and IDEs.
3 | # Read more: https://editorconfig.org/
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 4
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
15 | [*.md]
16 | indent_size = 2
17 | trim_trailing_whitespace = false
18 |
19 | [*.{yml,yaml,toml}*]
20 | indent_size = 2
21 |
22 | [*.py]
23 | # isort options
24 | line_length = 120
25 |
--------------------------------------------------------------------------------
/templates/etc/udev/rules.d/90-zfs-io-scheduler.rules.j2:
--------------------------------------------------------------------------------
1 | #jinja2: trim_blocks: "true", lstrip_blocks: "true"
2 | {{ ansible_managed | comment }}
3 |
4 | {% for item in device_properties.results %}
5 | {% set properties = dict(item.stdout_lines | map('split', '=', 1)) %}
6 | ACTION=="add|change", KERNEL=="sd[a-z]", {{
7 | 'ENV{ID_SERIAL}=="' ~ properties.ID_SERIAL ~ '"' if properties.ID_SERIAL is defined else
8 | 'ENV{ID_PART_TABLE_UUID}=="' ~ properties.ID_PART_TABLE_UUID ~ '"' if properties.ID_PART_TABLE_UUID is defined else
9 | 'ENV{ID_PATH}=="' ~ properties.ID_PATH ~ '"'}}, ATTR{queue/scheduler}="none"
10 | {% endfor %}
11 |
--------------------------------------------------------------------------------
/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: reload udev rules
3 | ansible.builtin.shell: "udevadm control --reload && udevadm trigger"
4 | changed_when: true
5 |
6 | - name: update initramfs (Debian)
7 | ansible.builtin.command: update-initramfs -u -k all
8 | changed_when: true
9 | when: ansible_os_family | lower == 'debian'
10 |
11 | - name: mkinitcpio (Arch Linux)
12 | ansible.builtin.command: mkinitcpio -P
13 | changed_when: true
14 | when: ansible_os_family | lower == 'archlinux'
15 |
16 | - name: restart zrepl
17 | ansible.builtin.systemd:
18 | name: zrepl
19 | state: restarted
20 | when: zfs_zrepl_enabled | bool
21 |
--------------------------------------------------------------------------------
/molecule/default/molecule.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependency:
3 | name: galaxy
4 | options:
5 | role-file: requirements.yml
6 | requirements-file: requirements.yml
7 | driver:
8 | name: vagrant
9 | platforms:
10 | - name: molecule-zfs-debian11-$TOX_ENVNAME
11 | box: debian/bullseye64
12 | memory: 512
13 | cpus: 1
14 |
15 | - name: molecule-zfs-centosstream8-$TOX_ENVNAME
16 | box: bento/centos-stream-8
17 | memory: 512
18 | cpus: 1
19 | provisioner:
20 | name: ansible
21 | lint: ansible-lint --force-color
22 | lint: |
23 | set -e
24 | yamllint .
25 | ansible-lint
26 | verifier:
27 | name: ansible
28 |
--------------------------------------------------------------------------------
/.yamllint:
--------------------------------------------------------------------------------
1 | ---
2 | # extend default yamllint configuration
3 | # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
4 | extends: default
5 |
6 | rules:
7 | braces:
8 | max-spaces-inside: 1
9 | level: error
10 | brackets:
11 | max-spaces-inside: 1
12 | level: error
13 | colons:
14 | max-spaces-after: -1
15 | level: error
16 | commas:
17 | max-spaces-after: -1
18 | level: error
19 | empty-lines:
20 | max: 3
21 | level: error
22 | hyphens:
23 | level: error
24 | new-lines:
25 | type: unix
26 | comments-indentation: disable
27 | document-start: disable
28 | line-length: disable
29 | truthy: disable
30 |
31 | ignore: |
32 | .tox/
33 | .cache/
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create GitHub Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | jobs:
9 | create-github-release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 |
15 | - name: Extract release notes
16 | id: extract-release-notes
17 | uses: ffurrer2/extract-release-notes@v1
18 |
19 | - name: Create release
20 | uses: actions/create-release@v1
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | with:
24 | tag_name: ${{ github.ref }}
25 | release_name: ${{ github.ref }}
26 | draft: false
27 | prerelease: false
28 | body: ${{ steps.extract-release-notes.outputs.release_notes }}
29 |
--------------------------------------------------------------------------------
/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependencies: []
3 |
4 | galaxy_info:
5 | author: André Lehmann
6 | namespace: aisbergg
7 | role_name: zfs
8 | description: Install ZFS, create or import zpools and manage datasets.
9 | company: none
10 | issue_tracker_url: https://github.com/aisbergg/ansible-role-zfs/issues
11 | license: MIT
12 | min_ansible_version: "2.10"
13 |
14 | platforms:
15 | - name: EL
16 | versions:
17 | - "8"
18 | - name: Debian
19 | versions:
20 | - bullseye
21 | - buster
22 | - name: Ubuntu
23 | versions:
24 | - hirsute
25 | - groovy
26 | - focal
27 | - bionic
28 | # - name: ArchLinux
29 | # versions:
30 | # - all
31 |
32 | galaxy_tags:
33 | - zfs
34 | - zpool
35 | - filesystem
36 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: github
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://github.com/aisbergg/ansible-role-zfs
6 | options:
7 | commits:
8 | filters:
9 | Type:
10 | - build
11 | - chore
12 | - ci
13 | - docs
14 | - feat
15 | - fix
16 | - perf
17 | - refactor
18 | commit_groups:
19 | title_maps:
20 | build: Build System
21 | chore: Chores
22 | ci: CI Configuration
23 | docs: Documentation
24 | feat: Features
25 | fix: Bug Fixes
26 | perf: Performance Improvements
27 | refactor: Code Refactoring
28 | header:
29 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?!?\\:\\s(.*)$"
30 | pattern_maps:
31 | - Type
32 | - Scope
33 | - Subject
34 | notes:
35 | keywords:
36 | - BREAKING CHANGE
37 | - SECURITY
38 |
--------------------------------------------------------------------------------
/examples/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | Vagrant.configure(2) do |config|
3 | # see http://www.vagrantbox.es/
4 | config.vm.box = "debian/bookworm64"
5 | # config.vm.network "private_network", ip: "172.10.10.10"
6 | config.vm.synced_folder '.', '/vagrant', disabled: true
7 | config.vm.provider "virtualbox" do |v|
8 | v.gui = false
9 | v.customize ["modifyvm", :id, "--memory", "1024"]
10 | v.customize ["modifyvm", :id, "--usb", "off"]
11 | v.customize ["modifyvm", :id, "--usbehci", "off"]
12 | end
13 |
14 | config.vm.disk :disk, name: "disk1", size: "100MB"
15 | config.vm.disk :disk, name: "disk2", size: "100MB"
16 |
17 | # provisioning with shell
18 | config.vm.provision "shell", inline: "apt-get update && apt-get install -y --no-install-recommends python3"
19 |
20 | # provisioning with ansible
21 | config.vm.provision "ansible" do |ansible|
22 | ansible.verbose = "v"
23 | ansible.playbook = "provision.yml"
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/templates/etc/yum.repos.d/zfs.repo.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | [zfs]
4 | name = ZFS on Linux for EL{{ ansible_distribution_version }} - dkms
5 | baseurl = {{ zfs_redhat_repo_dkms_url }}
6 | enabled = {{ (zfs_redhat_style == 'dkms') | ternary(1,0) }}
7 | metadata_expire = 7d
8 | gpgcheck = 1
9 | gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
10 | {% if zfs_redhat_repo_proxy | default('') %}
11 | proxy = {{ zfs_redhat_repo_proxy }}
12 | {% endif %}
13 |
14 | [zfs-kmod]
15 | name = ZFS on Linux for EL{{ ansible_distribution_version }} - kmod
16 | baseurl = {{ zfs_redhat_repo_kmod_url }}
17 | enabled = {{ (zfs_redhat_style == 'kmod') | ternary(1,0) }}
18 | metadata_expire = 7d
19 | gpgcheck = 1
20 | gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
21 | {% if zfs_redhat_repo_proxy | default('') %}
22 | proxy = {{ zfs_redhat_repo_proxy }}
23 | {% endif %}
24 |
--------------------------------------------------------------------------------
/tasks/manage_pools.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: get list of available zpools
3 | ansible.builtin.shell: "set -o pipefail; zpool list | awk 'FNR >1' | awk '{print $1}'"
4 | args:
5 | executable: /bin/bash
6 | register: available_zpools
7 | changed_when: false
8 | check_mode: false
9 |
10 | - name: get list of importable zpools
11 | ansible.builtin.shell: "set -o pipefail; zpool import | sed -nr 's/^\\s*pool: (.*)/\\1/p'"
12 | args:
13 | executable: /bin/bash
14 | register: importable_zpools
15 | changed_when: false
16 | check_mode: false
17 |
18 | - name: include single pool management tasks
19 | ansible.builtin.include_tasks: manage_pool.yml
20 | vars:
21 | zfs_pool: "{{ zfs_pools_defaults | combine(_zfs_pool) }}"
22 | zfs_dataset_properties: "{{
23 | zfs_filesystems_properties_defaults |
24 | combine(zfs_pool.filesystem_properties |
25 | default({}))
26 | }}"
27 | loop: "{{ zfs_pools }}"
28 | loop_control:
29 | loop_var: _zfs_pool
30 |
--------------------------------------------------------------------------------
/test_plugins/list.py:
--------------------------------------------------------------------------------
1 | from types import GeneratorType
2 |
3 |
4 | class TestModule(object):
5 |
6 | def tests(self):
7 | return {
8 | 'list': self.is_list,
9 | }
10 |
11 | def is_list(self, value):
12 | """Test if a value a list or generator type.
13 |
14 | Jinja2 provides the tests `iterable` and `sequence`, but those also
15 | match strings and dicts as well. To determine, if a value is essentially
16 | a list, you need to check the following:
17 |
18 | value is not string and value is not mapping and value is iterable
19 |
20 | This test is a shortcut, which allows to check for a list or generator
21 | simply with:
22 |
23 | value is list
24 |
25 | Args:
26 | value: A value, that shall be type tested
27 |
28 | Returns:
29 | bool: True, if value is of type list or generator, False otherwise.
30 | """
31 | return isinstance(value, (list, GeneratorType))
32 |
--------------------------------------------------------------------------------
/tasks/configure_zfs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: configure ZFS kernel parameters
3 | ansible.builtin.template:
4 | src: etc/modprobe.d/zfs.conf.j2
5 | dest: /etc/modprobe.d/zfs.conf
6 | owner: root
7 | group: root
8 | mode: "0644"
9 | notify:
10 | - update initramfs (Debian)
11 | - mkinitcpio (Arch Linux)
12 |
13 | - name: configure ZFS
14 | ansible.builtin.template:
15 | src: etc/default/zfs.j2
16 | dest: /etc/default/zfs
17 | owner: root
18 | group: root
19 | mode: "0644"
20 |
21 | - block:
22 | - name: get serial of devices
23 | ansible.builtin.command: "udevadm info -q property {{ item }}"
24 | register: device_properties
25 | changed_when: false
26 | check_mode: false
27 | failed_when: false
28 | loop: "{{ zfs_config_none_ioscheduler }}"
29 |
30 | - name: set IO scheduler of HDDs to 'none'
31 | ansible.builtin.template:
32 | src: etc/udev/rules.d/90-zfs-io-scheduler.rules.j2
33 | dest: /etc/udev/rules.d/90-zfs-io-scheduler.rules
34 | mode: "0644"
35 | notify: reload udev rules
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Andre Lehmann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tasks/manage_volume.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: assert correctness of ZFS volume parameters
3 | ansible.builtin.assert:
4 | that:
5 | - zfs_volume.name is defined and zfs_volume.name is string
6 | - (zfs_volume.properties | default({})) is mapping
7 | - (zfs_volume.state | default('present')) in ['present', 'absent']
8 | quiet: true
9 | fail_msg: ZFS volume parameters are invalid
10 |
11 | - name: gather properties of '{{ zfs_volume.name }}'
12 | community.general.zfs_facts:
13 | name: "{{ zfs_volume.name }}"
14 | failed_when: false
15 | changed_when: false
16 | register: zfs_volume_facts
17 |
18 | - name: prepare volume properties
19 | prepare_zfs_properties:
20 | new_properties: "{{ zfs_volume_properties }}"
21 | current_properties: "{{ ansible_zfs_datasets[0] | default({}) }}"
22 | type: volume
23 | register: prepared_volume_properties
24 |
25 | - name: manage volume '{{ zfs_volume.name }}'
26 | community.general.zfs:
27 | name: "{{ zfs_volume.name }}"
28 | state: "{{ zfs_volume.state | default('present') }}"
29 | extra_zfs_properties: "{{ prepared_volume_properties.properties }}"
30 |
--------------------------------------------------------------------------------
/tasks/manage_filesystem.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: assert correctness of ZFS filesystem parameters
3 | ansible.builtin.assert:
4 | that:
5 | - zfs_filesystem.name is defined and zfs_filesystem.name is string
6 | - (zfs_filesystem.properties | default({})) is mapping
7 | - (zfs_filesystem.state | default('present')) in ['present', 'absent']
8 | quiet: true
9 | fail_msg: ZFS filesystem parameters are invalid
10 |
11 | - name: gather properties of '{{ zfs_filesystem.name | mandatory }}'
12 | community.general.zfs_facts:
13 | name: "{{ zfs_filesystem.name }}"
14 | failed_when: false
15 | changed_when: false
16 | register: zfs_filesystem_facts
17 |
18 | - name: prepare filesystem properties
19 | prepare_zfs_properties:
20 | new_properties: "{{ zfs_filesystem_properties }}"
21 | current_properties: "{{ ansible_zfs_datasets[0] | default({}) }}"
22 | type: filesystem
23 | register: prepared_filesystem_properties
24 |
25 | - name: manage filesystem '{{ zfs_filesystem.name }}'
26 | community.general.zfs:
27 | name: "{{ zfs_filesystem.name }}"
28 | state: "{{ zfs_filesystem.state | default('present') }}"
29 | extra_zfs_properties: "{{ prepared_filesystem_properties.properties }}"
30 |
--------------------------------------------------------------------------------
/molecule/default/prepare.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Prepare
3 | hosts: all
4 | become: true
5 | tasks:
6 | - when: ansible_os_family == 'Debian'
7 | block:
8 | - name: Use backrefs with alternative group syntax to avoid conflicts with variable values
9 | ansible.builtin.lineinfile:
10 | path: /etc/apt/sources.list
11 | regexp: "{{ item }}"
12 | line: \g<1> main contrib
13 | backrefs: true
14 | loop:
15 | - "^(deb .*backports).*"
16 | - "^(deb-src .*backports).*"
17 |
18 | - name: update apt cache
19 | ansible.builtin.apt:
20 | update_cache: true
21 | cache_valid_time: 3600
22 |
23 | - when: ansible_os_family == 'RedHat'
24 | block:
25 | - name: install epel
26 | ansible.builtin.dnf:
27 | name: epel-release
28 | state: present
29 |
30 | - name: create files to act as loop file
31 | community.general.filesize:
32 | path: "/zfs{{ item }}.img"
33 | size: 512M
34 | loop:
35 | - 0
36 | - 1
37 |
38 | - name: create loop devices
39 | ansible.builtin.command: "losetup -P /dev/loop{{ item }} /zfs{{ item }}.img"
40 | loop:
41 | - 0
42 | - 1
43 |
--------------------------------------------------------------------------------
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 | {{ range .Versions }}
5 | {{ $strippedTagName := regexReplaceAll "^v" .Tag.Name "" -}}
6 | - [{{ $strippedTagName }} ({{ datetime "2006-01-02" .Tag.Date }})](#{{ regexReplaceAll "\\." $strippedTagName "" }}-{{ datetime "2006-01-02" .Tag.Date }})
7 | {{- end }}
8 |
9 | ---
10 | {{ range .Versions }}
11 | {{ $strippedTagName := regexReplaceAll "^v" .Tag.Name "" -}}
12 |
13 | ## [{{ $strippedTagName }}]({{ if .Tag.Previous }}{{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}{{ end }}) ({{ datetime "2006-01-02" .Tag.Date }})
14 | {{ if .Tag.Previous }}
15 | {{ range .CommitGroups -}}
16 | ### {{ .Title }}
17 |
18 | {{ range .Commits -}}
19 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
20 | {{ end }}
21 | {{ end -}}
22 |
23 | {{- if .RevertCommits -}}
24 | ### Reverts
25 |
26 | {{ range .RevertCommits -}}
27 | - {{ .Revert.Header }}
28 | {{ end }}
29 | {{ end -}}
30 |
31 | {{- if .NoteGroups -}}
32 | {{ range .NoteGroups -}}
33 | ### {{ if eq .Title "BREAKING CHANGE" }}⚠ BREAKING CHANGES{{ else if eq .Title "SECURITY" }}‼️ SECURITY{{ else}}{{ .Title }}{{ end }}
34 |
35 | {{ range .Notes }}
36 | {{ .Body }}
37 | {{ end }}
38 | {{ end -}}
39 | {{ end -}}
40 | {{ else }}
41 | Initial Release
42 | {{ end -}}
43 | {{ end -}}
44 |
--------------------------------------------------------------------------------
/files/centos/RPM-GPG-KEY-zfsonlinux:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 | Version: GnuPG v1.4.13 (GNU/Linux)
3 |
4 | mQENBFFLdrcBCADLsZ7FG2e2Cr/ZV6J52+CyzXXmmmIt2ibYJdflxhSaaxhuag6k
5 | 1OgWEM1R/igjWD3im66O99m30+XeDqDwwBC2flplTlAM5cVb2Q37M+q+LyGLaJgL
6 | JKIkHWfK3/arJO+QY5K2hxzvKUAO1ZJa5OYQMJmKIxLzKz3SX6YnRTtE7ohDq/bU
7 | F32ysIrW549XZUpFX4DYCbR9IEaF5kCvus4FwidBTHDC5aWKvb7qStaL/Yo9koV0
8 | CADl4/nCNAKTcmRoo8Pz+zFwFFNLKRdwu58IefLgkqon7RQBDhwMqKPs1Kw14RwJ
9 | HUv3HYw1JleFmkXv2hn1BMa0ct7jUrvenFBNABEBAAG0IVpGUyBvbiBMaW51eCA8
10 | emZzQHpmc29ubGludXgub3JnPokBOAQTAQIAIgUCUUt2twIbAwYLCQgHAwIGFQgC
11 | CQoLBBYCAwECHgECF4AACgkQqdWhwPFKtiAHWgf/SVXT92gs02HlJsz3h+vmHKHH
12 | 8esxHq8DzG9PaBTyeWLB6mMuN5IQ6Kbtpy44xYCyPyBo+MEoFyPwJXw4qU7th/NX
13 | fAaohXTT8KltyKYsibotdeUHGE4G/7ilbQl9kknlmbig9M16RCnRCDxBRiLrvdaQ
14 | X84YhQUlV2CUShRevuogNbcfORViF8jGb0vkRRTJFhsfZpq7XmW53Q3RXrzoe5Pf
15 | t9NzV2Tlx9ohyGGpPVGO4MgJ028vtoUSIVGA2a9Vg1dwKJkhZ/VvgeleZGds+jIP
16 | 0SpXCNJHgxTaE5AJ71GC8OiDst8b/syYmRFX4P1ioah9m7X3ImHiX4tZJc+Ee7kB
17 | DQRRS3a3AQgAtGrh/OjWeqyUAbw8aO6ew28u+wG0GOEaNdMPEm9120uM3XoEHxg7
18 | FpixPJHj6u9VfhZvHBQOEYiZ0sIY7qj/0wyifsTFYjSZrSCJJHJpbM4SnflTkD43
19 | uJlvcUmqMv0vfCnkaMIO71I1xqPlgOYxOQlenttcI+5xEVzv78cSoQOdddCdGFMs
20 | mdrfxh5NWJR6ehIEI1JQTl6iuZt8wJ/Tgqk2btyDYpDKvDdKLjkBpCbTwsVtQa0x
21 | 2/EvuSMbnu8rqqzqqVsOoBMzDi1ksxm1kC4fXmI9av2X+dGpGJNnn5AqIQAKsETE
22 | 8L4Ajzo8Tk2aaq2ase0i9sNdnsRtYGOjiwARAQABiQEfBBgBAgAJBQJRS3a3AhsM
23 | AAoJEKnVocDxSrYgJXYH/3gAPOr7LwA4p3BhV6NIwIaMGmGY+1dbMp/OxB+mFuOS
24 | NTTCKsBGUchGVFYjSlBtden07S8HNNTWB+bWLVfRTTgRkqomxp0DMMOZ8ry317l+
25 | cDKVRXMPEZvX9567q1PAOGDiGxiE8296ZUp9/hSFkOqv1sdp+HSM6KVMb4MP4Sx0
26 | +sAwcEumIQAKgXMzDLdpoPDrFnoAAfUmQfpddd7NKch0NJAhdlPtQryFpKdnmvpQ
27 | oLINrelqJxuVMo0hd7q0Xc/vJT0s6pe0f0fXdqXy1ijD3qAewXLZHO3XGVSF8fLY
28 | Q5XFCu4KH2vNBmn0lZxVX5BWm3R2M5XfuT/CJYHO+mk=
29 | =A19+
30 | -----END PGP PUBLIC KEY BLOCK-----
31 |
--------------------------------------------------------------------------------
/files/centos/RPM-GPG-KEY-zrepl:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | mQENBF9Ob+gBCADU2Lzzt6mvBL6dQTLsqjjYbevi5W1zSvdpLSb27W81wtskg+qa
4 | xJmTO+XqOpMjwe/qtTh18bNjzVjA47c5aPZrraq2/9ekZFaSFg7sY1u1Ocfb07sQ
5 | YoHShFFKA0C3TDEgYllxEulMdvXdQIYzAyRuM6fv+Qw1+kgODs8JRSj0np5ZimYz
6 | 9kp8zg6rmzvIjr/fk2fXQx9WXOMTpNsbR3lftUbjfUAz/2m3SlJUsmFea1Yi8WM0
7 | rGwzSnNfjHEbN1jYsFpSN28UtkqztgiWVU0ubqre77b0MKms70xIDZRCH3dQXszd
8 | X9OjPtVamqyPbx9zkWOsooNQ1cmsYUG9s7NBABEBAAG0G3pyZXBsLXJwbS1wa2dz
9 | QGNzY2h3YXJ6LmNvbYkBVAQTAQgAPhYhBPb26OpvLxRiKHi13lDjRBeCbizmBQJf
10 | Tm/oAhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEFDjRBeCbizm
11 | ZyUIAKZTFH4vFTCiyBAv7fsPn+fmqLnMPcp8uZ78C1rixzvIkzxIsGr4q0r2ten0
12 | OTJS0sJ/kDr/Q7AYw/XRMjt4NDVTDfLiZN3ST+lRNbzTWnj/gDXxws2JFniVBHpm
13 | 5bQd8Amtxa0ykTnXIugiJGfSXl2zw4q6JOiBIPp9CJx7WEfi/mwrbqtOzYeFr8ab
14 | DkOuZf6L+5OStdh4s7mpF8zfNHEJSmLBg0MFGUeswxPytzDX25CFp8sUNXmdutmY
15 | wU0aNXmHukjmbxsAqpsUN09nEgjvP8DHo9Pb1/3qiVxjC6oVN87qAL3NApND4YV9
16 | f2TrhAXe7Y//KBNn3BXQab41IK25AQ0EX05v6AEIANgoOo6oQJMsR1zwi1C412Ri
17 | yj14x2ygtAJkH+vHmGCaZXFY1emyRC0tclSX8K+6ocKWxdePDOoXFYXd2jw9wpM8
18 | /hvmn9Zxore93mNryJpdb1HObdGjtDt7pqygGvNFpfhu+k5c0ek+PkWClLT6cu6Y
19 | x5WObsk8ZKGfkwcehWTPghbIEbyki6+YC0X9hMxorTVqgcoo76OjZiHsu77ysKb8
20 | 64GLlJwGG7G1OEqvMoynumuAilypglBmch1wojddZ+STkUs++zXHy6Qrev8GoS+q
21 | TEgJ6ft8GeObQABmCsWsS5IyXKz1dZrAnh5aTSZxj7PlUeKtRSaQrw2lWruj5rUA
22 | EQEAAYkBNgQYAQgAIBYhBPb26OpvLxRiKHi13lDjRBeCbizmBQJfTm/oAhsMAAoJ
23 | EFDjRBeCbizmIwMH/RIB9FMorEDvQW0R20OrZVKzWU1P5fjXVKcXlc8uEgD0Ymso
24 | sRzSE82sIQwRfmIHQP93uT6aBbJM5qdbCCPVa08SpwWfqENjGSv1E4hOINZZh0rl
25 | tYrjikfkUzyZSTF3ddYQ5OFUuxIbaCcOrFQD5RXsiS8iL4ARHmUXjgZKp5gkOsIw
26 | UbLPLxMfm75tBamKd3kitpEZUJjEbKD1q4Z9tXvnHSgx40a92vuKeQsG1dUVRGX1
27 | E6o+AM8nTMtCBcXS5L83TJkWbw0+vTT0Yu0CW7TXV1jMhdNHwZod4Io3CbQ09Cjh
28 | 4j9WnHtDU2DHv8EpdWgeWcsDRLV1T6kjy+enhvM=
29 | =gRBv
30 | -----END PGP PUBLIC KEY BLOCK-----
31 |
--------------------------------------------------------------------------------
/files/debian/zrepl-apt-key.asc:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | mQENBF2owsoBCAC0EoCU6ljdaN0wtqfqS5Cl3szlwizLliVn2B6vadhEW0NTEKU1
4 | T8anCys+4JB6g7IE7QUVdsKP2B/RZli4Wz7S4N9+fPoBrs6mixDEB+NSe8w21ZX5
5 | 73neMztFZU4aPmCpUkoVYBPxQtqO52exFtJZph0/R2ITXpOykNH9CbmMXLolQlal
6 | V7RRaHrIjhabmSqELqTWNJIcxOWS2T/hKCLSldsoBkq82ruBXFkGOGcuEbtKDd33
7 | 8rriS/wh0smzcUnDo4qRao37xYjzZahDCIao5QwyDfRlNRF27b7Svlfn2wg0CMx1
8 | /IelKQCkJ3ErAAmoyiXMvVvifiva4U7JZ3V3ABEBAAG0MkNocmlzdGlhbiBTY2h3
9 | YXJ6IDx6cmVwbC1kZWJpYW4tcGtnc0Bjc2Nod2Fyei5jb20+iQFOBBMBCAA4AhsD
10 | BQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE4QFBj9PW+8udZaYtcIaZ/F8uvxYF
11 | AmFr7DsACgkQcIaZ/F8uvxa7kgf+Pdlky2mFB8vn3fAr4y7lvijVU9IqXKsKDoJG
12 | K1irMHkPfm63VPXP8KthPPItImOMAkW762euQP4KVWlbTJSysSot3FvWy5Zyy7UU
13 | TLSy/E+pW9vljbrnze5RCCHFzw7zRfCq0Mt40x3eG+rmb80lt9DbQ/Uu5jDL3L9s
14 | wnBYMYG6EmRupcHBaYsBYqqSlsaQYA7yvsA5bKxKwd5nU/fMR2pVOGKyGZxoZ59G
15 | S4xMzcCMGE7eIwq0XWv3o0cM/vcvLWnWrhVeDA0t0H3q/b2AbhCQjngPUJr/EG6O
16 | 2CW7mE8EPzcqiPR2AU/PEWqs1/Xef/vsjgIJg7qNpUaQPJGhm7kBDQRdqMLKAQgA
17 | riGPsfyuXVdgx7rkw4t9e+FVsBEb37oMUGyVyd14ZyiBLOFIUU2B3r6RKdIUmlkq
18 | Z64ccbpgu7GatFypnhxp6i4Gbp93boxIFk4okgMDENVflblRnk8a5YcgqIV5MQWK
19 | 3MezUyKNLoraPPREohzblLTmHX36xzzAxxR8oA4+4oQsbWWzcPTg9KH2nhJiT/NJ
20 | gQXO0DRwu0vD0qdZaJqOTPiWfJBpHeGxKRn863g1cTS0BxFOgrta8Is7MkJJNorP
21 | Dv8iPL3DI2VV/j1F6/AV2naHXDS+ZW2JZsXMp+zgk1iXO74mtv/y9CD/qLxEr9TY
22 | SyjtBUH7eaUXIgOZymz7HwARAQABiQE8BBgBCAAmAhsMFiEE4QFBj9PW+8udZaYt
23 | cIaZ/F8uvxYFAmFr6JgFCQWkWU4ACgkQcIaZ/F8uvxYhCQgAmIxLKH6x1g6eS1Ez
24 | 6CO9MYvuI74b9c1Gd8fCd7XQXJTO6I4oP3lP/oINg0Zodj3bgin58VNbLQG5zxD5
25 | +G/YYFDX6pAh1dYhHEkb9im95TbOPdGhPWHiFHgr1B6TRGUQQqhPAhcv7eeYl2UM
26 | hyTQ1gcc7y8wT3ZtIctqoxalisM7pKP+hmpguRFHskV7Y0VYiOGNH/iteie1Glhy
27 | FEHaVT6nDhHf/qhRgkA9czE0+x6vGyoxISo07/i0dtRghoebFRx3XkG297XKzIqr
28 | RVYlgvE4RA0pq4p2gb/7q1pcQk3SniQcakOzYX7s50oCzkouggA4ZcMNm4LszXVU
29 | 6sz0Zw==
30 | =Gv0n
31 | -----END PGP PUBLIC KEY BLOCK-----
32 |
--------------------------------------------------------------------------------
/templates/etc/cron.d/zfsnap.j2:
--------------------------------------------------------------------------------
1 | #jinja2: trim_blocks: "true", lstrip_blocks: "true"
2 | {{ ansible_managed | comment }}
3 | {% macro opt(option, var_name) %}{{ option ~ ' ' if snapshot[var_name] | default(false) | bool else '' }}{% endmacro %}
4 | {% macro snapshot_config(targets) %}
5 | {% for target in targets %}
6 | {% if target.snapshots | default([]) | length > 0 %}
7 |
8 | # snapshots for '{{ target.name }}'
9 | {% for snapshot in target.snapshots %}
10 | {{ snapshot.cron_schedule | default('0 0 * * *') }} root sh -c '/usr/local/sbin/zfsnap snapshot {{ opt('-r', 'recursive') }}{{ opt('-s', 'skip_resilvering_pools') }}{{ opt('-S', 'skip_scrubbing_pools') }}{{ opt('-z', 'round_snapshot_creation_time') }}-a {{ snapshot.ttl | default('1w') }} -p "{{ snapshot.prefix | default('') }}" {{ target.name }} && /usr/local/sbin/zfsnap destroy {{ opt('-r', 'recursive') }}{{ opt('-s', 'skip_resilvering_pools') }}{{ opt('-S', 'skip_scrubbing_pools') }}-p "{{ snapshot.prefix | default('') }}" {{ target.name }}'
11 | {% endfor %}
12 | {% endif %}
13 | {% endfor %}
14 | {% endmacro %}
15 |
16 | # ZFSNAP schedule
17 | # for more information see https://www.zfsnap.org/zfsnap_manpage.html
18 |
19 | {% if zfs_pools | default([]) | length > 0 %}
20 | # ------------------------------------------------------------------------------
21 | # ZPool Snapshots
22 | # ------------------------------------------------------------------------------
23 | {{ snapshot_config(zfs_pools) }}
24 |
25 |
26 | {% endif %}
27 | {% if zfs_filesystems | default([]) | length > 0 %}
28 | # ------------------------------------------------------------------------------
29 | # Filesystem Snapshots
30 | # ------------------------------------------------------------------------------
31 | {{ snapshot_config(zfs_filesystems) }}
32 |
33 |
34 | {% endif %}
35 | {% if zfs_volumes | default([]) | length > 0 %}
36 | # ------------------------------------------------------------------------------
37 | # Volume Snapshots
38 | # ------------------------------------------------------------------------------
39 | {{ snapshot_config(zfs_volumes) }}
40 |
41 |
42 | {% endif %}
43 |
--------------------------------------------------------------------------------
/filter_plugins/split.py:
--------------------------------------------------------------------------------
1 | # MIT License
2 | #
3 | # Copyright (c) 2021 Andre Lehmann
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 |
24 | class FilterModule(object):
25 |
26 | def filters(self):
27 | return {"split": self.split}
28 |
29 | def split(self, value, sep=None, maxsplit=-1):
30 | """Split a string by a specified seperator string.
31 |
32 | Splitting a string with Jinja can already accomplished by executing the
33 | "split" method on strings, but when you want to use split in combination
34 | with "map" for example, you need a filter like this one.
35 |
36 | Args:
37 | value (str): The string value to be splitted.
38 | sep (str, optional): The seperator string.
39 | maxsplit (int, optional): Number of splits to do. Defaults to "-1".
40 |
41 | Returns:
42 | list: A splitted representation of the string.
43 |
44 | Examples:
45 | {{ 'Hello World' | split(' ') }} -> ['Hello', 'World']
46 | """
47 | return str(value).split(sep, maxsplit)
48 |
--------------------------------------------------------------------------------
/tasks/install_debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # ------------------------------------------------------------------------------
3 | # ZFS Installation
4 | # ------------------------------------------------------------------------------
5 |
6 | - name: update APT cache (Debian)
7 | ansible.builtin.apt:
8 | update_cache: true
9 |
10 | - name: gather package facts (Debian)
11 | ansible.builtin.package_facts:
12 |
13 | - name: install Linux kernel headers (Debian)
14 | ansible.builtin.apt:
15 | # there might be installed kernels for which no headers are available, therefore we simply skip those
16 | name: "{{ item }}"
17 | state: present
18 | loop: "{{
19 | ansible_facts.packages.keys()
20 | | select('match', '^linux-image-.+')
21 | | map('replace', 'image', 'headers')
22 | | list
23 | }}"
24 | ignore_errors: true
25 |
26 | - name: install ZFS (Debian)
27 | ansible.builtin.apt:
28 | name: "{{ item }}"
29 | state: present
30 | default_release: "{{ zfs_debian_repo if zfs_debian_repo | length > 0 else omit }}"
31 | # install the packages one after another
32 | loop:
33 | - zfs-dkms
34 | - zfsutils-linux
35 | - zfs-zed
36 | - zfs-initramfs
37 |
38 | - name: ensure ZFS kernel module is loaded (Debian)
39 | community.general.modprobe:
40 | name: zfs
41 |
42 | - name: ensure ZFS kernel module is loaded in boot (Debian)
43 | ansible.builtin.copy:
44 | content: zfs
45 | dest: /etc/modules-load.d/zfs.conf
46 | owner: root
47 | group: root
48 | mode: "0644"
49 |
50 |
51 | # ------------------------------------------------------------------------------
52 | # Zrepl Installation
53 | # ------------------------------------------------------------------------------
54 |
55 | - when: zfs_zrepl_enabled
56 | block:
57 | - when: zfs_manage_repository | bool
58 | block:
59 | - name: import zrepl repo GPG key (Debian)
60 | ansible.builtin.apt_key:
61 | data: "{{ lookup('file', 'debian/zrepl-apt-key.asc') }}"
62 | id: E101418FD3D6FBCB9D65A62D708699FC5F2EBF16
63 | state: present
64 |
65 | - name: add zrepl repository (Debian)
66 | ansible.builtin.apt_repository:
67 | filename: zrepl
68 | repo: >-
69 | deb [arch=amd64] {{ zfs_zrepl_debian_repo_url }}/{{ ansible_distribution | lower }}
70 | {{ ansible_distribution_release | lower }}
71 | main
72 | state: present
73 |
74 | - name: install zrepl (Debian)
75 | ansible.builtin.apt:
76 | name: zrepl
77 | state: present
78 |
--------------------------------------------------------------------------------
/examples/provision.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Setup host
3 | hosts: all
4 | become: true
5 | become_method: sudo
6 |
7 | tasks:
8 | # the aisbergg.zfs role requires the backports repository but leaves it to the user to add it
9 | - name: add backports-contrib repository
10 | ansible.builtin.copy:
11 | content: |
12 | deb https://deb.debian.org/debian {{ ansible_distribution_release }} main contrib
13 | deb https://security.debian.org/debian-security {{ ansible_distribution_release }}-security main
14 | deb https://deb.debian.org/debian {{ ansible_distribution_release }}-updates main
15 | deb https://deb.debian.org/debian {{ ansible_distribution_release }}-backports main contrib
16 | dest: /etc/apt/sources.list
17 | owner: root
18 | group: root
19 | mode: "0644"
20 |
21 | - name: upgrade packages and potentially install a newer kernel
22 | ansible.builtin.apt:
23 | update_cache: true
24 | upgrade: full
25 | cache_valid_time: 3600
26 | register: apt_upgrade
27 |
28 | - when: apt_upgrade.changed
29 | block: #
30 | - name: reboot if a new kernel was installed
31 | ansible.builtin.reboot:
32 | reboot_timeout: 600
33 | msg: "Rebooting because we might have a new kernel"
34 |
35 | - name: gather facts again after reboot
36 | ansible.builtin.setup:
37 |
38 | - name: gather package facts
39 | ansible.builtin.package_facts:
40 |
41 | - name: remove unused kernels
42 | ansible.builtin.apt:
43 | name: "{{
44 | ansible_facts.packages.keys()
45 | | select('match', '^linux-(image|headers)-\\d+\\.\\d+\\.\\d+.*')
46 | | reject('match', '^linux-(image|headers)-'~kernel_version~'.*')
47 | | list
48 | }}"
49 | state: absent
50 | vars:
51 | kernel_version: "{{ ansible_kernel | regex_replace('^(\\d+\\.\\d+\\.\\d+-d+).*', '\\1') }}"
52 | ignore_errors: true
53 |
54 | - name: manage ZFS filesystems
55 | ansible.builtin.include_role:
56 | name: aisbergg.zfs
57 | vars:
58 | zfs_pools:
59 | - name: pool
60 | vdev: >-
61 | mirror
62 | sdb
63 | sdc
64 | scrub: true
65 | properties:
66 | ashift: 12
67 | filesystem_properties:
68 | mountpoint: /mnt/raid1
69 | compression: lz4
70 | # properties of zfs_filesystems_properties_defaults also apply here
71 |
72 | zfs_filesystems:
73 | - name: pool/vol1
74 | - name: pool/vol2
75 |
76 | # schedule for ZFS scrubs
77 | zfs_scrub_schedule: monthly
78 | # schedule for TRIM
79 | zfs_trim_schedule: weekly
80 |
--------------------------------------------------------------------------------
/tasks/install_redhat.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # ------------------------------------------------------------------------------
3 | # ZFS Installation
4 | # ------------------------------------------------------------------------------
5 |
6 | - when: zfs_manage_repository | bool
7 | block:
8 | - name: add ZFS repository (RedHat)
9 | ansible.builtin.template:
10 | src: etc/yum.repos.d/zfs.repo.j2
11 | dest: /etc/yum.repos.d/zfs.repo
12 | owner: root
13 | group: root
14 | mode: "0644"
15 |
16 | - name: copy ZFS repo GPG key (RedHat)
17 | ansible.builtin.copy:
18 | src: centos/RPM-GPG-KEY-zfsonlinux
19 | dest: /etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
20 | owner: root
21 | group: root
22 | mode: "0644"
23 |
24 | - name: import ZFS repo GPG key (RedHat)
25 | ansible.builtin.rpm_key:
26 | key: /etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
27 | state: present
28 | fingerprint: C93A FFFD 9F3F 7B03 C310 CEB6 A9D5 A1C0 F14A B620
29 |
30 | - name: install Linux kernel headers (RedHat)
31 | ansible.builtin.dnf:
32 | name: "kernel-devel-{{ ansible_kernel }}"
33 | state: present
34 | when: zfs_redhat_style == 'dkms'
35 |
36 | - name: install ZFS (RedHat)
37 | ansible.builtin.dnf:
38 | name: zfs
39 | state: present
40 |
41 | - name: ensure ZFS kernel module is loaded (RedHat)
42 | community.general.modprobe:
43 | name: zfs
44 |
45 | - name: ensure ZFS kernel module is loaded in boot (RedHat)
46 | ansible.builtin.copy:
47 | content: zfs
48 | dest: /etc/modules-load.d/zfs.conf
49 | owner: root
50 | group: root
51 | mode: "0644"
52 |
53 |
54 | # ------------------------------------------------------------------------------
55 | # Zrepl Installation
56 | # ------------------------------------------------------------------------------
57 |
58 | - when: zfs_zrepl_enabled
59 | block:
60 | - when: zfs_manage_repository | bool
61 | block:
62 | - name: add zrepl repository (RedHat)
63 | ansible.builtin.template:
64 | src: etc/yum.repos.d/zrepl.repo.j2
65 | dest: /etc/yum.repos.d/zrepl.repo
66 | owner: root
67 | group: root
68 | mode: "0644"
69 |
70 | - name: copy zrepl repo GPG key (RedHat)
71 | ansible.builtin.copy:
72 | src: centos/RPM-GPG-KEY-zrepl
73 | dest: /etc/pki/rpm-gpg/RPM-GPG-KEY-zrepl
74 | owner: root
75 | group: root
76 | mode: "0644"
77 |
78 | - name: import zrepl repo GPG key (RedHat)
79 | ansible.builtin.rpm_key:
80 | key: /etc/pki/rpm-gpg/RPM-GPG-KEY-zrepl
81 | state: present
82 | fingerprint: F6F6 E8EA 6F2F 1462 2878 B5DE 50E3 4417 826E 2CE6
83 |
84 | - name: install zrepl (RedHat)
85 | ansible.builtin.dnf:
86 | name: zrepl
87 | state: present
88 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | - [3.3.1 (2023-01-03)](#331-2023-01-03)
6 | - [3.3.0 (2022-12-18)](#330-2022-12-18)
7 | - [3.2.0 (2022-03-31)](#320-2022-03-31)
8 | - [3.1.0 (2022-01-28)](#310-2022-01-28)
9 | - [3.0.0 (2021-10-12)](#300-2021-10-12)
10 | - [2.0.0 (2021-04-02)](#200-2021-04-02)
11 | - [1.0.0 (2020-08-25)](#100-2020-08-25)
12 |
13 | ---
14 |
15 |
16 | ## [3.3.1](https://github.com/aisbergg/ansible-role-zfs/compare/v3.3.0...v3.3.1) (2023-01-03)
17 |
18 | ### Bug Fixes
19 |
20 | - fix syntax of requirements file
21 |
22 | ### Chores
23 |
24 | - explicitly set Ansible as verifier
25 | - use fully-qualified collection name
26 |
27 |
28 |
29 | ## [3.3.0](https://github.com/aisbergg/ansible-role-zfs/compare/v3.2.0...v3.3.0) (2022-12-18)
30 |
31 | ### Bug Fixes
32 |
33 | - linting
34 | - replace deprecated decorator
35 | - rename vars/centos.yml to redhat.yml
36 |
37 | ### Documentation
38 |
39 | - document yum/dnf variable
40 | - **README.md:** update documentation
41 |
42 | ### Features
43 |
44 | - add option 'zfs_trim_schedule' to control the TRIM schedule for drives
45 | - add option 'zfs_manage_repository' to control the package repository management
46 | - add proxy support for yum/dnf repositories.
47 |
48 |
49 |
50 | ## [3.2.0](https://github.com/aisbergg/ansible-role-zfs/compare/v3.1.0...v3.2.0) (2022-03-31)
51 |
52 | ### Bug Fixes
53 |
54 | - make udev tasks also work in check mode
55 | - run ZED script only if it is enabled
56 | - run mount generator tasks before service mgt tasks
57 |
58 | ### CI Configuration
59 |
60 | - add branch explicitly to make Ansible import action happy
61 | - bump Ansible Galaxy action version
62 |
63 | ### Chores
64 |
65 | - don't use bump2version to include the CHANGELOG in the bump commit, it doesn't do a good job
66 |
67 | ### Documentation
68 |
69 | - update links to manpages
70 |
71 | ### Features
72 |
73 | - load kernel module on boot, even if no pools are created first
74 |
75 |
76 |
77 | ## [3.1.0](https://github.com/aisbergg/ansible-role-zfs/compare/v3.0.0...v3.1.0) (2022-01-28)
78 |
79 | ### Bug Fixes
80 |
81 | - correct 'boolean' test name
82 | - update zrepl PGP key
83 |
84 | ### CI Configuration
85 |
86 | - fix automatic release and publish process
87 |
88 | ### Chores
89 |
90 | - include changelog in bump commits
91 |
92 |
93 |
94 | ## [3.0.0](https://github.com/aisbergg/ansible-role-zfs/compare/v2.0.0...v3.0.0) (2021-10-12)
95 |
96 | ### CI Configuration
97 |
98 | - add Github action for automatic releases
99 |
100 | ### Chores
101 |
102 | - update changelog
103 | - update development configs
104 | - **.ansible-lint:** update linter config
105 | - **.pre-commit-config.yaml:** bump pre-commit hook versions
106 | - **CHANGELOG.tpl.md:** update changelog template
107 | - **requirements.yml:** add role requirements
108 |
109 | ### Code Refactoring
110 |
111 | - rename variables, add assertions and other modifications
112 |
113 | ### Documentation
114 |
115 | - **README.md:** add proper documentation
116 |
117 |
118 |
119 | ## [2.0.0](https://github.com/aisbergg/ansible-role-zfs/compare/v1.0.0...v2.0.0) (2021-04-02)
120 |
121 | ### Code Refactoring
122 |
123 | - major overhaul
124 |
125 |
126 |
127 | ## [1.0.0]() (2020-08-25)
128 |
129 | Initial Release
130 |
--------------------------------------------------------------------------------
/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: include OS-specific variables
3 | tags: [zfs, always]
4 | ansible.builtin.include_vars: "{{ item }}"
5 | with_first_found:
6 | - "{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}.yml"
7 | - "{{ ansible_distribution | lower }}.yml"
8 | - "{{ ansible_os_family | lower }}_{{ ansible_distribution_major_version }}.yml"
9 | - "{{ ansible_os_family | lower }}.yml"
10 |
11 | - name: assert correctness of configuration
12 | tags: [zfs, always]
13 | ansible.builtin.assert:
14 | that:
15 | - zfs_redhat_style in ['kmod', 'dkms']
16 | - zfs_redhat_repo_dkms_url is string
17 | - zfs_redhat_repo_kmod_url is string
18 | - zfs_debian_repo is string
19 | - (zfs_service_import_cache_enabled | bool) is boolean
20 | - (zfs_service_import_scan_enabled | bool) is boolean
21 | - (zfs_service_mount_enabled | bool) is boolean
22 | - (zfs_service_share_enabled | bool) is boolean
23 | - (zfs_service_volume_wait_enabled | bool) is boolean
24 | - (zfs_service_zed_enabled | bool) is boolean
25 | - (zfs_use_zfs_mount_generator | bool) is boolean
26 | - zfs_scrub_schedule is string
27 | - zfs_config_none_ioscheduler is list
28 | - zfs_pools is list
29 | - zfs_pools_defaults is mapping
30 | - zfs_volumes is list
31 | - zfs_volumes_properties_defaults is mapping
32 | - zfs_filesystems is list
33 | - zfs_filesystems_properties_defaults is mapping
34 | - zfs_zrepl_config is mapping
35 | - (zfs_zrepl_enabled | bool) is boolean
36 | - zfs_zrepl_redhat_repo_url is string
37 | - zfs_zrepl_debian_repo_url is string
38 | quiet: true
39 | fail_msg: configuration for ZFS Role is invalid, check your parameters
40 |
41 | - name: include installation tasks
42 | tags: [zfs, zfs_install, install]
43 | ansible.builtin.include_tasks: "{{ task_file }}"
44 | loop_control:
45 | loop_var: task_file
46 | with_first_found:
47 | - "install_{{ ansible_distribution | lower }}_{{ ansible_distribution_major_version }}.yml"
48 | - "install_{{ ansible_distribution | lower }}.yml"
49 | - "install_{{ ansible_os_family | lower }}_{{ ansible_distribution_major_version }}.yml"
50 | - "install_{{ ansible_os_family | lower }}.yml"
51 |
52 | - name: include configuration tasks
53 | tags: [zfs, zfs_config, config]
54 | ansible.builtin.import_tasks: configure_zfs.yml
55 |
56 | - name: include pool management tasks
57 | tags: [zfs, zfs_pool]
58 | ansible.builtin.include_tasks: manage_pools.yml
59 |
60 | - name: include volume management tasks
61 | tags: [zfs, zfs_volumes, zfs_dataset]
62 | ansible.builtin.include_tasks: manage_volume.yml
63 | vars:
64 | zfs_volume_properties: "{{ zfs_volumes_properties_defaults | combine(zfs_volume.properties | default({})) }}"
65 | loop: "{{ zfs_volumes }}"
66 | loop_control:
67 | loop_var: zfs_volume
68 |
69 | - name: include filesystem management tasks
70 | tags: [zfs, zfs_filesystem, zfs_dataset]
71 | ansible.builtin.include_tasks: manage_filesystem.yml
72 | vars:
73 | zfs_filesystem_properties: "{{
74 | zfs_filesystems_properties_defaults |
75 | combine(zfs_filesystem.properties |
76 | default({}))
77 | }}"
78 | loop: "{{ zfs_filesystems }}"
79 | loop_control:
80 | loop_var: zfs_filesystem
81 |
82 | - name: include snapshot configuration tasks
83 | tags: [zfs, zfs_snapshots, zfs_config, config]
84 | ansible.builtin.import_tasks: configure_snapshots.yml
85 |
86 | - name: include service management tasks
87 | tags: [zfs, zfs_service, service]
88 | ansible.builtin.import_tasks: manage_services.yml
89 |
--------------------------------------------------------------------------------
/filter_plugins/selectattr2.py:
--------------------------------------------------------------------------------
1 | # MIT License
2 | #
3 | # Copyright (c) 2023 Andre Lehmann
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from collections.abc import Iterable
24 |
25 | from ansible import errors
26 | from jinja2.runtime import Undefined
27 |
28 | try:
29 | from jinja2.filters import pass_environment
30 | except ImportError:
31 | # compatibility for jinja2 < 3.0
32 | from jinja2.filters import environmentfilter
33 | pass_environment = environmentfilter
34 |
35 |
36 | class FilterModule(object):
37 |
38 | def filters(self):
39 | return {"selectattr2": self.selectattr2}
40 |
41 | @pass_environment
42 | def selectattr2(self, environment, seq, attr, func_name, *args, **kwargs):
43 | """Filter a sequence of objects by applying a test to the specified
44 | attribute of each object, and only selecting the objects with the test
45 | succeeding.
46 |
47 | The filter works much like the 'selectattr', but it does not fail in
48 | case an attribute doesn't exists. In case an attribute is missing, a
49 | default value is used. The default value can be specified as a
50 | keyed-arg.
51 |
52 | Args:
53 | seq (Iterable): The sequence to be filtered.
54 | attr (str): The attribute used for filtering.
55 | func_name (str): The name of filter function.
56 |
57 | Raises:
58 | errors.AnsibleFilterError: Raised if 'seq' is not an iterable and
59 | doesn't contain a mapping.
60 |
61 | Yields:
62 | The next item of the sequence, that passes the test.
63 |
64 | Examples:
65 | {{ users | selectattr2('state', '==', 'present', default='present') | list }}
66 | """
67 | if not isinstance(seq, Iterable):
68 | raise errors.AnsibleFilterError("'{}' is not an iterable".format(seq))
69 |
70 | default = kwargs.pop('default', Undefined())
71 | attr = [int(x) if x.isdigit() else x for x in attr.split(".")]
72 |
73 | def func(item):
74 | if not isinstance(item, dict):
75 | raise errors.AnsibleFilterError("'{}' is not a mapping".format(item))
76 |
77 | for part in attr:
78 | item = environment.getitem(item, part)
79 | if isinstance(item, Undefined):
80 | item = default
81 | break
82 |
83 | return environment.call_test(func_name, item, args, kwargs)
84 |
85 | if seq:
86 | for item in seq:
87 | if func(item):
88 | yield item
89 |
--------------------------------------------------------------------------------
/molecule/default/verify.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Verify
3 | hosts: all
4 | become: true
5 | vars:
6 |
7 | tasks:
8 | - name: gather package facts
9 | ansible.builtin.package_facts:
10 |
11 | - name: check if ZFS is installed
12 | ansible.builtin.assert:
13 | that:
14 | - "'zfs-dkms' in ansible_facts.packages"
15 | - "ansible_os_family == 'RedHat' or 'zfs-initramfs' in ansible_facts.packages"
16 | - "ansible_os_family == 'RedHat' or 'zfs-zed' in ansible_facts.packages"
17 | - "ansible_os_family == 'RedHat' or 'zfsutils-linux' in ansible_facts.packages"
18 | - "'zrepl' in ansible_facts.packages"
19 |
20 | - name: gather service facts
21 | ansible.builtin.service_facts:
22 |
23 | - name: check if services are enabled and running
24 | ansible.builtin.assert:
25 | that:
26 | - "'zfs-import-cache.service' in ansible_facts.services"
27 | - "'zfs-import-scan.service' in ansible_facts.services"
28 | - "'zfs-import.service' in ansible_facts.services"
29 | - "'zfs-load-key.service' in ansible_facts.services"
30 | - "ansible_os_family == 'RedHat' or 'zfs-load-module.service' in ansible_facts.services"
31 | - "'zfs-mount.service' in ansible_facts.services"
32 | - "'zfs-scrub@.service' in ansible_facts.services"
33 | - "'zfs-share.service' in ansible_facts.services"
34 | - "'zfs-volume-wait.service' in ansible_facts.services"
35 | - "'zfs-zed.service' in ansible_facts.services"
36 | - "ansible_facts.services['zfs-import-cache.service'].status == 'enabled'"
37 | - "ansible_os_family == 'RedHat' or ansible_facts.services['zfs-load-module.service'].status == 'enabled'"
38 | - "ansible_facts.services['zfs-volume-wait.service'].status == 'enabled'"
39 | - "ansible_facts.services['zrepl.service'].status == 'enabled'"
40 |
41 | - name: get number of zpools
42 | ansible.builtin.shell: "set -o pipefail; zpool list | tail -n+2 | wc -l"
43 | args:
44 | executable: /bin/bash
45 | register: number_of_zpools
46 | changed_when: false
47 |
48 | - name: assert number of pools
49 | ansible.builtin.assert:
50 | that: number_of_zpools.stdout == '1'
51 |
52 | - name: get zfs filesystems
53 | ansible.builtin.shell: "set -o pipefail; zfs list | awk 'FNR >1' | awk '{print $1}'"
54 | args:
55 | executable: /bin/bash
56 | register: zfs_filesystems
57 | changed_when: false
58 |
59 | - name: assert filesystems
60 | ansible.builtin.assert:
61 | that:
62 | - "zfs_filesystems.stdout_lines | length == 11"
63 | - "'rpool' in zfs_filesystems.stdout_lines"
64 | - "'rpool/ROOT' in zfs_filesystems.stdout_lines"
65 | - "'rpool/ROOT/default' in zfs_filesystems.stdout_lines"
66 | - "'rpool/home' in zfs_filesystems.stdout_lines"
67 | - "'rpool/home/root' in zfs_filesystems.stdout_lines"
68 | - "'rpool/var' in zfs_filesystems.stdout_lines"
69 | - "'rpool/var/cache' in zfs_filesystems.stdout_lines"
70 | - "'rpool/var/lib' in zfs_filesystems.stdout_lines"
71 | - "'rpool/var/lib/docker' in zfs_filesystems.stdout_lines"
72 | - "'rpool/var/log' in zfs_filesystems.stdout_lines"
73 | - "'rpool/var/spool' in zfs_filesystems.stdout_lines"
74 |
75 | - name: restart zrepl to ensure at least one snapshot has been created
76 | ansible.builtin.systemd:
77 | name: zrepl.service
78 | state: restarted
79 |
80 | - name: get zfs filesystems
81 | ansible.builtin.shell: "set -o pipefail; zfs list -t snapshot | awk 'FNR >1' | awk '{print $1}'"
82 | args:
83 | executable: /bin/bash
84 | register: zfs_filesystems
85 | changed_when: false
86 |
87 | - name: assert filesystems
88 | ansible.builtin.assert:
89 | that:
90 | - "zfs_filesystems.stdout_lines | length > 0"
91 | - "'rpool/home@auto_' in zfs_filesystems.stdout"
92 |
--------------------------------------------------------------------------------
/library/prepare_zfs_properties.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from ansible.module_utils.basic import AnsibleModule
3 |
4 |
5 | def get_changed_properties(current, new, unchangeable=None):
6 | """Get the changed properties.
7 |
8 | :param current: Current properties
9 | :type current: dict
10 | :param new: New properties to be set
11 | :type new: dict
12 | :param unchangeable: Set of unchangeable properties, defaults to None
13 | :type unchangeable: set, optional
14 | :raises ValueError: If the value of an unchangeable property is tried to be changed
15 | :return: Changed properties
16 | :rtype: dict
17 | """
18 | unchangeable = unchangeable or set()
19 | changed_properties = {}
20 | for name, new_value in new.items():
21 | # convert bool types
22 | if isinstance(new_value, bool):
23 | new_value = "on" if new_value else "off"
24 | else:
25 | new_value = str(new_value)
26 |
27 | # check if new value differs from current one and if the value is changeable
28 | current_value = current.get(name, None)
29 | if current_value is None:
30 | # the dataset does not yet exist -> add new value
31 | changed_properties[name] = new_value
32 |
33 | elif new_value != current_value:
34 | if name in unchangeable:
35 | dataset_type = current["type"]
36 | raise ValueError(
37 | "The value of {} property '{}' cannot be changed after creation."
38 | .format(dataset_type, name))
39 | if name.startswith("feature@"):
40 | if (current_value in ["active", "enabled"]
41 | and new_value == "disabled"):
42 | changed_properties[name] = new_value
43 | else:
44 | changed_properties[name] = new_value
45 |
46 | return changed_properties
47 |
48 |
49 | def main():
50 | # arguments definition
51 | module_args = dict(
52 | new_properties=dict(type="dict", required=True),
53 | current_properties=dict(type="dict", default={}),
54 | type=dict(type="str",
55 | default="filesystem",
56 | choices=["filesystem", "volume", "zpool"]),
57 | )
58 | module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
59 |
60 | # parse arguments
61 | new_properties = module.params["new_properties"]
62 | current_properties = module.params["current_properties"]
63 | type_ = module.params["type"]
64 |
65 | result = {}
66 |
67 | try:
68 | if type_ in ["filesystem", "volume"]:
69 | dataset_type = current_properties.get("type", type_)
70 | unchangeable_properties = {
71 | "filesystem": {
72 | "casesensitivity",
73 | "encryption",
74 | "keyformat",
75 | "normalization",
76 | "pbkdf2iters",
77 | "utf8only",
78 | },
79 | "volume": {
80 | "encryption",
81 | "keyformat",
82 | "pbkdf2iters",
83 | "volblocksize",
84 | "volsize",
85 | },
86 | "snapshot": {
87 | "casesensitivity",
88 | "encryption",
89 | "keyformat",
90 | "normalization",
91 | "pbkdf2iters",
92 | "utf8only",
93 | }
94 | }.get(dataset_type)
95 | result["properties"] = get_changed_properties(
96 | current_properties, new_properties, unchangeable_properties)
97 | else:
98 | result["properties"] = get_changed_properties(
99 | current_properties, new_properties)
100 | except ValueError as err:
101 | module.fail_json(msg=str(err))
102 |
103 | result["changed"] = (len(result["properties"]) > 0)
104 | module.exit_json(**result)
105 |
106 |
107 | if __name__ == '__main__':
108 | main()
109 |
--------------------------------------------------------------------------------
/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # ------------------------------------------------------------------------------
3 | # ZFS Installation
4 | # ------------------------------------------------------------------------------
5 |
6 | # enables/disables management of ZFS package repository
7 | zfs_manage_repository: true
8 |
9 | # installation on CentOS systems
10 | # can be either kmod or dkms; for more information see:
11 | # https://openzfs.github.io/openzfs-docs/Getting%20Started/RHEL%20and%20CentOS.html
12 | zfs_redhat_style: kmod
13 | zfs_redhat_repo_dkms_url: "http://download.zfsonlinux.org/epel/{{ ansible_distribution_version }}/$basearch/"
14 | zfs_redhat_repo_kmod_url: "http://download.zfsonlinux.org/epel/{{ ansible_distribution_version }}/kmod/$basearch/"
15 | zfs_redhat_repo_proxy:
16 |
17 | # installation on Debian systems
18 | zfs_debian_repo: "{{ ansible_distribution_release }}-backports"
19 |
20 |
21 |
22 | # ------------------------------------------------------------------------------
23 | # ZFS Service Management
24 | # ------------------------------------------------------------------------------
25 |
26 | # import ZFS pools by cache file
27 | zfs_service_import_cache_enabled: true
28 | # import ZFS pools by device scanning
29 | zfs_service_import_scan_enabled: false
30 | # ZFS mount mechanism
31 | zfs_service_mount_enabled: "{{ not (zfs_use_zfs_mount_generator | bool) }}"
32 | # ZFS file system shares
33 | zfs_service_share_enabled: false
34 | # wait for ZFS Volume (zvol) links in /dev
35 | zfs_service_volume_wait_enabled: true
36 | # ZFS Event Daemon (zed)
37 | zfs_service_zed_enabled: false
38 |
39 | # use systemd mount generator, to mount ZVolumes via systemd
40 | zfs_use_zfs_mount_generator: false
41 |
42 |
43 |
44 | # ------------------------------------------------------------------------------
45 | # ZFS Configuration
46 | # ------------------------------------------------------------------------------
47 |
48 | # https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html
49 | zfs_kernel_module_parameters: {}
50 |
51 | # time schedule for zpool scrubs; for valid time formats see:
52 | # https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events
53 | zfs_scrub_schedule: monthly
54 |
55 | # time schedule for trimming zpools; for valid time formats see:
56 | # https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events
57 | zfs_trim_schedule: weekly
58 |
59 | # set IO scheduler for listed HDDs to 'none'
60 | zfs_config_none_ioscheduler: []
61 |
62 |
63 |
64 | # ------------------------------------------------------------------------------
65 | # ZFS Pools and Datasets
66 | # ------------------------------------------------------------------------------
67 |
68 | zfs_pools: []
69 | # - name:
70 | # vdev: >-
71 | # mirror
72 | # dev1
73 | # dev1
74 | # mirror
75 | # dev1
76 | # dev2
77 | # log dd
78 | # scrub: true
79 | # dont_enable_features: false
80 | # properties:
81 | # ashift: 9
82 | # filesystem_properties:
83 | # extra_import_options: ""
84 | # extra_create_options: ""
85 | # https://openzfs.github.io/openzfs-docs/man/7/zpoolprops.7.html
86 | zfs_pools_defaults: {}
87 |
88 | zfs_volumes: []
89 | # https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html
90 | zfs_volumes_properties_defaults:
91 | volblocksize: 8K
92 | volsize: 1G
93 | compression: lz4
94 | dedup: false
95 | sync: standard
96 |
97 | zfs_filesystems: []
98 | # https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html
99 | zfs_filesystems_properties_defaults:
100 | acltype: posix
101 | atime: false
102 | canmount: true
103 | casesensitivity: sensitive
104 | compression: lz4
105 | dedup: false
106 | normalization: formD
107 | setuid: true
108 | snapdir: hidden
109 | sync: standard
110 | utf8only: true
111 | xattr: sa
112 |
113 |
114 |
115 | # ------------------------------------------------------------------------------
116 | # Automatic Snapshots using ZREPL
117 | # ------------------------------------------------------------------------------
118 |
119 | # configure zrepl; see https://zrepl.github.io/configuration/overview.html
120 | zfs_zrepl_config: {}
121 |
122 | # install and enable zrepl (https://github.com/zrepl/zrepl)
123 | zfs_zrepl_enabled: false
124 |
125 | # RPM repository URL of zrepl
126 | zfs_zrepl_redhat_repo_url: "https://zrepl.cschwarz.com/rpm/repo"
127 |
128 | # DEB repository URL of zrepl
129 | zfs_zrepl_debian_repo_url: "https://zrepl.cschwarz.com/apt"
130 |
--------------------------------------------------------------------------------
/molecule/default/converge.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Converge
3 | hosts: all
4 | become: true
5 | vars:
6 | #
7 | # Installation
8 | #
9 |
10 | zfs_redhat_style: dkms
11 |
12 | #
13 | # Service
14 | #
15 |
16 | # generate mount points using systemd
17 | zfs_use_zfs_mount_generator: true
18 | # use zfs_mount_generator but don't invoke ZED (Docker triggers it quite often)
19 | zfs_service_zed_enabled: false
20 |
21 |
22 | #
23 | # Configuration
24 | #
25 |
26 | # https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-module-parameters
27 | zfs_kernel_module_parameters:
28 | # use 1/4 of the memory for ZFS ARC
29 | zfs_arc_max: "{{ (ansible_memtotal_mb * 1024**2 * 0.25) | int }}"
30 |
31 | # schedule for ZFS scrubs
32 | zfs_scrub_schedule: monthly
33 |
34 | _zfs_performance_tuning_default:
35 | # store less metadata (still redundant in mirror setups)
36 | redundant_metadata: most
37 | # use standard behaviour for synchronous writes
38 | sync: standard
39 |
40 | _zfs_performance_tuning_async_only:
41 | # store less metadata (still redundant in mirror setups)
42 | redundant_metadata: most
43 | # turn synchronous writes into asynchronous ones
44 | sync: disabled
45 |
46 | _zfs_performance_tuning_ssd:
47 | # use standard behaviour for synchronous writes
48 | sync: standard
49 | # store less metadata (still redundant in mirror setups)
50 | redundant_metadata: most
51 | # optimize synchronous operations to write directly to disk instead of writing
52 | # to log. On HDDs this decreases the latency, but won't do much on SSDs.
53 | logbias: throughput
54 |
55 | _zfs_filesytems_properties:
56 | canmount: true
57 | snapdir: hidden
58 |
59 | # make ZFS behave like a Linux FS
60 | casesensitivity: sensitive
61 | normalization: formD
62 | utf8only: on
63 | setuid: true
64 | atime: false
65 |
66 | # enable use of ACLs
67 | acltype: posix
68 | xattr: sa
69 |
70 | # compression and deduplication
71 | compression: lz4
72 | dedup: false
73 |
74 | zfs_filesystems_properties_defaults: "{{
75 | _zfs_filesytems_properties | combine(
76 | _zfs_performance_tuning_async_only
77 | )}}"
78 |
79 | _zfs_volumes_properties:
80 | volblocksize: 8K
81 | volsize: 1G
82 | compression: lz4
83 | dedup: false
84 |
85 | # https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html
86 | zfs_volumes_properties_defaults: "{{
87 | _zfs_volumes_properties | combine(
88 | _zfs_performance_tuning_async_only
89 | )}}"
90 |
91 |
92 | #
93 | # ZPools
94 | #
95 |
96 | zfs_pools:
97 | - name: rpool
98 | vdev: >-
99 | mirror
100 | /dev/loop0
101 | /dev/loop1
102 | scrub: true
103 | filesystem_properties:
104 | canmount: off
105 | mountpoint: /zfs
106 |
107 |
108 | #
109 | # Datasets
110 | #
111 |
112 | zfs_filesystems:
113 | - name: rpool/ROOT
114 | properties:
115 | canmount: off
116 | mountpoint: none
117 | - name: rpool/ROOT/default
118 | properties:
119 | canmount: noauto
120 | mountpoint: /zfs
121 |
122 | - name: rpool/home
123 | - name: rpool/home/root
124 | properties:
125 | mountpoint: /root
126 | - name: rpool/var/lib/docker
127 | - name: rpool/var/log
128 | - name: rpool/var/spool
129 | - name: rpool/var/cache
130 |
131 |
132 | #
133 | # Automatic Snapthots Using ZREPL
134 | #
135 |
136 | zfs_zrepl_enabled: true
137 | zfs_zrepl_config:
138 | jobs:
139 | - name: storage
140 | type: snap
141 | filesystems: {
142 | "rpool<": true,
143 | "rpool/var<": false,
144 | }
145 | snapshotting:
146 | type: periodic
147 | interval: 12h
148 | prefix: auto_
149 | pruning:
150 | keep:
151 | # prune automatic snapshots
152 | - type: grid
153 | # in first 24 hours keep all snapshots
154 | # in first 7 days 1 snapshot each day
155 | # in first month keep 1 snapshot each week
156 | # discard the rest
157 | # details see: https://zrepl.github.io/configuration/prune.html#policy-grid
158 | grid: 1x24h(keep=all) | 7x1d(keep=1) | 3x7d(keep=1)
159 | regex: "^auto_.*"
160 |
161 | # keep manual snapshots
162 | - type: regex
163 | regex: "^manual_.*"
164 |
165 | roles:
166 | - role: aisbergg.zfs
167 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or
22 | advances of any kind
23 | * Trolling, insulting or derogatory comments, and personal or political attacks
24 | * Public or private harassment
25 | * Publishing others' private information, such as a physical or email
26 | address, without their explicit permission
27 | * Other conduct which could reasonably be considered inappropriate in a
28 | professional setting
29 |
30 | ## Enforcement Responsibilities
31 |
32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33 |
34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35 |
36 | ## Scope
37 |
38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39 |
40 | ## Enforcement
41 |
42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at aisberg@posteo.de. All complaints will be reviewed and investigated promptly and fairly.
43 |
44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45 |
46 | ## Enforcement Guidelines
47 |
48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49 |
50 | ### 1. Correction
51 |
52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53 |
54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55 |
56 | ### 2. Warning
57 |
58 | **Community Impact**: A violation through a single incident or series of actions.
59 |
60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61 |
62 | ### 3. Temporary Ban
63 |
64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65 |
66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67 |
68 | ### 4. Permanent Ban
69 |
70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71 |
72 | **Consequence**: A permanent ban from any sort of public interaction within the community.
73 |
74 | ## Attribution
75 |
76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78 |
79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80 |
81 | [homepage]: https://www.contributor-covenant.org
82 |
83 | For answers to common questions about this code of conduct, see the FAQ at
84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
85 |
--------------------------------------------------------------------------------
/tasks/manage_pool.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: assert correctness of zpool parameters
3 | ansible.builtin.assert:
4 | that:
5 | - zfs_pool.name is defined and zfs_pool.name is string
6 | - zfs_pool.vdev is defined and zfs_pool.vdev is string
7 | - (zfs_pool.scrub | default(true) | bool) is boolean
8 | - (zfs_pool.dont_enable_features | default(false) | bool) is boolean
9 | - (zfs_pool.properties | default({})) is mapping
10 | - (zfs_pool.filesystem_properties | default({})) is mapping
11 | - (zfs_pool.extra_import_options | default('')) is string
12 | - (zfs_pool.extra_create_options | default('')) is string
13 | quiet: true
14 | fail_msg: ZPool parameters are invalid
15 |
16 | # import a zpool
17 | - when:
18 | - zfs_pool.name not in available_zpools.stdout_lines
19 | - zfs_pool.name in importable_zpools.stdout_lines
20 | block:
21 | - name: prepare zpool properties
22 | prepare_zfs_properties:
23 | new_properties: "{{ zfs_pool.properties | default({}) }}"
24 | type: zpool
25 | register: prepared_zpool_properties
26 |
27 | - name: create 'zpool import' command
28 | ansible.builtin.set_fact:
29 | zpool_import_command: >-
30 | zpool import
31 | {% for property, value in (prepared_zpool_properties.properties | dictsort) %}
32 | -o {{ (property ~ '=' ~ value) | quote }}
33 | {% endfor %}
34 | {{ zfs_pool.extra_import_options | default('') }}
35 | {{ zfs_pool.name }}
36 |
37 | - name: print ZPool import command
38 | ansible.builtin.debug:
39 | msg: "{{ zpool_import_command }}"
40 | verbosity: 1
41 |
42 | - name: import ZPool '{{ zfs_pool.name }}'
43 | ansible.builtin.command: "{{ zpool_import_command }}"
44 | register: zpool_import_result
45 | changed_when: true
46 | failed_when: (zpool_import_result.rc != 0) or (zpool_import_result.stderr | length > 0)
47 |
48 |
49 |
50 | # create a new zpool
51 | - when:
52 | - zfs_pool.name not in available_zpools.stdout_lines
53 | - zfs_pool.name not in importable_zpools.stdout_lines
54 | block:
55 | - name: prepare zpool properties
56 | prepare_zfs_properties:
57 | new_properties: "{{ zfs_pool.properties | default({}) }}"
58 | type: zpool
59 | register: prepared_zpool_properties
60 |
61 | - name: prepare dataset properties
62 | prepare_zfs_properties:
63 | new_properties: "{{ zfs_dataset_properties }}"
64 | type: filesystem
65 | register: prepared_dataset_properties
66 |
67 | - name: create 'zpool create' command
68 | ansible.builtin.set_fact:
69 | zpool_create_command: >-
70 | zpool create
71 | {{ '-d ' if zfs_pool.dont_enable_features | default(false) | bool else '' }}
72 | {% for property, value in (prepared_zpool_properties.properties | dictsort) %}
73 | -o {{ (property ~ '=' ~ value) | quote }}
74 | {% endfor %}
75 | {% for property, value in (prepared_dataset_properties.properties | dictsort) %}
76 | -O {{ (property ~ '=' ~ value) | quote }}
77 | {% endfor %}
78 | {{ zfs_pool.name }}
79 | {{ zfs_pool.extra_create_options | default('') }}
80 | {{ zfs_pool.vdev | regex_replace('\n\s*', ' ') }}
81 |
82 | - name: print ZPool creation command
83 | ansible.builtin.debug:
84 | msg: "{{ zpool_create_command }}"
85 | verbosity: 1
86 |
87 | - name: create ZPool '{{ zfs_pool.name }}'
88 | ansible.builtin.command: "{{ zpool_create_command }}"
89 | changed_when: true
90 |
91 |
92 |
93 | # set zpool properties on an existing pool
94 | - when: zfs_pool.name in available_zpools.stdout_lines
95 | block:
96 | - name: gather properties of '{{ zfs_pool.name }}'
97 | community.general.zpool_facts:
98 | name: "{{ zfs_pool.name }}"
99 | parsable: true
100 | failed_when: false
101 | changed_when: false
102 | register: zfs_zpool_facts
103 |
104 | - name: prepare zpool properties
105 | prepare_zfs_properties:
106 | new_properties: "{{ zfs_pool.properties | default({}) }}"
107 | current_properties: "{{ ansible_zfs_pools.0 | default({}) }}"
108 | type: zpool
109 | register: prepared_zpool_properties
110 |
111 | - name: manage zpool properties of '{{ zfs_pool.name }}'
112 | ansible.builtin.command: "zpool set {{ (item.0 ~ '=' ~ item.1) | quote }} {{ zfs_pool.name }}"
113 | changed_when: true
114 | loop_control:
115 | label: "{{ item.0 }}={{ item.1 }}"
116 | loop: "{{ prepared_zpool_properties.properties | default({}) | dict2items }}"
117 |
118 |
119 |
120 | # set dataset properties on an existing pool
121 | - when: (zfs_pool.name in available_zpools.stdout_lines)
122 | or (zfs_pool.name in importable_zpools.stdout_lines)
123 | block:
124 | - name: gather dataset properties of '{{ zfs_pool.name }}'
125 | community.general.zfs_facts:
126 | name: "{{ zfs_pool.name }}"
127 | failed_when: false
128 | changed_when: false
129 |
130 | - name: prepare dataset properties
131 | prepare_zfs_properties:
132 | new_properties: "{{ zfs_dataset_properties }}"
133 | current_properties: "{{ ansible_zfs_datasets[0] | default({}) }}"
134 | type: filesystem
135 | register: prepared_dataset_properties
136 |
137 | - name: manage dataset properties of '{{ zfs_pool.name }}'
138 | community.general.zfs:
139 | name: "{{ zfs_pool.name }}"
140 | state: present
141 | extra_zfs_properties: "{{ prepared_dataset_properties.properties }}"
142 | when: "prepared_dataset_properties.properties | length > 0"
143 |
--------------------------------------------------------------------------------
/templates/etc/default/zfs.j2:
--------------------------------------------------------------------------------
1 | {{ ansible_managed | comment }}
2 |
3 | # ZoL userland configuration.
4 |
5 | # NOTE: This file is intended for sysv init and initramfs.
6 | # Changing some of these settings may not make any difference on
7 | # systemd-based setup, e.g. setting ZFS_MOUNT=no will not prevent systemd
8 | # from launching zfs-mount.service during boot.
9 | # See: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=901436
10 |
11 | # To enable a boolean setting, set it to yes, on, true, or 1.
12 | # Anything else will be interpreted as unset.
13 |
14 | # Run `zfs mount -a` during system start?
15 | ZFS_MOUNT='yes'
16 |
17 | # Run `zfs unmount -a` during system stop?
18 | ZFS_UNMOUNT='yes'
19 |
20 | # Run `zfs share -a` during system start?
21 | # nb: The shareiscsi, sharenfs, and sharesmb dataset properties.
22 | ZFS_SHARE='yes'
23 |
24 | # Run `zfs unshare -a` during system stop?
25 | ZFS_UNSHARE='yes'
26 |
27 | # By default, a verbatim import of all pools is performed at boot based on the
28 | # contents of the default zpool cache file. The contents of the cache are
29 | # managed automatically by the 'zpool import' and 'zpool export' commands.
30 | #
31 | # By setting this to 'yes', the system will instead search all devices for
32 | # pools and attempt to import them all at boot, even those that have been
33 | # exported. Under this mode, the search path can be controlled by the
34 | # ZPOOL_IMPORT_PATH variable and a list of pools that should not be imported
35 | # can be listed in the ZFS_POOL_EXCEPTIONS variable.
36 | #
37 | # Note that importing all visible pools may include pools that you don't
38 | # expect, such as those on removable devices and SANs, and those pools may
39 | # proceed to mount themselves in places you do not want them to. The results
40 | # can be unpredictable and possibly dangerous. Only enable this option if you
41 | # understand this risk and have complete physical control over your system and
42 | # SAN to prevent the insertion of malicious pools.
43 | ZPOOL_IMPORT_ALL_VISIBLE='no'
44 |
45 | # Specify specific path(s) to look for device nodes and/or links for the
46 | # pool import(s). See zpool(8) for more information about this variable.
47 | # It supersedes the old USE_DISK_BY_ID which indicated that it would only
48 | # try '/dev/disk/by-id'.
49 | # The old variable will still work in the code, but is deprecated.
50 | #ZPOOL_IMPORT_PATH="/dev/disk/by-vdev:/dev/disk/by-id"
51 |
52 | # List of pools that should NOT be imported at boot
53 | # when ZPOOL_IMPORT_ALL_VISIBLE is 'yes'.
54 | # This is a space separated list.
55 | #ZFS_POOL_EXCEPTIONS="test2"
56 |
57 | # List of pools that SHOULD be imported at boot by the initramfs
58 | # instead of trying to import all available pools. If this is set
59 | # then ZFS_POOL_EXCEPTIONS is ignored.
60 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
61 | # This is a semi-colon separated list.
62 | #ZFS_POOL_IMPORT="pool1;pool2"
63 |
64 | # Should the datasets be mounted verbosely?
65 | # A mount counter will be used when mounting if set to 'yes'.
66 | VERBOSE_MOUNT='no'
67 |
68 | # Should we allow overlay mounts?
69 | # This is standard in Linux, but not ZFS which comes from Solaris where this
70 | # is not allowed).
71 | DO_OVERLAY_MOUNTS='no'
72 |
73 | # Any additional option to the 'zfs import' commandline?
74 | # Include '-o' for each option wanted.
75 | # You don't need to put '-f' in here, unless you want it ALL the time.
76 | # Using the option 'zfsforce=1' on the grub/kernel command line will
77 | # do the same, but on a case-to-case basis.
78 | ZPOOL_IMPORT_OPTS=""
79 |
80 | # Full path to the ZFS cache file?
81 | # See "cachefile" in zpool(8).
82 | # The default is "@sysconfdir@/zfs/zpool.cache".
83 | #ZPOOL_CACHE="@sysconfdir@/zfs/zpool.cache"
84 | #
85 | # Setting ZPOOL_CACHE to an empty string ('') AND setting ZPOOL_IMPORT_OPTS to
86 | # "-c @sysconfdir@/zfs/zpool.cache" will _enforce_ the use of a cache file.
87 | # This is needed in some cases (extreme amounts of VDEVs, multipath etc).
88 | # Generally, the use of a cache file is usually not recommended on Linux
89 | # because it sometimes is more trouble than it's worth (laptops with external
90 | # devices or when/if device nodes changes names).
91 | #ZPOOL_IMPORT_OPTS="-c @sysconfdir@/zfs/zpool.cache"
92 | #ZPOOL_CACHE=""
93 |
94 | # Any additional option to the 'zfs mount' command line?
95 | # Include '-o' for each option wanted.
96 | MOUNT_EXTRA_OPTIONS=""
97 |
98 | # Build kernel modules with the --enable-debug switch?
99 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
100 | ZFS_DKMS_ENABLE_DEBUG='no'
101 |
102 | # Build kernel modules with the --enable-debuginfo switch?
103 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
104 | ZFS_DKMS_ENABLE_DEBUGINFO='no'
105 |
106 | # Keep debugging symbols in kernel modules?
107 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
108 | ZFS_DKMS_DISABLE_STRIP='no'
109 |
110 | # Wait for this many seconds in the initrd pre_mountroot?
111 | # This delays startup and should be '0' on most systems.
112 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
113 | ZFS_INITRD_PRE_MOUNTROOT_SLEEP='0'
114 |
115 | # Wait for this many seconds in the initrd mountroot?
116 | # This delays startup and should be '0' on most systems. This might help on
117 | # systems which have their ZFS root on a USB disk that takes just a little
118 | # longer to be available
119 | # Only applicable for Debian GNU/Linux {dkms,initramfs}.
120 | ZFS_INITRD_POST_MODPROBE_SLEEP='0'
121 |
122 | # List of additional datasets to mount after the root dataset is mounted?
123 | #
124 | # The init script will use the mountpoint specified in the 'mountpoint'
125 | # property value in the dataset to determine where it should be mounted.
126 | #
127 | # This is a space separated list, and will be mounted in the order specified,
128 | # so if one filesystem depends on a previous mountpoint, make sure to put
129 | # them in the right order.
130 | #
131 | # It is not necessary to add filesystems below the root fs here. It is
132 | # taken care of by the initrd script automatically. These are only for
133 | # additional filesystems needed. Such as /opt, /usr/local which is not
134 | # located under the root fs.
135 | # Example: If root FS is 'rpool/ROOT/rootfs', this would make sense.
136 | #ZFS_INITRD_ADDITIONAL_DATASETS="rpool/ROOT/usr rpool/ROOT/var"
137 |
138 | # Optional arguments for the ZFS Event Daemon (ZED).
139 | # See zed(8) for more information on available options.
140 | #ZED_ARGS="-M"
141 |
--------------------------------------------------------------------------------
/tasks/manage_services.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # ------------------------------------------------------------------------------
3 | # ZFS Mount Generator
4 | # ------------------------------------------------------------------------------
5 |
6 | - when:
7 | - zfs_use_zfs_mount_generator | bool
8 | - not (ansible_distribution == 'Ubuntu' and ansible_distribution_release == 'bionic')
9 | block: # noqa: name[missing]
10 | - name: enable the ZED script
11 | ansible.builtin.file:
12 | src: "{{ __zfs_zedletdir }}/history_event-zfs-list-cacher.sh"
13 | dest: /etc/zfs/zed.d/history_event-zfs-list-cacher.sh
14 | state: link
15 | register: history_event_zedlet
16 |
17 | - name: inform ZED about script change
18 | ansible.builtin.systemd:
19 | name: zfs-zed
20 | state: restarted
21 | when:
22 | - zfs_service_zed_enabled
23 | - history_event_zedlet.changed
24 |
25 | - name: ensure mount generator cache directory exists
26 | ansible.builtin.file:
27 | path: /etc/zfs/zfs-list.cache
28 | state: directory
29 | owner: root
30 | group: root
31 | mode: "0755"
32 |
33 | - name: create generator cache content # noqa: risky-shell-pipe
34 | ansible.builtin.shell: |
35 | # see history_event-zfs-list-cacher.sh
36 | PROPS="name,mountpoint,canmount,atime,relatime,devices,exec\
37 | ,readonly,setuid,nbmand,encroot,keylocation\
38 | ,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for\
39 | ,org.openzfs.systemd:before,org.openzfs.systemd:after\
40 | ,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by\
41 | ,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore"
42 |
43 | # include only datasets, that do not use legacy mounts
44 | zfs list -H -t filesystem -o $PROPS -r -s name {{ zfs_pool.name | quote }} | awk '{if ($2 != "legacy") {print} }'
45 |
46 | register: zfs_list_cache
47 | changed_when: false
48 | check_mode: false
49 | loop: "{{ zfs_pools }}"
50 | loop_control:
51 | loop_var: zfs_pool
52 | label: "{{ zfs_pool.name }}"
53 |
54 | - name: create generator cache files
55 | ansible.builtin.copy:
56 | content: "{{ cache_entry.stdout }}"
57 | dest: "/etc/zfs/zfs-list.cache/{{ cache_entry.zfs_pool.name }}"
58 | owner: root
59 | group: root
60 | mode: "0644"
61 | loop: "{{ zfs_list_cache.results }}"
62 | loop_control:
63 | loop_var: cache_entry
64 | label: "{{ cache_entry.zfs_pool.name }}"
65 |
66 | # fix for journald when using zfs-mount.service
67 | - when: zfs_service_mount_enabled
68 | block: # noqa: name[missing]
69 | - name: ensure '/etc/systemd/system/systemd-journald.service.d' directory exists
70 | ansible.builtin.file:
71 | path: /etc/systemd/system/systemd-journald.service.d/
72 | state: directory
73 | owner: root
74 | group: root
75 | mode: "0755"
76 |
77 | - name: create dependency for journald
78 | ansible.builtin.template:
79 | src: etc/systemd/system/systemd-journald.service.d/after-zfs-mount.service.j2
80 | dest: /etc/systemd/system/systemd-journald.service.d/after-zfs-mount.service
81 | owner: root
82 | group: root
83 | mode: "0644"
84 | register: journald_service
85 |
86 |
87 | # ------------------------------------------------------------------------------
88 | # ZFS Services
89 | # ------------------------------------------------------------------------------
90 |
91 | - name: manage ZFS services
92 | ansible.builtin.systemd:
93 | name: "{{ item.name }}"
94 | enabled: "{{ item.enabled }}"
95 | state: "{{ item.enabled | ternary('started', 'stopped') }}"
96 | # exclude zfs-volume-wait on Ubuntu Bionic
97 | when: >-
98 | not (
99 | item.name == 'zfs-volume-wait' and
100 | (ansible_distribution == 'Ubuntu' and ansible_distribution_release == 'bionic')
101 | )
102 | loop:
103 | - name: zfs.target
104 | enabled: true
105 | - name: zfs-import.target
106 | enabled: true
107 | - name: zfs-import-cache
108 | enabled: "{{ zfs_service_import_cache_enabled | bool }}"
109 | - name: zfs-import-scan
110 | enabled: "{{ zfs_service_import_scan_enabled | bool }}"
111 | - name: zfs-mount
112 | enabled: "{{ zfs_service_mount_enabled | bool }}"
113 | - name: zfs-share
114 | enabled: "{{ zfs_service_share_enabled | bool }}"
115 | - name: zfs-volume-wait
116 | enabled: "{{ zfs_service_volume_wait_enabled | bool }}"
117 | - name: zfs-zed
118 | enabled: "{{ zfs_service_zed_enabled | bool }}"
119 |
120 |
121 | # ------------------------------------------------------------------------------
122 | # ZFS Scrub Service
123 | # ------------------------------------------------------------------------------
124 |
125 | - block: # noqa: name[missing]
126 | - name: remove scrub cron job
127 | ansible.builtin.file:
128 | path: /etc/cron.d/zfsutils-linux
129 | state: absent
130 |
131 | - name: create scrub service
132 | ansible.builtin.template:
133 | src: etc/systemd/system/zpool-scrub@.service.j2
134 | dest: /etc/systemd/system/zpool-scrub@.service
135 | owner: root
136 | group: root
137 | mode: "0644"
138 | register: scrub_service
139 |
140 | - name: create scrub timer
141 | ansible.builtin.template:
142 | src: etc/systemd/system/zpool-scrub@.timer.j2
143 | dest: /etc/systemd/system/zpool-scrub@.timer
144 | owner: root
145 | group: root
146 | mode: "0644"
147 | register: scrub_timer
148 |
149 | - name: reload systemd
150 | ansible.builtin.systemd:
151 | daemon_reload: true
152 | when: scrub_service.changed or scrub_timer.changed or journald_service.changed
153 |
154 | - name: enable scrub timer
155 | ansible.builtin.systemd:
156 | name: "zpool-scrub@{{ item.name }}.timer"
157 | enabled: true
158 | state: started
159 | when: item.scrub | default(true) | bool
160 | loop: "{{ zfs_pools }}"
161 | loop_control:
162 | label: "{{ item.name }}"
163 |
164 | - name: gather service facts
165 | ansible.builtin.service_facts:
166 |
167 | - name: disable undefined scrub timer
168 | ansible.builtin.systemd:
169 | name: "zpool-scrub@{{ item }}.timer"
170 | enabled: false
171 | state: stopped
172 | loop: "{{
173 | ansible_facts.services.keys() |
174 | select('match', 'zpool-scrub@.+\\.service') |
175 | map('regex_replace', 'zpool-scrub@(.+)\\.service', '\\1') | list |
176 | difference(zfs_pools | selectattr2('scrub', '==', true, default=true) | map(attribute='name') | list)
177 | }}"
178 |
179 |
180 | # ------------------------------------------------------------------------------
181 | # ZFS Trim Service
182 | # ------------------------------------------------------------------------------
183 |
184 | - block:
185 | - name: remove trim cron job
186 | ansible.builtin.file:
187 | path: /etc/cron.d/zfsutils-linux
188 | state: absent
189 |
190 | - name: create trim service
191 | ansible.builtin.template:
192 | src: etc/systemd/system/zpool-trim@.service.j2
193 | dest: /etc/systemd/system/zpool-trim@.service
194 | owner: root
195 | group: root
196 | mode: "0644"
197 | register: trim_service
198 |
199 | - name: create trim timer
200 | ansible.builtin.template:
201 | src: etc/systemd/system/zpool-trim@.timer.j2
202 | dest: /etc/systemd/system/zpool-trim@.timer
203 | owner: root
204 | group: root
205 | mode: "0644"
206 | register: trim_timer
207 |
208 | - name: reload systemd
209 | ansible.builtin.systemd:
210 | daemon_reload: true
211 | when: trim_service.changed or trim_timer.changed or journald_service.changed
212 |
213 | - name: enable trim timer
214 | ansible.builtin.systemd:
215 | name: "zpool-trim@{{ item.name }}.timer"
216 | enabled: true
217 | state: started
218 | when: item.trim | default(false) | bool
219 | loop: "{{ zfs_pools }}"
220 | loop_control:
221 | label: "{{ item.name }}"
222 |
223 | - name: gather service facts
224 | ansible.builtin.service_facts:
225 |
226 | - name: disable undefined trim timer
227 | ansible.builtin.systemd:
228 | name: "zpool-trim@{{ item }}.timer"
229 | enabled: false
230 | state: stopped
231 | loop: "{{
232 | ansible_facts.services.keys() |
233 | select('match', 'zpool-trim@.+\\.service') |
234 | map('regex_replace', 'zpool-trim@(.+)\\.service', '\\1') | list |
235 | difference(zfs_pools | selectattr2('trim', '==', true, default=true) | map(attribute='name') | list)
236 | }}"
237 |
238 |
239 | # ------------------------------------------------------------------------------
240 | # ZREPL Service
241 | # ------------------------------------------------------------------------------
242 |
243 | - name: manage zrepl service
244 | ansible.builtin.systemd:
245 | name: zrepl
246 | enabled: "{{ zfs_zrepl_enabled | bool }}"
247 | state: "{{ zfs_zrepl_enabled | bool | ternary('started', 'stopped') }}"
248 | when: ('zrepl.service' in ansible_facts.services)
249 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ansible Role: `aisbergg.zfs`
2 |
3 | This Ansible role installs the ZFS filesystem module, creates or imports zpools and manages ZFS datasets on Debian and RedHat systems.
4 |
5 | Destructive operations such as ZFS pool deletions are out of scope and not supported by the role. Therefore, you don't have to worry to much about losing data when using this role.
6 |
7 | **Table of Contents:**
8 |
9 | - [Requirements](#requirements)
10 | - [Role Variables](#role-variables)
11 | - [Dependencies](#dependencies)
12 | - [Example Playbooks](#example-playbooks)
13 | - [Basic Example](#basic-example)
14 | - [Advanced Example](#advanced-example)
15 | - [License](#license)
16 | - [Author Information](#author-information)
17 |
18 | ---
19 |
20 | ## Requirements
21 |
22 | - For Debian systems, the `backports` `contrib` repository needs to be enabled first. It is not included in the role. See [Debian Backports](https://backports.debian.org/Instructions/) for instructions.
23 | - The target system needs to be managed with Systemd.
24 |
25 | ## Role Variables
26 |
27 | **Bold** variables are required.
28 |
29 | | Variable | Default | Comments |
30 | |----------|---------|----------|
31 | | `zfs_manage_repository` | `true` | Manage package repositories (YUM, APT). |
32 | | `zfs_redhat_style` | `kmod` | Style of ZFS module installation. Can be either kmod or dkms. Applies only to RedHat systems. See [official documentation](https://openzfs.github.io/openzfs-docs/Getting%20Started/RHEL-based%20distro/index.html#rhel-based-distro) for information on DKMS and kmod version of openZFS. |
33 | | `zfs_redhat_repo_dkms_url` | `http://download.zfsonlinux.org/`
`epel/{{ ansible_distribution_version }}/$basearch/` | Repository URL used for DKMS installation of ZFS. Applies only to RedHat systems. |
34 | | `zfs_redhat_repo_kmod_url` | `http://download.zfsonlinux.org/`
`epel/{{ ansible_distribution_version }}/kmod/$basearch/` | Repository URL used for kmod installation of ZFS. Applies only to RedHat systems. |
35 | | `zfs_redhat_repo_proxy` | | YUM/DNF repository proxy URL |
36 | | `zfs_debian_repo` | `{{ ansible_distribution_release }}-backports` | Repository used for installation. Applies only to Debian systems. |
37 | | `zfs_service_import_cache_enabled` | `true` | Enable service to import ZFS pools by cache file. |
38 | | `zfs_service_import_scan_enabled` | `false` | Enable service to import ZFS pools by device scanning. |
39 | | `zfs_service_mount_enabled` | `false` if zfs_use_zfs_mount_generator else `true` }}"` | Enable service to mount ZFS filesystems using the ZFS built-in mounting mechanism. |
40 | | `zfs_service_share_enabled` | `false` | Enable ZFS file system shares service. |
41 | | `zfs_service_volume_wait_enabled` | `true` | Enable service to wait for ZFS Volume links in `/dev`. |
42 | | `zfs_service_zed_enabled` | `false` | Enable ZFS Event Daemon (ZED) service. |
43 | | `zfs_use_zfs_mount_generator` | `false` | Enable Systemd Mount Generator, to automatically mount volumes on boot with Systemd. |
44 | | `zfs_kernel_module_parameters` | `{}` | Dictionary (key-value pairs) of ZFS kernel module parameters. See [official documentation](https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html?highlight=zfs_arc_max) for available parameters. |
45 | | `zfs_scrub_schedule` | `monthly` | Time schedule for zpool scrubs. Valid options can be looked up [here](https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events). |
46 | | `zfs_trim_schedule` | `weekly` | Time schedule for trim operations (for SSDs or virtual drives). Valid options can be looked up [here](https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events). |
47 | | `zfs_config_none_ioscheduler` | `[]` | Set IO scheduler for the listed HDDs to `none`. |
48 | | `zfs_pools` | `[]` | List of ZFS Pools (zpools). |
49 | | **`zfs_pools[].name`** | | Name of the ZPool. |
50 | | **`zfs_pools[].vdev`** | | VDev definition for the ZPool. |
51 | | `zfs_pools[].scrub` | `true` | Enable scrub for this ZPool. |
52 | | `zfs_pools[].dont_enable_features` | `false` | Don't enable any feature. Use this in combination with `properties` to enable a custom set of features. |
53 | | `zfs_pools[].properties` | `{}` | ZPool properties. |
54 | | `zfs_pools[].filesystem_properties` | `{}` | Filesystem properties to apply to the whole ZPool. |
55 | | `zfs_pools[].extra_import_options` | `""` | String of extra options to pass to the ZPool import command. |
56 | | `zfs_pools[].extra_create_options` | `""` | String of extra options to pass to the ZPool create command. |
57 | | `zfs_pools_defaults` | `{}` | Default properties for ZPools. The properties can be overwritten on a per ZPool basis. |
58 | | `zfs_volumes` | `[]` | List of ZFS Volumes. |
59 | | **`zfs_volume[].name`** | | ZFS volume name. |
60 | | `zfs_volume[].properties` | `{}` | Dictionary (key-value pairs) of volume properties to be set. |
61 | | `zfs_volume[].state` | `present` | Whether to create (present), or remove (absent) the volume. |
62 | | `zfs_volumes_properties_defaults` | `volblocksize: 8K`
`volsize: 1G`
`compression: lz4`
`dedup: false`
`sync: standard`
| Default properties for ZFS volumes. The properties can be overwritten on a per Volume basis. |
63 | | `zfs_filesystems` | `[]` | List of ZFS Filesystems. |
64 | | **`zfs_filesystem[].name`** | | ZFS Filesystem name. |
65 | | `zfs_filesystem[].properties` | `{}` | Dictionary (key-value pairs) of filesystem properties to be set. |
66 | | `zfs_filesystem[].state` | `present` | Whether to create (present), or remove (absent) the filesystem. |
67 | | `zfs_filesystems_properties_defaults` | `acltype: posix`
`atime: false`
`canmount: true`
`casesensitivity: sensitive`
`compression: lz4`
`dedup: false`
`normalization: formD`
`setuid: true`
`snapdir: hidden`
`sync: standard`
`utf8only: true`
`xattr: sa`
| Default properties for ZFS filesystems. The properties can be overwritten on a per FS basis. |
68 | | `zfs_zrepl_config` | `{}` | Configuration for ZREPL. See the [official documentation](https://zrepl.github.io/configuration.html) for a list of available parameters. Examples can be found [here](https://github.com/zrepl/zrepl/tree/master/config/samples). |
69 | | `zfs_zrepl_enabled` | `false` | Install and enable [ZREPL](https://github.com/zrepl/zrepl) for replication and snapshots. |
70 | | `zfs_zrepl_redhat_repo_url` | `https://zrepl.cschwarz.com/`
`rpm/repo` | Repository URL used for ZREPL installation. Applies only to RedHat systems. |
71 | | `zfs_zrepl_debian_repo_url` | `https://zrepl.cschwarz.com/`
`apt` | Repository URL used for ZREPL installation. Applies only to Debian systems. |
72 |
73 |
74 | ## Dependencies
75 |
76 | Depends on `community.general` collection.
77 |
78 | ## Example Playbooks
79 |
80 | ### Basic Example
81 |
82 | A simple example to create a ZFS pool with a mirror vdev and two disks. You can test this one out using Vagrant+VirtualBox and the `Vagrantfile` provided in the `examples/` directory. Simply run `vagrant up` in the `examples/` directory and a virtual machine will be spun up with ZFS installed and a pool created.
83 |
84 | > Note: Depending on the mechanism used to install ZFS (DKMS or kmod), it might take some time to compile the kernel module and for the role to finish. This is especially true for the first run.
85 |
86 | ```yaml
87 | - hosts: all
88 | tasks:
89 | # ensure you have enabled backports repository on Debian systems first
90 |
91 | - ansible.builtin.include_role:
92 | name: aisbergg.zfs
93 | vars:
94 | zfs_pools:
95 | - name: pool
96 | vdev: >-
97 | mirror
98 | sdb
99 | sdc
100 | scrub: true
101 | properties:
102 | ashift: 12
103 | filesystem_properties:
104 | mountpoint: /mnt/raid1
105 | compression: lz4
106 | # properties of zfs_filesystems_properties_defaults also apply here
107 | zfs_filesystems:
108 | - name: pool1/vol1
109 | - name: pool1/vol2
110 |
111 | # schedule for ZFS scrubs
112 | zfs_scrub_schedule: monthly
113 | # schedule for TRIM
114 | zfs_trim_schedule: weekly
115 | ```
116 |
117 | ### Advanced Example
118 |
119 | This example is a more advanced setup with multiple pools, volumes and filesystems. It also includes ZREPL configuration for automatic snapshots. It reflects a system setup of mine where I installed the whole system on ZFS. It consists of two pools, `rpool` and `bpool`. `rpool` is the root pool with the system installed on it and `bpool` is the boot pool with the boot partition.
120 |
121 | > Note: Depending on the mechanism used to install ZFS (DKMS or kmod), it might take some time to compile the kernel module and for the role to finish. This is especially true for the first run.
122 | >
123 | ```yaml
124 | - hosts: all
125 | vars:
126 | #
127 | # Service
128 | #
129 |
130 | # generate mount points using systemd
131 | zfs_use_zfs_mount_generator: true
132 | # use zfs_mount_generator but don't invoke ZED (Docker triggers it quite often)
133 | zfs_service_zed_enabled: false
134 |
135 | #
136 | # Configuration
137 | #
138 |
139 | # https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-module-parameters
140 | zfs_kernel_module_parameters:
141 | # use 1/4 of the memory for ZFS ARC
142 | zfs_arc_max: "{{ (ansible_memtotal_mb * 1024**2 * 0.25) | int }}"
143 |
144 | # schedule for ZFS scrubs
145 | zfs_scrub_schedule: monthly
146 | # schedule for TRIM
147 | zfs_trim_schedule: weekly
148 |
149 | _zfs_performance_tuning_default:
150 | # store less metadata (still redundant in mirror setups)
151 | redundant_metadata: most
152 | # use standard behaviour for synchronous writes
153 | sync: standard
154 |
155 | _zfs_performance_tuning_async_only:
156 | # store less metadata (still redundant in mirror setups)
157 | redundant_metadata: most
158 | # turn synchronous writes into asynchronous ones
159 | sync: disabled
160 |
161 | _zfs_performance_tuning_ssd:
162 | # use standard behaviour for synchronous writes
163 | sync: standard
164 | # store less metadata (still redundant in mirror setups)
165 | redundant_metadata: most
166 | # optimize synchronous operations to write directly to disk instead of writing
167 | # to log. On HDDs this decreases the latency, but won't do much on SSDs.
168 | logbias: throughput
169 |
170 | _zfs_filesytems_properties:
171 | canmount: true
172 | snapdir: hidden
173 |
174 | # make ZFS behave like a Linux FS
175 | casesensitivity: sensitive
176 | normalization: formD
177 | utf8only: on
178 | setuid: true
179 | atime: false
180 |
181 | # enable use of ACLs
182 | acltype: posix
183 | xattr: sa
184 |
185 | # compression and deduplication
186 | compression: lz4
187 | dedup: false
188 |
189 | zfs_filesystems_properties_defaults: "{{
190 | _zfs_filesytems_properties | combine(
191 | _zfs_performance_tuning_async_only
192 | )}}"
193 |
194 | _zfs_volumes_properties:
195 | volblocksize: 8K
196 | volsize: 1G
197 | compression: lz4
198 | dedup: false
199 |
200 | # https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html
201 | zfs_volumes_properties_defaults: "{{
202 | _zfs_volumes_properties | combine(
203 | _zfs_performance_tuning_async_only
204 | )}}"
205 |
206 | #
207 | # ZPools
208 | #
209 |
210 | zfs_pools:
211 | - name: rpool
212 | vdev: >-
213 | mirror
214 | r1
215 | r2
216 | scrub: true
217 | properties:
218 | ashift: 12
219 | filesystem_properties:
220 | # don't mount, just supply a base path for sub datasets
221 | canmount: off
222 | mountpoint: /
223 |
224 | - name: bpool
225 | vdev: >-
226 | mirror
227 | {{ _zfs_boot_partition1 }}
228 | {{ _zfs_boot_partition2 }}
229 | scrub: true
230 | properties:
231 | ashift: 12
232 | "feature@async_destroy": enabled
233 | "feature@bookmarks": enabled
234 | "feature@embedded_data": enabled
235 | "feature@empty_bpobj": enabled
236 | "feature@enabled_txg": enabled
237 | "feature@extensible_dataset": enabled
238 | "feature@filesystem_limits": enabled
239 | "feature@hole_birth": enabled
240 | "feature@large_blocks": enabled
241 | "feature@lz4_compress": enabled
242 | "feature@spacemap_histogram": enabled
243 | dont_enable_features: true
244 | filesystem_properties:
245 | canmount: off
246 | mountpoint: /boot
247 |
248 | #
249 | # Datasets
250 | #
251 |
252 | zfs_filesystems:
253 | # root
254 | - name: rpool/ROOT
255 | properties:
256 | canmount: off
257 | mountpoint: none
258 | - name: rpool/ROOT/default
259 | properties:
260 | canmount: noauto
261 | mountpoint: /
262 |
263 | - name: rpool/home
264 | - name: rpool/home/root
265 | properties:
266 | mountpoint: /root
267 | - name: rpool/var/lib/docker
268 | - name: rpool/var/log
269 | - name: rpool/var/spool
270 | - name: rpool/var/cache
271 |
272 | # boot
273 | - name: bpool/default
274 | properties:
275 | mountpoint: /boot
276 |
277 | #
278 | # Automatic Snapthots Using ZREPL
279 | #
280 |
281 | zfs_zrepl_enabled: true
282 | zfs_zrepl_config:
283 | jobs:
284 | - name: storage
285 | type: snap
286 | filesystems: {
287 | "rpool<": true,
288 | "rpool/var<": false,
289 | }
290 | snapshotting:
291 | type: periodic
292 | interval: 12h
293 | prefix: auto_
294 | pruning:
295 | keep:
296 | # prune automatic snapshots
297 | - type: grid
298 | # in first 24 hours keep all snapshots
299 | # in first 7 days 1 snapshot each day
300 | # in first month keep 1 snapshot each week
301 | # discard the rest
302 | # details see: https://zrepl.github.io/configuration/prune.html#policy-grid
303 | grid: 1x24h(keep=all) | 7x1d(keep=1) | 3x7d(keep=1)
304 | regex: "^auto_.*"
305 |
306 | # keep manual snapshots
307 | - type: regex
308 | regex: "^manual_.*"
309 |
310 | roles:
311 | - aisbergg.zfs
312 | ```
313 |
314 | ## License
315 |
316 | MIT
317 |
318 | ## Author Information
319 |
320 | Andre Lehmann (aisberg@posteo.de)
321 |
--------------------------------------------------------------------------------