diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5c98df4b270313154fa4769f01501ca591f3ebd9..b6d83e10917cd2e76941474849b121a459b5efee 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,24 +16,30 @@ default:
     - source /opt/molecule/bin/activate
     - molecule destroy --scenario-name default
 
+variables:
+  SCENARIO: "default"
+  ANSIBLE_VAULT_PASSWORD_FILE: "${CI_PROJECT_DIR}/molecule/${SCENARIO}/../../../lza_repair.pass"
+  ANSIBLE_FORCE_COLOR: 'true'
+  PY_COLORS: '1'
+
 test-job:
   stage: test
   tags:
     - "shell"
   script:
     # make sure that Ansible Vaults are present and can be decrypted
-    - echo "${VAULT_REPAIR}" > ../../../lza_repair.pass
-    - export ANSIBLE_VAULT_IDENTITY_LIST="../../../lza_repair.pass"
+    - echo "${VAULT_REPAIR}" > ${ANSIBLE_VAULT_PASSWORD_FILE}
+    - export ANSIBLE_VAULT_IDENTITY_LIST="${ANSIBLE_VAULT_PASSWORD_FILE}"
     - rm -rf ../ansible_vaults/
     - git clone https://gitlab+deploy-token-25:${VAULT_ACCESS_TOKEN}@git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git ../ansible_vaults/; \
     # run Molecule tests
-    - molecule syntax --scenario-name default
+    - molecule syntax --scenario-name ${SCENARIO}
     # We cannot use `molecule lint` anymore because:
     # - https://github.com/ansible-community/molecule/pull/3802 "Remove lint command"
     # - https://github.com/ansible-community/molecule/discussions/3825#discussioncomment-4908366
     - yamllint --strict --format colored ./
     - ansible-lint --format full --profile production --strict --force-color ./
-    - molecule create --scenario-name default
-    - molecule converge --scenario-name default
-    - molecule idempotence --scenario-name default
-    # - molecule verify --scenario-name default
+    - molecule create --scenario-name ${SCENARIO}
+    - molecule converge --scenario-name ${SCENARIO}
+    - molecule idempotence --scenario-name ${SCENARIO}
+    # - molecule verify --scenario-name ${SCENARIO}
diff --git a/meta/main.yml b/meta/main.yml
index 4e9ec98089c37b9fc9fde79ca5afd951bf419db2..c62de675493daa811785db6226d58365a0d66c9b 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -8,7 +8,7 @@ galaxy_info:
   # If the issue tracker for your role is not on github, uncomment the next line and provide a value issue_tracker_url: http://example.com/issue/tracker Some suggested licenses: - BSD
   # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
   license: public domain
-  min_ansible_version: "2.4"
+  min_ansible_version: "2.10"
   # If this a Container Enabled role, provide the minimum Ansible Container version. min_ansible_container_version: Optionally specify the branch Galaxy will use when accessing the GitHub repo
   # for this role. During role install, if no tags are available, Galaxy will use this branch. During import Galaxy will access files on this branch. If Travis integration is configured, only
   # notifications for this branch will be accepted. Otherwise, in all cases, the repo's default branch (usually master) will be used. github_branch:
@@ -23,6 +23,7 @@ galaxy_info:
       versions:
         - "buster"
         - "bullseye"
+        - "bookworm"
   galaxy_tags: []
     # List tags for your role here, one per line. A tag is a keyword that describes and categorizes the role. Users find roles by searching for tags. Be sure to remove the '[]' above, if you
     # add tags to this list.
diff --git a/molecule/resources/playbooks/prepare.yml b/molecule/resources/playbooks/prepare.yml
index b67f4d4b61e8ee91baa129d5108b275684cd33da..078471a22f900806c3b11060830029c573541c29 100644
--- a/molecule/resources/playbooks/prepare.yml
+++ b/molecule/resources/playbooks/prepare.yml
@@ -9,24 +9,24 @@
     - name: configure additional package repositories for Debian
       when: ansible_os_family == "Debian"
       block:
