--- # The 500 GB data disk. # # knots-box does this by hand: /mnt/knots_data holds ~875 GB of blockchain and # appears nowhere in Ansible, so a rebuild of that host would come up with the # chain silently missing. This role does it in code instead. # Does the disk already carry our filesystem? Its label is the identity from # here on. When it is found, the device name below is never read: everything # that follows works from the labelled filesystem, so a stale or reordered # device name cannot break a routine run. blkid exits 2 for "no such label". - name: Look for an existing Monero filesystem by its label ansible.builtin.command: "blkid -L {{ monero_data_label }}" register: monero_labelled_fs changed_when: false failed_when: monero_labelled_fs.rc not in [0, 2] check_mode: false - name: Find the device backing the root filesystem ansible.builtin.command: findmnt -no SOURCE / register: monero_root_source changed_when: false check_mode: false # First run only: the disk is blank and has to be found by name. - name: Prepare a blank data disk when: monero_labelled_fs.rc != 0 block: # The configured name is a /dev/disk/by-id symlink. Resolve it before # comparing it to anything: the root source is a kernel name like # /dev/sdb1, and a symlink path is never a prefix of one, so comparing the # unresolved name would make the root-disk guard below always pass. - name: Resolve the configured data device to its kernel name ansible.builtin.command: "readlink -e {{ monero_data_device }}" register: monero_device_path changed_when: false failed_when: false check_mode: false - name: Check the Monero data device ansible.builtin.stat: path: "{{ monero_device_path.stdout | default('', true) or monero_data_device }}" register: monero_device - name: Find the disk the root filesystem lives on ansible.builtin.command: "lsblk -no PKNAME {{ monero_root_source.stdout }}" register: monero_root_disk changed_when: false check_mode: false # A guard, not ceremony. mkfs on the wrong device is unrecoverable. - name: Assert the data device is a real block device and is not the root disk ansible.builtin.assert: that: - monero_device_path.rc == 0 - monero_device.stat.exists - monero_device.stat.isblk | default(false) - monero_device_path.stdout != '/dev/' ~ (monero_root_disk.stdout | trim) - not monero_root_source.stdout.startswith(monero_device_path.stdout) quiet: true fail_msg: >- {{ monero_data_device }} (-> {{ monero_device_path.stdout | default('nothing', true) }}) is not a usable data device, or it is the disk the root filesystem lives on ({{ monero_root_source.stdout }}). Refusing to touch it. Check `ls -l /dev/disk/by-id` on the host. - name: Read any existing filesystem on the data device ansible.builtin.command: "blkid -o value -s TYPE {{ monero_device_path.stdout }}" register: monero_existing_fs changed_when: false failed_when: false check_mode: false # No monero-data label was found, so a filesystem here is something else. # community.general.filesystem would leave an ext4 of any label alone but # happily format over a disk it does not recognise. Say so out loud instead. - name: Refuse to reformat a disk that already carries a different filesystem ansible.builtin.assert: that: - monero_existing_fs.stdout | trim in ['', monero_data_fstype] quiet: true fail_msg: >- {{ monero_data_device }} already holds a '{{ monero_existing_fs.stdout | trim }}' filesystem, not '{{ monero_data_fstype }}'. Refusing to overwrite it. - name: Create the filesystem on the data device community.general.filesystem: fstype: "{{ monero_data_fstype }}" dev: "{{ monero_device_path.stdout }}" opts: "{{ monero_data_mkfs_opts }}" # By label: on the first run the filesystem was just created with it, on every # other run it was found by it. - name: Read the filesystem UUID ansible.builtin.command: "blkid -o value -s UUID -t LABEL={{ monero_data_label }}" register: monero_fs_uuid changed_when: false failed_when: false check_mode: false # Everything past this point mounts by UUID, so there is nothing sensible to do # without one. On a --check run against a disk that has no filesystem yet this # is expected and unavoidable: check mode did not create the filesystem, so # there is no UUID to read. Say that, rather than leaving a bare blkid rc=2. # Exactly one: two disks with this label (a cloned disk, say) would make the # mount below ambiguous. - name: Assert exactly one UUID was found ansible.builtin.assert: that: - monero_fs_uuid.stdout | trim | length == 36 quiet: true fail_msg: >- Expected exactly one filesystem labelled {{ monero_data_label }}, found: '{{ monero_fs_uuid.stdout | trim }}'. {% if ansible_check_mode %}No UUID is expected under --check when the disk has not been formatted yet - check mode cannot create the filesystem, so it cannot dry-run the mount either. Run without --check once, then --check is meaningful from then on.{% else %}If none: mkfs appears to have failed. If several: more than one disk carries the label. Check `lsblk -f` on the host.{% endif %} # Same owner and mode as the "Own the mounted data directory" task below, and # that is deliberate rather than duplication. Before the disk is mounted this # creates the mount point; afterwards it targets the mounted filesystem and is a # no-op. Giving it root:root 0755 here - the obvious thing to write - made the # two tasks disagree, and each run flipped the directory back and forth. - name: Create the Monero data directory as a mount point ansible.builtin.file: path: "{{ monero_data_dir }}" state: directory owner: "{{ monero_user }}" group: "{{ monero_group }}" mode: '0750' # By UUID, never by a device name. nofail so a missing disk does not drop a headless # VM into an emergency shell - monerod is stopped from starting by # RequiresMountsFor= in its unit instead, which the systemd monitor then reports. # noatime because LMDB touches a lot of pages and none of it wants an atime write. - name: Mount the data disk by UUID ansible.posix.mount: path: "{{ monero_data_dir }}" src: "UUID={{ monero_fs_uuid.stdout | trim }}" fstype: "{{ monero_data_fstype }}" opts: defaults,noatime,nofail state: mounted # AFTER the mount, never before: chowning the mount point first sets permissions # on the directory the mount then hides, and the blockchain lands on a root-owned # filesystem that monerod cannot write to. - name: Own the mounted data directory ansible.builtin.file: path: "{{ monero_data_dir }}" state: directory owner: "{{ monero_user }}" group: "{{ monero_group }}" mode: '0750' - name: Confirm the data directory is a mount point and not the root filesystem ansible.builtin.command: "findmnt -no SOURCE --target {{ monero_data_dir }}" register: monero_mounted_source changed_when: false check_mode: false - name: Assert the blockchain will land on the data disk ansible.builtin.assert: that: - monero_mounted_source.stdout | trim != monero_root_source.stdout | trim quiet: true fail_msg: >- {{ monero_data_dir }} resolves to {{ monero_mounted_source.stdout | trim }}, which is the root filesystem. The 500 GB disk is not mounted and a full sync would fill the 10 GB root disk.