Configure subvolumes on Btrfs devices.
The motivation behind this role is to automate the tedious task of creating
Btrfs subvolumes for a volume that does not have its root volume mounted.
(subvolid=5
). This includes mounting the root volume, creating subvolumes,
adding subvolumes to /etc/fstab, and unmounting the root volume. This role has
been designed with flexibility in mind, while keeping the configuration overhead
as low as possible.
This role is developed on Codeberg and mirrored to GitHub. Issues and pull requests are welcome on both platforms.
- Create new Btrfs subvolumes.
- Mount and unmount Btrfs subvolumes.
- Add and delete
/etc/fstab
entries. - Configure user and group per subvolume. Recursive change is possible.
- Configure mount options globally or per subvolume.
- Detection and removal of duplicated subvolume mounts and
/etc/fstab
entries (e. g. after a path change). Can be disabled bybtrfssubvol_unmount_unmanaged_mounts
.
- While the mount points can be removed, the subvolumes itself will be preserved.
- This role is currently only tested on Arch Linux, but it should be generic enough to work on every distribution. Please open an issue if you face any problems.
- Unmounting the unmanaged mounts is not always possible, because the targets
might be busy. Therefore, errors on these tasks are ignored by default. If it is
preferred that the role fail in such a case, it is possible to either modify
btrfssubvol_unmount_ignore_errors
or setunmount_ignore_errors
tofalse
in thebtrfssubvol_subvolumes
item definition.
Control Host:
- Collection community.general installed.
Target Host:
- An already existing device formatted to Btrfs.
- Btrfs file system utilities need to be available.
- Users and groups for the subvolumes need to exist.
This role is published to Ansible Galaxy as a standalone role. It can be installed using the Ansible Galaxy CLI:
ansible-galaxy role install lingling9000.btrfssubvol
It is also possible to specify this role in the requirements.yaml
/
requirements.yml
file:
---
roles:
- name: lingling9000.btrfssubvol
version: "v1.0.0" # optionally specify version
And use the Ansible Galaxy CLI to install the definitions from the requirements file:
ansible-galaxy install -r requirements.yaml
Alternatively, the role can also be downloaded as a git repository. The following options are available for doing so:
-
Using
requirements.yaml
/requirements.yml
in the Ansible project directory.version
can be every Git commit-ish object.-
From Codeberg Source:
--- roles: - name: lingling9000.btrfssubvol src: https://codeberg.org/lingling9000/ansible-role-btrfssubvol scm: git version: main
-
Or from GitHub Mirror:
--- roles: - name: lingling9000.btrfssubvol src: https://github.com/lingling9000/ansible-role-btrfssubvole scm: git version: main
-
Afterwards, install it using Ansible Galaxy CLI:
ansible-galaxy install -r requirements.yaml
-
-
Using Git Submodules. In this case, the Ansible project directory must be a git repository.
# Change directory to your Ansible project root. cd ~/path/to/ansible-project git submodule add https://codeberg.org/lingling9000/ansible-role-btrfssubvol roles/lingling9000.btrfssubvol
-
Git Clone in the projects roles folder or a globally accessible roles path (see Ansible Configuration Settings - DEFAULT_ROLES_PATH).
git clone https://codeberg.org/lingling9000/ansible-role-btrfssubvol ~/.ansible/roles/lingling9000.btrfssubvol
This role is designed to run even without configuration. But then it won't do anything except syntax checking.
The subvolumes are defined in the btrfssubvol_subvolumes
list. However, before
defining subvolumes, the UUID of the target device(s) must be obtained. This
is necessary for the role to know on which device(s) the subvolumes should be
created. Obtain the UUID(s) with following shell command:
## Replace /dev/sdXY with your device.
blkid --match-tag UUID --output value /dev/sdXY
Set btrfssubvol_device_uuid
to the obtained UUID. Alternatively, set the UUID
per item in btrfssubvol_subvolumes
using the device_uuid
key. The latter is
useful when managing multiple Btrfs devices.
See Example Playbook for a quick start or read on for an explanation of all the available variables.
None.
Define the subvolumes:
btrfssubvol_subvolumes
(list / elements=dictionary), default[]
. List of dictionaries defining the subvolumes to be managed. See Defining Subvolumes for a tutorial or jump directly to the list of all supported keys.
Defaults for all subvolumes (can be overwritten per subvolume):
btrfssubvol_device_uuid
(string), defaultnone
. UUID of the device, which should contain the Btrfs subvolumes. Is used to determine the right device for mounting and fstab entries.btrfssubvol_mount_parent
(string), default/media
. The parent directory for the subvolume mounts.btrfssubvol_mount_options
(list / elements=string), default["defaults", "compress=zstd", "discard=async", "noatime"]
. Contains a list of mount options used for the subvolume mounts. See Btrfs Mount Options for a detailed explanation.btrfssubvol_unmount_ignore_errors
(boolean), defaulttrue
. If set totrue
, errors will be ignored when unmounting subvolumes. Note that this only affects unmounting the unmanaged mounts. It does not affect to changes of the mount state of the managed mount point.btrfssubvol_unmount_unmanaged_mounts
(boolean), defaulttrue
. When set totrue
, the role attempts to remove mounts of a subvolume that are not managed by this role. This also ensures that there are no remaining mounts after changing the mount path with either thebtrfssubvol_mount_parent
ormount_point
key in thebtrfssubvol_subvolumes
list.
Before digging into subvolume definition, note that this role automatically
appends an @
to the subvolume name on the root volume (subvolid=5
). This is
a common convention for identifying Btrfs subvolumes and is therefore hardcoded
into the role. Also, the following examples assume that
btrfssubvol_device_uuid
is set. Otherwise, the device_uuid
must be set on
each subvolume.
Defining subvolumes requires only the name
key:
btrfssubvol_subvolumes:
- name: documents
- name: photos
The role will then create the subvolumes, create an /etc/fstab
entry, create
the mount point, and mount the subvolumes. The subvolume will be mounted under
the directory specified in btrfssubvol_mount_parent
. Assuming the default is
used, this would be /media/documents
and /media/photos
, respectively. The
mount point can be set per subvolume definition with the mount_point
key.
btrfssubvol_subvolumes:
# - name: documents # Remove the corresponding item to stop managing a subvolume.
- name: photos
mount_point: /srv/nfs/photos
With this definition the photos subvolume will be mounted at /srv/nfs/photos
.
By default, the new subvolume will be owned by the root user and group. This
can be modified by specifying the owner
and group
keys:
btrfssubvol_subvolumes:
- name: photos
owner: alice
group: photo-users
mount_point: /srv/nfs/photos
This will set the group and owner on the mounted subvolume folder. However, this
only affects the subvolume folder itself. If it is desired to change the
permissions on all files in the subvolume, set recurse
to true
. Note that
the users and groups must exist on the system, creating users and groups is
outside of the scope of this role. Refer to the
ansible.builtin.user
and
ansible.builtin.group
modules for user and group creation, respectively.
Mounting behavior can be adjusted with themount_state
key, which accepts all
states of the
ansible.posix.mount
module:
btrfssubvol_subvolumes:
- name: photos
owner: alice
group: photo-users
mount_point: /srv/nfs/photos
mount_state: absent
This configuration removes the /etc/fstab
entry, unmounts the subvolume, and
removes the mount point. It doesn't remove the subvolume itself. Note that the
role will fail if the device can't be unmounted, which happens when the device
is already in use (target is busy). Use absent_from_fstab
to avoid failures,
but remove the entries from /etc/fstab
, so they will be gone after the next
reboot. Another way to remove the mount is to set mount_point
to an empty
string:
btrfssubvol_subvolumes:
- name: photos
owner: alice
group: photo-users
mount_point: ""
# mount_state: absent # has no effect when mount_point is set to ""
unmount_ignore_errors: true # already the default
unmount_unmanaged_mounts: true # already the default
Setting mount_point
to ""
causes the initial mount task to be skipped. With
this configuration, all mounts of the subvolume photos will be considered as
unmanaged and therefore unmounted as long as unmount_unmanaged_mounts
is set
to true
.
Note that it's is not possible to set the permissions when the subvolume isn't
mounted. However, it is good practice to keep the appropriate keys (owner
,
group
, recurse
) defined, as they will take effect the next time the
subvolume is mounted. Otherwise, the subvolume will be set as owned by root on
the next time it is mounted.
Some values are inherited or composed from the defaults set for this role. The
defaults can be overridden on a per-item item in btrfssubvol_subvolumes
. The
following table shows all the options for defining a subvolume:
Key | Default | Description |
---|---|---|
device_uuid (string) |
{{ btrfssubvol_device_uuid }} |
Same as btrfssubvol_device_uuid , but per subvolume. |
group (string) |
- | The group of the subvolume. Will be root if unset. |
name (string, required) |
- | The name of the subvolume. Must be a filename compatible string. This will be the name for the subvolume and mount point. |
mount_options (list) |
{{ btrfssubvol_mount_options }} |
Same as btrfssubvol_mount_options , but per subvolume. |
mount_point (string) |
{{ btrfssubvol_mount_parent }}/{{ name }} |
The mount point of the subvolume. Overrides the default of being a subdirectory of btrfssubvol_mount_parent . |
mount_state (string) |
- | One of ["absent", "absent_from_fstab", "mounted", "present", "unmounted", "remounted", "ephemeral"] . See ansible.posix.mount documentation. Will be mounted if unset. |
owner (string) |
- | The owner of the subvolume. Will be root if unset. |
recurse (boolean) |
- | If the user and group should be modified recursively. Useful for modifying an already existing subvolume. |
unmount_ignore_errors (boolean) |
{{ btrfssubvol_unmount_ignore_errors }} |
Same as btrfssubvol_unmount_ignore_errors , but per subvolume. |
unmount_unmanaged_mounts (boolean) |
{{ btrfssubvol_unmount_unmanaged_mounts }} |
Same as btrfssubvol_unmount_unmanaged_mounts , but per subvolume. |
Complete example with all possible values set:
btrfssubvol_subvolumes:
- name: photos
device_uuid: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
owner: alice
group: photo-users
recurse: true
mount_options:
- defaults
- compress=no
- relatime
mount_point: /srv/nfs/photos
mount_state: remounted
unmount_unmanaged_mounts: true
unmount_ignore_errors: false
To understand and customize the mount options, see the following man pages:
Explanation of the default of btrfssubvol_mount_options
:
defaults
: See link to mount(8) above.compress=zstd
: Btrfs specific mount option to enable compression with zstd. On volumes containing already compressed files (such as image, video, or audio files), it may be desirable to disable compression completely.discard=async
: Using this mount option enables asynchronous execution of the SSD TRIM command, which is a special feature of Btrfs. In the case of full disk encryption, or in some other cases, it is may be desirable to disable this withnodiscard
. Refer to the following resources to make an informed decision:noatime
: This option can improve the performance of a Btrfs volume. Refer to btrfs(5) - NOTES ON GENERIC MOUNT OPTIONS for details.
The subvol=<path>
option is derived from the name field in
btrfssubvol_subvolumes
. Therefore, it neither necessary nor allowed to set
subvol=
or subvolid=
! The btrfssubvol_mount_options
will be merged with
the derived subvol=<path>
option at execution time.
Redefine btrfssubvol_mount_options
to change the options. If this variable
is set to an empty list ([]
), the mount option defaults
is set at execution
time.
None.
To simplify the testing of this role, all tasks are tagged. Check Ansible documentation of tags for reference. Following tags are available:
checks
: Added to all tasks, which perform checks or gather information for subsequent checks. This tag contains a superset of tasks having a tag beginning withchecks-
.checks-syntax
: Added only to tasks performing simple syntax checks on variables, e. g. if they are defined and have the correct data type.checks-host-config
: Added to tasks, which check if host configuration does comply with the configuration in variables.configure
: Added to tasks, which actually perform changes on the target host or prepare for them.
None.
Host configuration (host_vars/somehost.mydomain.tld/main.yaml
):
## Replace with the real UUID obtained from `blkid`!
btrfssubvol_device_uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
btrfssubvol_subvolumes:
# Subvolume backups will be owned by user root and group root.
- name: backups
# Subvolume documents will be owned by user bob and group office. If the
# permission should be changed recursively, set `recurse: true`
- name: documents
owner: bob
group: office
# Subvolume photos is on another device and mounted on a specified mount point.
- name: photos
device_uuid: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
mount_point: /srv/nfs/photos
Playbook (playbook.yaml
):
- name: Configure
hosts: all
tasks:
- name: Include role lingling9000.btrfssubvol
ansible.builtin.include_role:
name: lingling9000.btrfssubvol
In a larger playbook, it may be desirable to check variable syntax at an early stage. This prevents the playbook from aborting in the middle of a run. Two files are provided for this purpose:
- checks-syntax.yaml: Checks the syntax of role variables.
- checks-host-config.yaml: Checks the host
configuration, especially if the users and groups specified in
btrfssubvol_subvolumes
exist.
For example, the following task can be added at the desired position in the playbook to perform the syntax checks separately:
- name: Include lingling9000.btrfssubvol role variable syntax checks
ansible.builtin.include_role:
name: lingling9000.btrfssubvol
tasks_from: checks-syntax.yaml