-        - name: install GPG
-          ansible.builtin.apt:
-            name: "gnupg"
-            state: latest
-            update_cache: true
-          become: true
-        - name: add GPG key for SLUB Debian repository
-          ansible.builtin.apt_key:
-            url: "https://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key"
-            state: present
-          become: true
-        - name: add repo URL to sources.list
-          ansible.builtin.apt_repository:
-            repo: "deb https://sdvdebianrepo.slub-dresden.de/deb-repository bookworm main"
-            state: present
-            update_cache: true
-            mode: "0644"
-          become: true
+      - name: install GPG
+        ansible.builtin.apt:
+          name: "gnupg"
+          state: latest
+          update_cache: true
+        become: true
+      - name: add custom repo for SLUB's custom Debian repo
+        ansible.builtin.deb822_repository:
+          architectures: "amd64"
+          components: "main"
+          enabled: true
+          name: "slub"
+          pdiffs: true
+          signed_by: "https://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key"
+          suites: "{{ ansible_lsb.codename }}"
+          uris: "https://sdvdebianrepo.slub-dresden.de/deb-repository"
+        notify: update package cache
+        become: true
     - name: install tooling before running the actual tests, so we're not bound by the fixed versions
       ansible.builtin.apt:
         name: [
@@ -38,25 +38,54 @@
       become: true
 
     - name: configure additional package repositories for RedHat
-      when: ansible_os_family == "RedHat"
       block:
-        - name: add custom repositories
-          ansible.builtin.yum_repository:
-            name: "{{ item.name }}"
-            description: "{{ item.description }}"
-            baseurl: "{{ item.baseurl }}"
-            gpgcheck: "{{ item.gpgcheck | default('true') }}"
-            gpgkey: "{{ item.gpgkey | default(omit) }}"
-          loop:
-            - name: "epel"
-              description: EPEL YUM repo
-              baseurl: "https://download.fedoraproject.org/pub/epel/{{ ansible_distribution_major_version }}/x86_64/"
-              gpgkey: "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{{ ansible_distribution_major_version }}"
-            - name: "slub"
-              description: SLUB YUM repo
-              baseurl: "http://sdvrhelrepo.slub-dresden.de/"
-              gpgcheck: "false"
-        - name: remove legacy repo configuration to avoid double configuration for SLUB repo
-          ansible.builtin.file:
-            path: "/etc/yum.repos.d/SLUB.repo"
-            state: absent
+      - name: add custom repositories
+        ansible.builtin.yum_repository:
+          name: "{{ item.name }}"
+          description: "{{ item.description }}"
+          baseurl: "{{ item.baseurl }}"
+          gpgcheck: "{{ item.gpgcheck | default('true') }}"
+          gpgkey: "{{ item.gpgkey | default(omit) }}"
+        loop:
+          - name: "epel"
+            description: EPEL YUM repo
+            baseurl: "https://download.fedoraproject.org/pub/epel/{{ ansible_distribution_major_version }}/x86_64/"
+            gpgkey: "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{{ ansible_distribution_major_version }}"
+          - name: "slub"
+            description: SLUB YUM repo
+            baseurl: "https://sdvrhelrepo.slub-dresden.de/"
+            gpgcheck: "false"
+        notify: update package cache
+      - name: remove legacy repo configuration to avoid double configuration for SLUB repo
+        ansible.builtin.file:
+          path: "/etc/yum.repos.d/SLUB.repo"
+          state: absent
+        notify: update package cache
+      when: ansible_os_family == "RedHat"
+
+    # Ansible roles can install a multitude of firewall rules, some of which
+    # will lock us out of our Molecule test VM if we don't take precautions.
+    # As Molecule itself uses SSH just like Ansible, we need to open port
+    # tcp/22 to the private /24 subnet that Vagrant uses when provisioning the
+    # VM. As we don't know for sure what the address for this subnet is and it
+    # can change across servers/platforms, we gather this information
+    # dynamically and filter it through `ipaddr` to get the address of the
+    # whole subnet. The rule is inserted right on top of the list to make sure
+    # we always get access.
+    - name: add firewall rule to allow access from Molecule host into testing VM
+      ansible.builtin.iptables:
+        action: insert
+        rule_num: 1
+        chain: INPUT
+        comment: "molecule access"
+        jump: "ACCEPT"
+        protocol: tcp
+        source: "{{ ansible_default_ipv4.address | ansible.utils.ipaddr('network') }}/24"
+        destination_port: "22"
+      become: true
+
+  handlers:
+    - name: update package cache
+      ansible.builtin.package:
+        update_cache: true
+      become: true
diff --git a/site.yml b/site.yml
index e6956ba6f6e071ca5a6ba296e283e9bc83cd232f..8476b4747b81d2d3e909367ae685c616e668c6a0 100644
--- a/site.yml
+++ b/site.yml
@@ -4,9 +4,9 @@
   pre_tasks:
     - name: Verify that the installed version of Ansible meets this playbook's version requirements.
       ansible.builtin.assert:
-        that: "ansible_version.full is version_compare('2.5', '>=')"
+        that: "ansible_version.full is version_compare('2.10', '>=')"
         msg: >
-          "You must update Ansible to at least 2.5 to use this playbook."
+          "You must update Ansible to at least 2.10 to use this playbook."
   # Collect facts from remote system? Possible values: true, false
   gather_facts: true
   # Gather only certain subsects of facts. Ansible supports network, hardware, virtual, facter, ohai as subset.
diff --git a/tasks/install-packages.yml b/tasks/install-packages.yml
index fc4d041c4158cd4c75900520b3b2faa5016aa181..988ba3269d31f3810ac4289930426340262b6896 100644
--- a/tasks/install-packages.yml
+++ b/tasks/install-packages.yml
@@ -3,6 +3,7 @@
   ansible.builtin.apt:
     state: present
     name: [
+      'gpg',
       'logrotate',
       'lsb-release',
       'nfs-common',
@@ -15,5 +16,5 @@
       'tar',
       'unzip'
     ]
-    update_cache: "yes"
+    update_cache: true
     autoremove: true
diff --git a/tasks/install-repair-tools.yml b/tasks/install-repair-tools.yml
index 2fb838f470868f920bd85afb49828e8cce7cc18f..d689a7c4425cc8c8deb24db9a07981e107fa5752 100644
--- a/tasks/install-repair-tools.yml
+++ b/tasks/install-repair-tools.yml
@@ -1,20 +1,28 @@
 ---
-- name: configure Debian repositories
-  when: "ansible_facts['distribution'] == 'Debian'"
+- name: add custom repo for SLUB's custom Debian repo
+  ansible.builtin.deb822_repository:
+    architectures: "amd64"
+    components: "main"
+    enabled: true
+    name: "slub"
+    pdiffs: true
+    signed_by: "{{ vault_debrepo_url }}deb-repository/pub.gpg.key"
+    suites: "{{ ansible_lsb.codename }}"
+    uris: "{{ vault_debrepo_url }}deb-repository"
+  when: "ansible_os_family == 'Debian'"
+
+- name: remove legacy apt-keys/sources
+  when: "ansible_os_family == 'Debian'"
   block:
     - name: öffentlichen Schlüssel hinzufügen (sonst muss bei jeder Installation eine Warnmeldung bestätigt werden)
       ansible.builtin.apt_key:
         url: "{{ vault_debrepo_url }}deb-repository/pub.gpg.key"
-        state: present
-      when: ansible_distribution_major_version == '11'
+        state: absent
       tags: [apt, aptkey]
     - name: SLUB-lokales Debian-Repository für Installation der SubApp in /etc/apt/sources.list.d/ eintragen
       ansible.builtin.apt_repository:
         repo: "deb {{ vault_debrepo_url }}deb-repository bullseye main"
-        state: present
-        update_cache: "yes"
-        mode: "0644"
-      when: ansible_distribution_major_version == '11'
+        state: absent
       tags: [apt]
 
 - name: install repairtool dependencies from local Debian repo server (if we're running in prod)