diff --git a/.ansible-lint b/.ansible-lint
index 9c9323e8df825eddaf10a9188e0fd26583c5dde0..2076d8dd087e7c91a8fcecb2e25551b68e68f0fa 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -6,7 +6,7 @@
 # and not relative to the CWD of execution. CLI arguments passed to the --exclude
 # option will be parsed relative to the CWD of execution.
 exclude_paths:
-  - .cache/ # implicit unless exclude_paths is defined in config
+  - .cache/    # implicit unless exclude_paths is defined in config
   - .git/
   - .githooks/
   - backups/
@@ -15,24 +15,19 @@ exclude_paths:
 # verbosity: 1
 
 # Mock modules or roles in order to pass ansible-playbook --syntax-check
-#mock_modules:
-#  - zuul_return
-#  # note the foo.bar is invalid as being neither a module or a collection
-#  - fake_namespace.fake_collection.fake_module
-#  - fake_namespace.fake_collection.fake_module.fake_submodule
-#mock_roles:
-#  - mocked_role
-#  - author.role_name # old standalone galaxy role
-#  - fake_namespace.fake_collection.fake_role # role within a collection
+# mock_modules:
+#   - zuul_return
+#   # note the foo.bar is invalid as being neither a module or a collection
+#   - fake_namespace.fake_collection.fake_module
+#   - fake_namespace.fake_collection.fake_module.fake_submodule
+# mock_roles:
+#   - mocked_role
+#   - author.role_name # old standalone galaxy role
+#   - fake_namespace.fake_collection.fake_role # role within a collection
 
 # Enable checking of loop variable prefixes in roles
 loop_var_prefix: "{role}_"
 
-# Enforce variable names to follow pattern below, in addition to Ansible own
-# requirements, like avoiding python identifiers. To disable add `var-naming`
-# to skip_list.
-var_naming_pattern: "^[a-z_][a-z0-9_]*$"
-
 use_default_rules: true
 # Load custom rules from this specific folder
 # rulesdir:
@@ -46,9 +41,9 @@ skip_list:
 # Any rule that has the 'opt-in' tag will not be loaded unless its 'id' is
 # mentioned in the enable_list:
 enable_list:
-  - empty-string-compare # opt-in
-  - no-log-password # opt-in
-  - no-same-owner # opt-in
+  # - empty-string-compare    # opt-in
+  - no-log-password         # opt-in
+  - no-same-owner           # opt-in
   # add yaml here if you want to avoid ignoring yaml checks when yamllint
   # library is missing. Normally its absence just skips using that rule.
   - yaml
@@ -60,19 +55,19 @@ enable_list:
 warn_list:
   - skip_this_tag
   - git-latest
-  - experimental # experimental is included in the implicit list
+  - experimental    # experimental is included in the implicit list
   # - role-name
 
 # Offline mode disables installation of requirements.yml
 offline: false
 
 # Define required Ansible's variables to satisfy syntax check
-#extra_vars:
-#  foo: bar
-#  multiline_string_variable: |
-#    line1
-#    line2
-#  complex_variable: ":{;\t$()"
+# extra_vars:
+#   foo: bar
+#   multiline_string_variable: |
+#     line1
+#     line2
+#   complex_variable: ":{;\t$()"
 
 # Uncomment to enforce action validation with tasks, usually is not
 # needed as Ansible syntax check also covers it.
diff --git a/.config/molecule/config.yml b/.config/molecule/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ece7ff6dd671a6a55ec8f7e9e498c0310e52d4cc
--- /dev/null
+++ b/.config/molecule/config.yml
@@ -0,0 +1,2 @@
+---
+prerun: false
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d072439c1f198e68f9b135b857a68a5501889a2a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,33 @@
+---
+# A pipeline is composed of independent jobs that run scripts, grouped into stages.
+# Stages run in sequential order, but jobs within stages run in parallel.
+#
+# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
+
+stages:          # List of stages for jobs, and their order of execution
+  - test
+
+default:
+  before_script:
+    - source /opt/molecule/bin/activate
+    - ansible --version
+    - molecule --version
+
+test-job:
+  stage: test
+  tags:
+    - "shell"
+  script:
+    # make sure that Ansible Vaults are present and can be decrypted
+    - echo "${VAULT_INSTALL_COMMON}" > ../lza_install_common.pass
+    - export ANSIBLE_VAULT_PASSWORD_FILE=../lza_install_common.pass
+    - 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 lint --scenario-name default
+    - molecule create --scenario-name default
+    - molecule converge --scenario-name default
+    - molecule idempotence --scenario-name default
+    # - molecule verify --scenario-name default
+    - molecule destroy --scenario-name default
diff --git a/ansible.cfg b/ansible.cfg
index adc3c7e1a038f14e25b3684f98914173d46df44e..e98a2af86591c589fcf3252584258c0f55078cc5 100644
--- a/ansible.cfg
+++ b/ansible.cfg
@@ -1,7 +1,8 @@
 [defaults]
 # If set, configures the path to the Vault password file as an alternative to
 # specifying --vault-password-file on the command line.
-vault_identity_list = ../lza_install_common.pass, ../slub_osquery.pass
+# vault_identity_list = ../lza_install_common.pass, ../slub_osquery.pass
+vault_identity_list = ../lza_install_common.pass
 
 # Path to default inventory file
 # Administrators can override this by using the "-i <inventoryfile>" CLI
@@ -24,7 +25,7 @@ nocows = 1
 
 # Custom role path that guarantees roles are always found, no matter where a
 # user checks them out.
-roles_path = ../:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
+roles_path = ./:../:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
 
 # Toggle to control displaying skipped task/host entries in a task in the
 # default callback.
diff --git a/handlers/main.yml b/handlers/main.yml
index 48c66fc6401a04429c00379402442a37faf29d09..e975c979b522f45ec0d0c2d5ff1980c8f3c97e4e 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -2,64 +2,67 @@
 - name: save iptables rules (Debian)
   block:
     - name: Ordner für iptables-Config erstellen
-      file:
+      ansible.builtin.file:
         path: "/etc/iptables"
         state: directory
         owner: "root"
         group: "root"
-        mode: 0755
+        mode: "0755"
       listen: "save iptables rules"
     - name: install netfilter-persistent to be able to save iptables rules
-      apt:
+      ansible.builtin.apt:
         name: netfilter-persistent
         state: present
       listen: "save iptables rules"
+    # we exclude this task from being linted for "no-changed-when", because handlers only ever run if there's a change triggered by a task
     - name: save iptables rules
-      command: 'netfilter-persistent save'
+      ansible.builtin.command: 'netfilter-persistent save'      # noqa no-changed-when
       listen: "save iptables rules"
   when: ansible_os_family == "Debian"
 
 - name: save iptables rules (RedHat)
   block:
     - name: make sure iptables config file exists
-      file:
+      ansible.builtin.file:
         path: "/etc/sysconfig/iptables"
         state: touch
         owner: "root"
         group: "root"
-        mode: 0600
+        mode: "0600"
       listen: "save iptables rules"
+    # we exclude this task from being linted for "no-changed-when", because handlers only ever run if there's a change triggered by a task
     - name: save rules
-      command: /usr/sbin/iptables-save        # noqa 303
+      ansible.builtin.command: /usr/sbin/iptables-save        # noqa no-changed-when
       listen: "save iptables rules"
   when: ansible_os_family == "RedHat"
 
 - name: restart exim
-  systemd:
+  ansible.builtin.systemd:
     name: "exim4"
-    state: restarted
+    state: reloaded
     enabled: true
 
 - name: restart postfix
-  service:
+  ansible.builtin.service:
     name: "postfix"
-    state: restarted
+    state: reloaded
 
 - name: restart sshd
-  systemd:
+  ansible.builtin.systemd:
     name: "sshd"
-    state: restarted
+    state: reloaded
 
 - name: reload journald configuration
-  systemd:
+  ansible.builtin.systemd:
     name: "systemd-journald"
     state: restarted
 
 - name: restart logrotate.service
-  systemd:
+  ansible.builtin.systemd:
     name: "logrotate.service"
-    state: restarted
+    state: reloaded
   when: ansible_os_family == "Debian"
+  changed_when: false
 
 - name: udev-Regel bekannt machen
-  command: "udevadm control --reload"
+  ansible.builtin.command: "udevadm control --reload"
diff --git a/meta/main.yml b/meta/main.yml
index 15082aa00a4ea42bafd62220558aad13a1576516..5f04002a2dbf94d805d1e4cfa9d8350fddfdc140 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -1,17 +1,25 @@
 ---
 galaxy_info:
   author: Jörg Sachse
-  description: role to deploy a base install of Debian for use in the SLUBarchiv digital preservation repository
   company: SLUB Dresden
-  # 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
+  description: role to deploy a base install of Debian for use in the SLUBarchiv digital preservation repository
+  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.
+    #
+    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
+    #       Maximum 20 tags per role.
+  # issue_tracker_url: "https://example.com/"
+    # 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
   license: public domain
-  min_ansible_version: 2.5
-  # 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:
-  #
-  # Provide a list of supported platforms, and for each platform a list of versions. If you don't wish to enumerate all versions for a particular platform, use 'all'. To view available 
+    # Some suggested licenses: - BSD
+    # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
+  min_ansible_version: "2.5"
+    # 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:
+  namespace: "slub"
+  # Provide a list of supported platforms, and for each platform a list of versions. If you don't wish to enumerate all versions for a particular platform, use 'all'. To view available
   # platforms and versions (or releases), visit: https://galaxy.ansible.com/api/v1/platforms/
   #
   # platforms: - name: Fedora
@@ -19,16 +27,10 @@ galaxy_info:
   platforms:
     - name: Debian
       versions:
-        - 9
-        - 10
-    - name: RedHat
+        - "buster"
+        - "bullseye"
+    - name: EL
       versions:
-        - 6
-        - 7
-  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.
-    #
-    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
-    #       Maximum 20 tags per role.
+        - "7"
+        - "8"
 dependencies: []
diff --git a/molecule/README.md b/molecule/README.md
deleted file mode 100644
index 33a7eb56b8f65d94165fbc812cf391d629d8fd2a..0000000000000000000000000000000000000000
--- a/molecule/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Testing with Molecule
-
-## Prerequisites
-
-In order to be able to use the tests, you need to have some software packages installed. You may need sudo privileges for some of these operations.
-
-        ### install VirtualBox
-        # do NOT use distribution packages
-        # process documented at https://www.virtualbox.org/wiki/Linux_Downloads
-        #
-        # add repository URL
-        sudo echo "deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian stretch contrib" > /etc/apt/sources.d/virtualbox.list
-        # add GPG key
-        wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
-        # update sources
-        sudo apt update
-        # install VirtualBox
-        sudo apt-get install virtualbox-6.1
-
-        ### install Vagrant
-        # do NOT use distribution packages
-        # 
-        # download Debian package from Hashicorp
-        wget https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.deb
-        # install package
-        sudo dpkg -i vagrant_2.2.9_x86_64.deb
-
-        ### install Molecule et. al.
-        # prepare directories
-        mkdir ~/python-envs/ && cd ~/python-env/
-        # create Python Virtual Environment with Python3 interpreter (Python2 is deprecated!)
-        virtualenv -p python3 molecule-env
-        # enter the Virtual Environment in your current shell (other shells will be unaffected)
-        source molecule-env/bin/activate
-        # install packages
-        pip3 install molecule ansible testinfra ansible-lint molecule-vagrant molecule-docker
-
-        # leave the Virtual Environment only when you're done
-        deactivate
-
-You can find suitable documentation at the respective vendors' websites.
-* [Vagrant Installation Guide](https://www.vagrantup.com/docs/installation/)
-* [VirtualBox Installation Guide](https://www.virtualbox.org/wiki/Downloads)
-* [Molecule Installation Guide](https://molecule.readthedocs.io/en/stable/installation.html)
-
-## Initialising a new Molecule scenario
-
-If you have already created a role without the Molecule test framework or you want to add test scenarios, you can use:
-	molecule init scenario --scenario-name <my_scenario> --driver [azure|delegated|docker|ec2|gce|linode|lxc|lxd|openstack|vagrant] --verifier-name [ansible|testinfra]
-
-If you need any help with the options, please use:
-	molecule init role --help
-
-## Running Tests
-
-Molecule helps with creating a test infrastructure, running tests against it and removing the test infrastructure afterwards.
-
-Various test environments are separated into so-called "scenarios" that can be based on different OSses, drivers, verifiers or might just differ in a minor detail. 
-
-In the simplest configuration, the `molecule/` directory only contains one `default/` directory that contains the default scenario. This scenario is run if no other scenario is chosen using the `-s` CLI option.
-
-This is the basic usage of Molecule:
-	# create test infrastructure
-	cd <role_directory>
-	molecule create
-	# run playbooks against test infrastructure
-	molecule converge
-	# run Testinfra tests
-	molecule verify
-	# remove test infrastructure
-	molecule destroy
-
-	# run all steps at once:
-	molecule test
-
-It has proven helpful to use Vagrant to create a snapshot of the VM after the creation phase has completed.
-	# First, get UUID of the VM
-	vagrant global-status
-	# Then, create the snapshot
-	vagrant snapshot save <uuid> <snapshot_name>
-	# To restore the snapshot, use
-	vagrant snapshot restore <uuid> <snapshot_name>
-	# And to remove the snapshot, use
-	vagrant snapshot delete <uuid> <snapshot_name>
-
diff --git a/molecule/default b/molecule/default
new file mode 120000
index 0000000000000000000000000000000000000000..0c538fbdbd9a2c57dbed3f3361450fdc87b66af6
--- /dev/null
+++ b/molecule/default
@@ -0,0 +1 @@
+./virtualbox_debian11
\ No newline at end of file
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
deleted file mode 100644
index 5214569efeff9b1498aa015fa58a6313bb4600dc..0000000000000000000000000000000000000000
--- a/molecule/default/converge.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - { role: ansible_lza_install_common, become: true }
-    - { role: ansible_slub_osquery, become: true }
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
deleted file mode 100644
index b6b98a6ee30f58de778e08974ccd4dceb06deb9a..0000000000000000000000000000000000000000
--- a/molecule/default/molecule.yml
+++ /dev/null
@@ -1,31 +0,0 @@
----
-dependency:
-  name: galaxy
-driver:
-  name: vagrant
-  provider:
-    name: virtualbox
-lint: |
-  set -e
-  yamllint .
-  ansible-lint -x formatting
-  flake8 --ignore=E501
-platforms:
-  - name: molecule-install-common-debian
-    box: debian/buster64
-    memory: 512
-    cpus: 1
-provisioner:
-  name: ansible
-  log: true
-  config_options:
-    defaults:
-      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
-      vault_identity_list: "@$HOME/.ansible/roles/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass, @$HOME/.ansible/roles/lza_ingest.pass, @$HOME/.ansible/roles/slub_osquery.pass"
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  options:
-    v: 1
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
deleted file mode 100644
index d903f567ea8ed2309c8359152b80adcf44855d1b..0000000000000000000000000000000000000000
--- a/molecule/default/prepare.yml
+++ /dev/null
@@ -1,40 +0,0 @@
----
-- name: Prepare
-  hosts: all
-  gather_facts: true
-  pre_tasks:
-    - name: include vars
-      include_vars: "../../../ansible_vaults/molecule_prepare/{{ item }}"
-      loop:
-        - "prepare.vault"
-    - name: Install python for Ansible
-      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
-      become: true
-      changed_when: false
-    - name: create users (as deployed in production)
-      user:
-        name: "{{ item.name }}"
-        uid: "{{ item.uid }}"
-        create_home: "yes"
-        shell: "/bin/bash"
-      loop: "{{ vault_molecule_users | flatten(levels=1) }}"
-      become: true
-    - name: add nonfree repos
-      apt_repository:
-        repo: "deb http://ftp2.de.debian.org/debian/ buster main non-free contrib"
-        state: present
-        update-cache: "yes"
-      become: true
-    - name: Install required packages
-      apt:
-        name: [
-          'aptitude',
-          'gpg',
-          'less',
-          'libuser'
-        ]
-        state: present
-      become: true
-  roles:
-    - {role: ansible_lza_install_common, become: true}
-    - {role: ansible_lza_server_hardening, become: true}
diff --git a/molecule/default/tests/conftest.py b/molecule/default/tests/conftest.py
deleted file mode 100644
index ba0f1e81239226ea76950de710f5ec996831b7bc..0000000000000000000000000000000000000000
--- a/molecule/default/tests/conftest.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""PyTest Fixtures."""
-from __future__ import absolute_import
-import os
-import pytest
-
-
-def pytest_runtest_setup(item):
-    """Run tests only when under molecule with testinfra installed."""
-    try:
-        import testinfra
-    except ImportError:
-        pytest.skip("Test requires testinfra", allow_module_level=True)
-    if "MOLECULE_INVENTORY_FILE" in os.environ:
-        pytest.testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-            os.environ["MOLECULE_INVENTORY_FILE"]
-        ).get_hosts("all")
-    else:
-        pytest.skip(
-            "Test should run only from inside molecule.", allow_module_level=True
-        )
diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py
deleted file mode 100644
index d6fe02d58fe26d39b8475da564d890dc44203c66..0000000000000000000000000000000000000000
--- a/molecule/default/tests/test_default.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# import os
-
-# import testinfra.utils.ansible_runner
-#
-# testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-#    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
-
-
-# def test_configure_network(host):
-
-
-# def test_configure_package_repositories(host):
-
-
-# f test_uninstall_tsm_client(host):
-
-
-# f test_install_packages(host):
-
-
-def test_install_hotadd_scripts(host):
-    hotaddtools = [
-        "hot-add-cpu.sh",
-        "hot-add-ram.sh"
-    ]
-    for item in hotaddtools:
-        h = host.file("/usr/local/bin/" + item)
-        assert h.exists
-        assert h.is_file
-        assert h.user == "root"
-        assert h.group == "root"
-        assert h.mode == 0o700
-
-
-def test_install_lzip_tools(host):
-    lziptools = [
-        "lzipcat",
-        "lzipless"
-    ]
-    for item in lziptools:
-        t = host.file("/usr/local/bin/" + item)
-        assert t.exists
-        assert t.is_file
-        assert t.user == "root"
-        assert t.group == "root"
-        assert t.mode == 0o755
-
-
-# def test_prepare_skel(host):
-
-
-# def test_configure_autoupdate(host):
-
-
-# def test_configure_tmux(host):
-
-
-# def test_configure_logrotate(host):
-
-
-# def test_create_users_groups(host):
-
-
-# def test_configure_ssh_keys(host):
-
-
-# def test_remove_users_keys(host):
-
-
-# def test_configure_syslog_server_logging(host):
-
-
-# def test_configure_persistent_journald_logging(host):
-
-
-# def test_install_needrestart(host):
-
-
-# def test_install_checkmk_plugins(host):
-
-
-# def test_configure_root_shell(host):
-
-
-# def test_configure_sudoers(host):
-
-
-# def test_configure_exim(host):
diff --git a/molecule/redhat/INSTALL.rst b/molecule/redhat/INSTALL.rst
deleted file mode 100644
index 4f44b6745beb4e4a1ca19e93e42ccc8e98919d41..0000000000000000000000000000000000000000
--- a/molecule/redhat/INSTALL.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-*******
-Vagrant driver installation guide
-*******
-
-Requirements
-============
-
-* Vagrant
-* Virtualbox, Parallels, VMware Fusion, VMware Workstation or VMware Desktop
-
-Install
-=======
-
-Please refer to the `Virtual environment`_ documentation for installation best
-practices. If not using a virtual environment, please consider passing the
-widely recommended `'--user' flag`_ when invoking ``pip``.
-
-.. _Virtual environment: https://virtualenv.pypa.io/en/latest/
-.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site
-
-.. code-block:: bash
-
-    $ pip install 'molecule[vagrant]'
diff --git a/molecule/redhat/molecule.yml b/molecule/redhat/molecule.yml
deleted file mode 100644
index 3158bdf046ef94b19f044256a0520a53b920df77..0000000000000000000000000000000000000000
--- a/molecule/redhat/molecule.yml
+++ /dev/null
@@ -1,31 +0,0 @@
----
-dependency:
-  name: galaxy
-driver:
-  name: vagrant
-  provider:
-    name: virtualbox
-lint: |
-  set -e
-  yamllint .
-  ansible-lint -x formatting
-  flake8 --ignore=E501
-platforms:
-  - name: molecule-install-common-redhat
-    box: centos/7
-    memory: 512
-    cpus: 1
-provisioner:
-  name: ansible
-  log: true
-  config_options:
-    defaults:
-      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
-      vault_identity_list: "@$HOME/.ansible/roles/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass, @$HOME/.ansible/roles/lza_ingest.pass, @$HOME/.ansible/roles/slub_osquery.pass"
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  options:
-    v: 1
diff --git a/molecule/redhat/playbook.yml b/molecule/redhat/playbook.yml
deleted file mode 100644
index a75a790ae173a7da17387c68a6673ada295979e5..0000000000000000000000000000000000000000
--- a/molecule/redhat/playbook.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - role: ansible_lza_install_common
diff --git a/molecule/redhat/prepare.yml b/molecule/redhat/prepare.yml
deleted file mode 100644
index eb3bf4a0e33ee9fbf072391a62c48e03c2fddd31..0000000000000000000000000000000000000000
--- a/molecule/redhat/prepare.yml
+++ /dev/null
@@ -1,40 +0,0 @@
----
-- name: Prepare
-  hosts: all
-  gather_facts: true
-  pre_tasks:
-    - name: include vars
-      include_vars: "../../../ansible_vaults/molecule_prepare/{{ item }}"
-      loop:
-        - "prepare.vault"
-    - name: Install python for Ansible
-      raw: test -e /usr/bin/python || (yum -y update && yum install -y python-minimal)
-      become: true
-      changed_when: false
-    - name: create users (as deployed in production)
-      user:
-        name: "{{ item.name }}"
-        uid: "{{ item.uid }}"
-        create_home: "yes"
-        shell: "/bin/bash"
-      loop: "{{ vault_molecule_users | flatten(levels=1) }}"
-      become: true
-    - name: add nonfree repos
-      apt_repository:
-        repo: "deb http://ftp2.de.debian.org/debian/ buster main non-free contrib"
-        state: present
-        update-cache: "yes"
-      become: true
-    - name: Install required packages
-      apt:
-        name: [
-          'aptitude',
-          'gpg',
-          'less',
-          'libuser'
-        ]
-        state: present
-      become: true
-  roles:
-    - {role: ansible_lza_install_common, become: true}
-    - {role: ansible_lza_server_hardening, become: true}
diff --git a/molecule/redhat/tests/test_default.py b/molecule/redhat/tests/test_default.py
deleted file mode 100644
index eedd64a1d3d5c5f97f055b53c500cee079b02711..0000000000000000000000000000000000000000
--- a/molecule/redhat/tests/test_default.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import os
-
-import testinfra.utils.ansible_runner
-
-testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
-
-
-def test_hosts_file(host):
-    f = host.file('/etc/hosts')
-
-    assert f.exists
-    assert f.user == 'root'
-    assert f.group == 'root'
diff --git a/molecule/resources/playbooks/Dockerfile b/molecule/resources/playbooks/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..da596e7ddc242169a6f04b49be07cd09f4dd9a48
--- /dev/null
+++ b/molecule/resources/playbooks/Dockerfile
@@ -0,0 +1,20 @@
+FROM debian:stable-slim
+
+RUN adduser lza;
+
+### configure SLUB Debian Repository
+RUN apt-get update; \
+    apt-get install -y --no-install-recommends gnupg wget git python3 ansible sudo; \
+    wget -O - http://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key | apt-key add - ; \
+    echo "deb http://sdvdebianrepo.slub-dresden.de/deb-repository bullseye main" > /etc/apt/sources.list.d/slub.list; \
+    apt-get update;
+    #apt-get -y --no-install-recommends install python3-pip python3-virtualenv;
+
+RUN echo "lza     ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/lza-user
+
+#RUN mkdir /opt/venv/ && cd /opt/venv/; \
+#    virtualenv -p python3 molecule; \
+#    . /opt/venv/molecule/bin/activate; \
+#    pip3 install ansible molecule molecule-docker;
+
+USER lza
diff --git a/molecule/default/INSTALL.rst b/molecule/resources/playbooks/INSTALL.rst
similarity index 84%
rename from molecule/default/INSTALL.rst
rename to molecule/resources/playbooks/INSTALL.rst
index 4f44b6745beb4e4a1ca19e93e42ccc8e98919d41..0c4bf5c7eb43b1b428b1824a62f8fb8a213f3600 100644
--- a/molecule/default/INSTALL.rst
+++ b/molecule/resources/playbooks/INSTALL.rst
@@ -1,6 +1,6 @@
-*******
+*********************************
 Vagrant driver installation guide
-*******
+*********************************
 
 Requirements
 ============
@@ -20,4 +20,4 @@ widely recommended `'--user' flag`_ when invoking ``pip``.
 
 .. code-block:: bash
 
-    $ pip install 'molecule[vagrant]'
+    $ pip install 'molecule_vagrant'
diff --git a/molecule/resources/playbooks/README.md b/molecule/resources/playbooks/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c91883011699e3b0ec08bad9bcc19d201ef6331
--- /dev/null
+++ b/molecule/resources/playbooks/README.md
@@ -0,0 +1,3 @@
+This drectory contains shared playbooks and a shared Dockerfile.
+
+Visit https://molecule.readthedocs.io/en/latest/examples.html#sharing-across-scenarios for details on sharing playbooks, tests etc. across multiple scenarios.
diff --git a/molecule/resources/playbooks/converge.yml b/molecule/resources/playbooks/converge.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0b7cbdb7a8c30e58c3ef1810cac84f175791278
--- /dev/null
+++ b/molecule/resources/playbooks/converge.yml
@@ -0,0 +1,11 @@
+---
+- name: Converge
+  hosts: all
+  pre_tasks:
+    - name: update apt cache
+      ansible.builtin.apt:
+        update_cache: true
+        upgrade: dist
+      become: true
+  roles:
+    - {role: "ansible_lza_install_common", become: true}
diff --git a/molecule/resources/playbooks/prepare.yml b/molecule/resources/playbooks/prepare.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a20ecff01683308e1b648dd4fbb9ff4703258b76
--- /dev/null
+++ b/molecule/resources/playbooks/prepare.yml
@@ -0,0 +1,22 @@
+---
+- name: Prepare
+  hosts: "*"
+  tasks:
+    - 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 bullseye main"
+        state: present
+        update_cache: true
+        mode: "0644"
+      become: true
diff --git a/molecule/resources/playbooks/verify.yml b/molecule/resources/playbooks/verify.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e707420ab5c87edfa59c7805ce4534ff1b387177
--- /dev/null
+++ b/molecule/resources/playbooks/verify.yml
@@ -0,0 +1,10 @@
+---
+# This is an example playbook to execute Ansible tests.
+
+- name: Verify
+  hosts: all
+  gather_facts: false
+  tasks:
+  - name: Example assertion
+    ansible.builtin.assert:
+      that: true
diff --git a/molecule/virtualbox_centos7/molecule.yml b/molecule/virtualbox_centos7/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..50b58170372ab9bb3c68846e87feaa4feb72f4c6
--- /dev/null
+++ b/molecule/virtualbox_centos7/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner
+    box: centos/7
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: true
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      # vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_install_common.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/molecule/virtualbox_debian10/molecule.yml b/molecule/virtualbox_debian10/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e43c1152d40db1ee30b1d21c7d8ca1237529912e
--- /dev/null
+++ b/molecule/virtualbox_debian10/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner
+    box: debian/buster64
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: true
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      # vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_install_common.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/molecule/virtualbox_debian11/molecule.yml b/molecule/virtualbox_debian11/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..280948c8887361f4fd199ccbdca10b9778746165
--- /dev/null
+++ b/molecule/virtualbox_debian11/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module,package-latest
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner
+    box: debian/bullseye64
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: true
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      # vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_install_common.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/molecule/virtualbox_rocky8/molecule.yml b/molecule/virtualbox_rocky8/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1022bd3e1f25e28ce0ad63d73b5944fbe97687ef
--- /dev/null
+++ b/molecule/virtualbox_rocky8/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner
+    box: rockylinux/8
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: true
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      # vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_install_common.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/requirements.yml b/requirements.yml
deleted file mode 100644
index 97bfe9c0c57a4ee9c76049915a074e8d946b1f73..0000000000000000000000000000000000000000
--- a/requirements.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-# https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-multiple-roles-from-a-file
-- src: git+https://git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git
-  scm: git
diff --git a/site.yml b/site.yml
index 26fb3930e92f6cf4b3d8291129e19d3dbc0e5d33..a461d4a962aa1d651cb9960d1ad45f8228fd0ea3 100644
--- a/site.yml
+++ b/site.yml
@@ -25,4 +25,3 @@
 
   roles:
     - { role: ansible_lza_install_common, become: true }
-    - { role: ansible_slub_osquery, become: true }
diff --git a/tasks/backup_ssh_hostkeys.yml b/tasks/backup_ssh_hostkeys.yml
index 81e29742e55df3bdd52aeeab9c2484c88d3fa3d1..0494fb014be621034883f3a9f048e27145369a09 100644
--- a/tasks/backup_ssh_hostkeys.yml
+++ b/tasks/backup_ssh_hostkeys.yml
@@ -2,11 +2,12 @@
 # https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html
 # https://docs.ansible.com/ansible/latest/user_guide/playbooks_lookups.html
 - name: Backupordner erzeugen
-  local_action:
-    module: file
+  ansible.builtin.file:
     path: "{{ playbook_dir }}/backups/{{ ansible_date_time.iso8601 }}/"
     state: directory
+    mode: "0755"
   become_user: "{{ lookup('env', 'USER') }}"
+  delegate_to: localhost
 
 # Whenever servers are newly installed, the installer creates new SSH host keys
 # for that server. However, this can lead to client-side warnings about possible
@@ -14,7 +15,7 @@
 # the server keys to be sure we can deploy them later.
 # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/fetch_module.html
 - name: Server-SSH-Schlüssel sichern
-  fetch:
+  ansible.builtin.fetch:
     src: "/etc/ssh/{{ item }}"
     dest: "{{ playbook_dir }}/backups/{{ ansible_date_time.iso8601 }}/"
   loop:
diff --git a/tasks/configure_autoupdate.yml b/tasks/configure_autoupdate.yml
index 2ead9225c462d23481bae1a181ff0f654f0198c9..f0c3dbb0ee51b7a151c45353a96996df41c16034 100644
--- a/tasks/configure_autoupdate.yml
+++ b/tasks/configure_autoupdate.yml
@@ -2,11 +2,11 @@
 - name: remove apt-cron autoupdate (Debian)
   block:
     - name: Uninstall autoupdate packages (Debian)
-      apt:
+      ansible.builtin.apt:
         name: 'cron-apt'
         state: absent
     - name: cron-apt Konfigurationsdateien entfernen
-      file:
+      ansible.builtin.file:
         path: "{{ item }}"
         state: absent
       loop:
@@ -19,22 +19,24 @@
 - name: Install & configure unattended-upgrades (Debian/Ubuntu)
   block:
     - name: install unattended-upgrades
-      apt:
+      ansible.builtin.apt:
         name: "unattended-upgrades"
         state: latest
-    - name: 
-      lineinfile:
+    - name: configure unattended upgrade conditions
+      ansible.builtin.lineinfile:
         path: "/etc/apt/apt.conf.d/10periodic"
         create: true
         line: "{{ item }}"
+        mode: "0644"
       loop:
         - 'APT::Periodic::Update-Package-Lists "1";'
         - 'APT::Periodic::Unattended-Upgrade "1";'
-    - name:
-      lineinfile:
+    - name: configure unattended upgrade mail settings
+      ansible.builtin.lineinfile:
         path: "/etc/apt/apt.conf.d/90unattended-upgrades-mail"
         create: true
         line: "{{ item }}"
+        mode: "0644"
       loop:
         - 'Unattended-Upgrade::Mail "root";'
         - 'Unattended-Upgrade::MailOnlyOnError "true";'
@@ -43,7 +45,7 @@
     # delete it.
     # (refers to https://git.slub-dresden.de/slub-referat-2-4/sdvuda10/-/commit/4c265c91ffef7775cad8abbe69da35bc398622e9)
     - name: install all upgrades, not only security patches
-      file:
+      ansible.builtin.file:
         path: "/etc/apt/apt.conf.d/51only-security-upgrades"
         state: absent
   when: ansible_os_family == "Debian"
@@ -52,16 +54,16 @@
 - name: Install & configurate autoupdate (RedHat)
   block:
     - name: include vars yum-cron.yml
-      include_vars: "yum-cron.yml"
+      ansible.builtin.include_vars: "yum-cron.yml"
 
     - name: Install autoupdate packages (RedHat)
-      yum:
+      ansible.builtin.yum:
         name: [
           'yum-cron'
         ]
         state: present
     - name: configure yum-cron
-      template:
+      ansible.builtin.template:
         src: "etc/yum/yum-cron.conf.j2"
         dest: "/etc/yum/yum-cron.conf"
         owner: "root"
diff --git a/tasks/configure_bash.yml b/tasks/configure_bash.yml
index 64b353797950b9c6afd3068c311d6f28f4d57023..e94bd6477fdaccda0894a00bd8546fc0ecf3a7c5 100644
--- a/tasks/configure_bash.yml
+++ b/tasks/configure_bash.yml
@@ -1,13 +1,13 @@
 ---
 - name: remove .bashrc skel block (systems now get systemwide bash prompt config, so we don't need to provide users with a separate one)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/skel/.bashrc"
     state: absent
     backup: "no"
     create: "yes"
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     marker: "# {mark} ANSIBLE MANAGED BLOCK - CUSTOM BASH PROMPT"
     block: |
       # custom bash prompt
@@ -29,35 +29,35 @@
     insertafter: EOF
 
 - name: remove .bash_aliases skel file (systems now get a systemwide alias config, so we don't need to provide users with a separate one)
-  file:
+  ansible.builtin.file:
     path: "/etc/skel/.bash_aliases"
     state: absent
 
 # see http://vimdoc.sourceforge.net/htmldoc/starting.html#system-vimrc
 - name: remove .vimrc skel file (systems now get a systemwide vimrc config, so we don't need to provide users with a separate one)
-  file:
+  ansible.builtin.file:
     path: "/etc/skel/.vimrc"
     state: absent
 
 - name: deploy Bash configuration (custom prompt, aliases)
-  copy:
+  ansible.builtin.copy:
     src: "{{ item }}"
     dest: "/{{ item }}"
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
   loop:
     - "etc/bash.promptrc"
     - "etc/profile.d/bash_aliases.sh"
     - "etc/profile.d/bash_functions.sh"
 
 - name: configure systemwide bashrc (Bash completion, import custom prompt)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/bash.bashrc"
     backup: false
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     create: true
     marker: "# {mark} ANSIBLE MANAGED BLOCK - CUSTOM BASH PROMPT"
     block: |
diff --git a/tasks/configure_exim.yml b/tasks/configure_exim.yml
index 223828ee7b09aa14def9fb620e40e15aeabacd07..bf3b5befa682d51920638cb6c290a0c83e579f2c 100644
--- a/tasks/configure_exim.yml
+++ b/tasks/configure_exim.yml
@@ -1,45 +1,51 @@
 ---
 - name: Configure exim4
-  template:
+  ansible.builtin.template:
     src: 'update-exim4.conf.conf'
     dest: "/etc/exim4/update-exim4.conf.conf"
+    mode: "0644"
   notify: restart exim
 
 # The plain text file /etc/mailname is used by the Mail Transfer Agent (i.e. mail server) to know its own hostname.
 # Debian policy says: If your package needs to know what hostname to use on (for example) outgoing news and mail messages which are generated locally, you should use the file /etc/mailname. It will contain the portion after the username and @ (at) sign for email addresses of users on the machine (followed by a newline).
 # (https://wiki.debian.org/EtcMailName)
 - name: Update mailname
-  copy:
+  ansible.builtin.copy:
     content: "{{ exim4_sendonly_fqdn }}"
     dest: "/etc/mailname"
+    mode: "0644"
   notify: restart exim
 
 - name: Define email aliases
-  lineinfile:
+  ansible.builtin.lineinfile:
     dest: "/etc/aliases"
     regexp: "{{ item.regexp }}"
     line: "{{ item.line }}"
-  with_items: "{{ exim4_sendonly_email_aliases }}"
+    mode: "0644"
+  loop: "{{ exim4_sendonly_email_aliases }}"
   when: exim4_sendonly_email_aliases|length
   notify: restart exim
 
 - name: Define email addresses
-  template:
+  ansible.builtin.template:
     src: "email-addresses.j2"
     dest: "/etc/email-addresses"
+    mode: "0644"
   when: exim4_sendonly_email_addresses|length
   notify: restart exim
 
 - name: Set auth for relay host
-  template:
+  ansible.builtin.template:
     src: "passwd.client"
     # dest: "/etc/{{ exim_name }}/passwd.client"
     dest: "/etc/exim4/passwd.client"
+    mode: "0644"
   notify: restart exim
 
 - name: Enable TLS
-  template:
+  ansible.builtin.template:
     src: "exim4.conf.localmacros"
     dest: "/etc/exim4/exim4.conf.localmacros"
+    mode: "0644"
   when: exim4_sendonly_enable_tls
   notify: restart exim
diff --git a/tasks/configure_glances.yml b/tasks/configure_glances.yml
index 8ef1e15a799d162bc9fd82a2f6070eff0d2ce165..b9d1ad34b850ff7b5a6690849a8115f0a87c064a 100644
--- a/tasks/configure_glances.yml
+++ b/tasks/configure_glances.yml
@@ -1,7 +1,27 @@
 ---
+- name: find out if Glances Service is enabled
+  ansible.builtin.command: systemctl is-enabled glances.service
+  register: glances_enabled
+  changed_when: false
+  failed_when:
+    - glances_enabled.stdout != "enabled"
+    - glances_enabled.stdout != "disabled"
+
+# this is idempotent
 - name: stop Glances (web-)server
-  service:
-    name: "glances"
+  ansible.builtin.service:
+    name: "glances.service"
     state: stopped
+  when:
+    - ansible_os_family == "Debian"
+    - glances_enabled.stdout != "disabled"
+
+# this is NOT idempotent, so it needs the "changed: false" stanza
+- name: disable Glances (web-)server
+  ansible.builtin.service:
+    name: "glances.service"
     enabled: false
-  when: ansible_os_family == "Debian"
+  when:
+    - ansible_os_family == "Debian"
+    - glances_enabled.stdout != "disabled"
+  changed_when: false
diff --git a/tasks/configure_htop.yml b/tasks/configure_htop.yml
index b150c67aa35d398a81719f9125d2db70e24e1303..27b3d205ebf4aadcf0f0cf47e03e3e4f0a82079c 100644
--- a/tasks/configure_htop.yml
+++ b/tasks/configure_htop.yml
@@ -1,5 +1,6 @@
 ---
 - name: Konfigurationsdateien einspielen - HTOP
-  copy:
+  ansible.builtin.copy:
     src: "etc/htoprc"
     dest: "/etc/htoprc"
+    mode: "0644"
diff --git a/tasks/configure_logrotate.yml b/tasks/configure_logrotate.yml
index 1eb7f16241f64714a6b2966024613ae652ef5276..07bd4b97a03c857f542b24c802ac3008750dabd7 100644
--- a/tasks/configure_logrotate.yml
+++ b/tasks/configure_logrotate.yml
@@ -1,15 +1,17 @@
 ---
+# copy module modifies parent directory permissions, when file or directory is copied with owner and group different than root. It is also not idempotent and changes on subsequent runs.
 - name: rollout default logrotate config
-  copy:
+  ansible.builtin.copy:
     src: "etc/logrotate.conf"
     dest: "/etc/logrotate.conf"
     owner: "root"
     group: "root"
     mode: "0644"
   notify: restart logrotate.service
+  tags: [molecule-idempotence-notest]
 
 - name: set custom compression algorithm
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/logrotate.conf"
     create: "yes"
     owner: "root"
@@ -36,7 +38,7 @@
   notify: restart logrotate.service
 
 - name: create logrotate config if necessary
-  copy:
+  ansible.builtin.copy:
     src: "etc/logrotate.d/wtmp"
     dest: "/etc/logrotate.d/wtmp"
     owner: "root"
diff --git a/tasks/configure_motd.yml b/tasks/configure_motd.yml
index 18cadf486d772ebd1eee4f33f57e79c47999ba36..d9b7d0a5a7cf816d3b9bae41bd30e2cf89cc6a30 100644
--- a/tasks/configure_motd.yml
+++ b/tasks/configure_motd.yml
@@ -1,5 +1,6 @@
 ---
 - name: motd Script einspielen
-  copy:
+  ansible.builtin.copy:
     src: "etc/motd"
     dest: "/etc/motd"
+    mode: "0644"
diff --git a/tasks/configure_ntp.yml b/tasks/configure_ntp.yml
index 38e6cad890bbb20cce9f4cc7951620f767dfbcab..a9b6294d82ff25f2886c3d0654dd7b6faaba3ee4 100644
--- a/tasks/configure_ntp.yml
+++ b/tasks/configure_ntp.yml
@@ -1,8 +1,10 @@
 ---
+# copy module modifies parent directory permissions, when file or directory is copied with owner and group different than root. It is also not idempotent and changes on subsequent runs.
 - name: configure NTP
-  copy:
+  ansible.builtin.copy:
     src: "etc/ntp.conf"
     dest: "/etc/ntp.conf"
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
+  tags: [molecule-idempotence-notest]
diff --git a/tasks/configure_package_repositories.yml b/tasks/configure_package_repositories.yml
index e8909bb4e2716922ab4b5f070e40adff02977dd9..080841b9d7681f9ecd145203a4bff34020f29892 100644
--- a/tasks/configure_package_repositories.yml
+++ b/tasks/configure_package_repositories.yml
@@ -2,12 +2,12 @@
 - name: configure Debian repositories
   block:
     - name: öffentlichen Schlüssel hinzufügen (sonst muss bei jeder Installation eine Warnmeldung bestätigt werden)
-      apt_key:
+      ansible.builtin.apt_key:
         url: "{{ vault_debrepo_url }}deb-repository/pub.gpg.key"
       tags: [aptkey]
 
     - name: SLUB-lokales Debian-Repository für Installation der SubApp in /etc/apt/sources.list.d/ eintragen
-      apt_repository:
+      ansible.builtin.apt_repository:
         repo: "deb {{ vault_debrepo_url }}deb-repository {{ ansible_facts['distribution_release'] }} main"
         state: present
         update_cache: "yes"
@@ -15,7 +15,7 @@
   when: "ansible_facts['distribution'] == 'Debian'"
 
 - name: add custom repositories
-  yum_repository:
+  ansible.builtin.yum_repository:
     name: "{{ item.name }}"
     description: "{{ item.description }}"
     baseurl: "{{ item.baseurl }}"
@@ -33,7 +33,7 @@
   when: ansible_os_family == "RedHat"
 
 - name: remove legacy repo configuration to avoid double configuration for SLUB repo
-  file:
+  ansible.builtin.file:
     path: "/etc/yum.repos.d/SLUB.repo"
     state: absent
   when: ansible_os_family == "RedHat"
diff --git a/tasks/configure_persistent_journald_logging.yml b/tasks/configure_persistent_journald_logging.yml
index 71031d13071cd81cfc34a6bf1e6ae1930ac1016f..383367cc2892ac2ce425ff22439b53da6f8df61e 100644
--- a/tasks/configure_persistent_journald_logging.yml
+++ b/tasks/configure_persistent_journald_logging.yml
@@ -1,39 +1,26 @@
 ---
-# enable persistent systemd journalctl logging
-# Documentation: zless /usr/share/doc/systemd/README.Debian.gz
-- block:
-  - name: create log directory
-    file:
-      path: "/var/log/journal"
-      state: directory
-      owner: "root"
-      group: "systemd-journal"
+# Documentation: https://www.freedesktop.org/software/systemd/man/journald.conf.html
+- name: configure journald
+  ansible.builtin.blockinfile:
+    path: "/etc/systemd/journald.conf.d/persistence.conf"
+    owner: "root"
+    group: "root"
+    mode: "0644"
+    create: "yes"
+    state: present
+    block: |
+      # If "persistent", data will be stored preferably on disk, i.e. below the /var/log/journal hierarchy (which is created if needed), with a fallback to /run/log/journal (which is created if needed), during early boot and if the disk is not writable.
+      Storage=persistent
 
-  - name: link directory name to systemd
-    command: systemd-tmpfiles --create --prefix /var/log/journal
+      # If enabled (the default), data objects that shall be stored in the journal and are larger than the default threshold of 512 bytes are compressed before they are written to the file system.
+      Compress=true
 
-  # Documentation: https://www.freedesktop.org/software/systemd/man/journald.conf.html
-  - name: configure journald
-    blockinfile:
-      path: "/etc/systemd/journald.conf.d/persistence.conf"
-      owner: "root"
-      group: "root"
-      mode: "0644"
-      create: "yes"
-      state: present
-      block: |
-        # If "persistent", data will be stored preferably on disk, i.e. below the /var/log/journal hierarchy (which is created if needed), with a fallback to /run/log/journal (which is created if needed), during early boot and if the disk is not writable.
-        Storage=persistent
+      # Controls how much disk space the journal may use up at most. (default: 10%)
+      SystemMaxUse=1G
 
-        # If enabled (the default), data objects that shall be stored in the journal and are larger than the default threshold of 512 bytes are compressed before they are written to the file system.
-        Compress=true
-
-        # Controls how much disk space the journal may use up at most. (default: 10%)
-        SystemMaxUse=1G
-
-        # Controls how much disk space systemd-journald shall leave free for other uses. (default: 15%)
-        # THIS DOES NOT WORK, HOWEVER: SystemKeepFree=15%
-        SystemKeepFree=350M
-    notify:
-      - reload journald configuration
+      # Controls how much disk space systemd-journald shall leave free for other uses. (default: 15%)
+      # THIS DOES NOT WORK, HOWEVER: SystemKeepFree=15%
+      SystemKeepFree=350M
+  notify:
+    - reload journald configuration
   when: ansible_facts.service_mgr == "systemd"
diff --git a/tasks/configure_postfix.yml b/tasks/configure_postfix.yml
index 454c9f5576f8f7f4e203631d68540a5f8e0de027..20a8af96286194a741320ff54936cc4fbd3e36ba 100644
--- a/tasks/configure_postfix.yml
+++ b/tasks/configure_postfix.yml
@@ -1,6 +1,6 @@
 ---
 - name: configure Postfix
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/postfix/main.cf"
     owner: "root"
     group: "root"
diff --git a/tasks/configure_root_shell.yml b/tasks/configure_root_shell.yml
index b7a6ecbe9bf7b8dd82dd42b8b6828d59f602d421..33cb7238ab432d0f2fae766d65fabebe3697007b 100644
--- a/tasks/configure_root_shell.yml
+++ b/tasks/configure_root_shell.yml
@@ -1,9 +1,9 @@
 ---
 - name: insert lines into root .bashrc
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/root/.bashrc"
     create: "yes"
-    mode: 0755
+    mode: "0755"
     state: absent
     block: |
       # farbige Warnung für root shell
diff --git a/tasks/configure_sar.yml b/tasks/configure_sar.yml
index 4e738aeff501ca69ff2d2e82d687961a2e605f54..34b5de0a74d68af064b680efd122aa623c1d46ba 100644
--- a/tasks/configure_sar.yml
+++ b/tasks/configure_sar.yml
@@ -1,16 +1,16 @@
 ---
 - name: sar konfigurieren
-  lineinfile:
+  ansible.builtin.lineinfile:
     dest: "/etc/default/sysstat"
     regexp: '^ENABLED="false"'
     line: 'ENABLED="true"'
   when: ansible_distribution == "Debian"
 
 - name: sysstat aktivieren (RedHat 7)
-  systemd:
+  ansible.builtin.systemd:
     state: started
     name: "sysstat"
-    enabled: "yes"
+    enabled: true
   when:
     - ansible_os_family == "RedHat"
     - ansible_distribution_major_version == "7"
diff --git a/tasks/configure_ssh_keys.yml b/tasks/configure_ssh_keys.yml
index 9ca75918da2b786b37676e5b6e4adcd4340e128f..b3a91bf63134f82014dec6b510d6b8a8f6aadc23 100644
--- a/tasks/configure_ssh_keys.yml
+++ b/tasks/configure_ssh_keys.yml
@@ -1,27 +1,27 @@
 ---
 - name: gültige SSH-Keys für Public-Key Authentication einspielen (HUMAN_USERS)
-  authorized_key:
+  ansible.builtin.authorized_key:
     user: "{{ item.key }}"
     comment: "{{ item.value.ssh_comment_current | default('') }}"
     key: "{{ item.value.ssh_key_current | default('') }}"
     state: present
-  with_dict: "{{ human_users }}"
+  loop: "{{ human_users | dict2items }}"
   when: item.value.state != "absent"
 
 - name: gültige SSH-Keys für Public-Key Authentication einspielen (ROBOT_USERS)
-  authorized_key:
+  ansible.builtin.authorized_key:
     user: "{{ item.key }}"
     comment: "{{ item.value.ssh_comment_current | default('') }}"
     key: "{{ item.value.ssh_key_current | default('') }}"
     state: present
-  with_dict: "{{ robot_users }}"
+  loop: "{{ robot_users | dict2items }}"
   when: item.value.state != "absent"
 
 - name: alle gültigen SSH-Keys zum Installationsuser hinzufügen
-  authorized_key:
+  ansible.builtin.authorized_key:
     user: "{{ vault_install_username }}"
     comment: "{{ item.value.ssh_comment_current | default('') }}"
     key: "{{ item.value.ssh_key_current | default('') }}"
     state: present
-  with_dict: "{{ human_users }}"
+  loop: "{{ human_users | dict2items }}"
   when: item.value.state != "absent"
diff --git a/tasks/configure_ssh_login.yml b/tasks/configure_ssh_login.yml
index 15a8afe35c61d8e900f645ba52e8a8615c32735b..639680c6a22079ffb951622cddda9c3640229f6a 100644
--- a/tasks/configure_ssh_login.yml
+++ b/tasks/configure_ssh_login.yml
@@ -1,8 +1,8 @@
 ---
 - name: Konfigurationsdateien einspielen - SSH-Login
-  copy:
+  ansible.builtin.copy:
     src: "etc/profile.d/ssh-loginscreen.sh"
     dest: "/etc/profile.d/ssh-loginscreen.sh"
     owner: "root"
     group: "root"
-    mode: 0755
+    mode: "0755"
diff --git a/tasks/configure_sudoers.yml b/tasks/configure_sudoers.yml
index e60ae85f17ef82888fb1d66ecf617da889cd34eb..70e759c09538f79cdc4125c481dedbdd4d6fd584 100644
--- a/tasks/configure_sudoers.yml
+++ b/tasks/configure_sudoers.yml
@@ -1,12 +1,12 @@
 ---
 - name: sudoers konfigurieren
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/sudoers.d/group_{{ vault_admin_groupname }}"
     backup: "no"
     create: "yes"
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     state: present
     marker: "### {mark} ANSIBLE MANAGED BLOCK"
     block: |
diff --git a/tasks/configure_swap.yml b/tasks/configure_swap.yml
index 94b4dda6b3b3828bd4baa7a3cf690abb5f5f27ef..3a6d08872023c1e673e4b38a7fc52f69a9924b31 100644
--- a/tasks/configure_swap.yml
+++ b/tasks/configure_swap.yml
@@ -1,12 +1,23 @@
 ---
+- name: check if swap is active
+  ansible.builtin.command: swapon -s
+  register: swap_active
+  changed_when: false
+
 # https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-strings
 - name: switch off swap (immediate result for running server, not reboot persistent)
-  command: swapoff -a
-  when: ansible_hostname is not search("validate")
+  ansible.builtin.command: swapoff -va
+  when:
+    - ansible_hostname is not search("validate")
+    - swap_active.stdout == ""
+  register: disable_swap
+  changed_when: disable_swap.stdout not in "swapoff LABEL=swap"
 
 - name: switch off swap (no result for running server, reboot persistent)
-  mount:
+  ansible.posix.mount:
     path: "none"
     fstype: "swap"
     state: "absent"
-  when: ansible_hostname is not search("validate")
+  when:
+    - ansible_hostname is not search("validate")
+    - swap_active.stdout == ""
diff --git a/tasks/configure_syslog_server_logging.yml b/tasks/configure_syslog_server_logging.yml
index 5cd96acb66f6a3ce33d1cedc4982fd6942459c43..a6b1a9a96a0b10c4eff41878897ef435f75637b4 100644
--- a/tasks/configure_syslog_server_logging.yml
+++ b/tasks/configure_syslog_server_logging.yml
@@ -1,13 +1,13 @@
 ---
 - name: Logging auf Syslog-Server einrichten
-  lineinfile:
+  ansible.builtin.lineinfile:
     dest: "/etc/rsyslog.conf"
     line: "{{ item }}"
   loop:
     - '$template GRAYLOGRFC5424,"<%PRI%>%PROTOCOL-VERSION% %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n"'
     - '*.* @{{ vault_syslog_url }}:{{ vault_syslog_port }};RSYSLOG_SyslogProtocol23Format'
 - name: Logging auf Syslog-Server einrichten
-  lineinfile:
+  ansible.builtin.lineinfile:
     dest: "/etc/rsyslog.conf"
     line: "{{ item }}"
     state: absent
diff --git a/tasks/configure_tmux.yml b/tasks/configure_tmux.yml
index 29554d25af912184de30f62d81708812b387d5bb..ed396781f3d6645e8506a9b5f447fdca3c5a8326 100644
--- a/tasks/configure_tmux.yml
+++ b/tasks/configure_tmux.yml
@@ -1,12 +1,12 @@
 ---
 - name: Enable mouse mode in tmux, increase scrollback buffer to 10.000 (newer OSses)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/tmux.conf"
     state: present
     create: true
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     block: |
       set -g mouse on
       set -g history-limit 10000
@@ -14,13 +14,13 @@
     (ansible_os_family == "Debian")
 
 - name: Enable mouse mode in tmux, increase scrollback buffer to 10.000 (older OSses)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/tmux.conf"
     state: present
     create: true
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     block: |
       # Allow mouse dragging to resize panes
       set -g mouse-resize-pane on
diff --git a/tasks/configure_vim.yml b/tasks/configure_vim.yml
index 70d2969f91dc1733d4651d5053bfb47109148396..d7ebf5b41086f40cc024ad7bfa68c375eaea2206 100644
--- a/tasks/configure_vim.yml
+++ b/tasks/configure_vim.yml
@@ -1,9 +1,9 @@
 ---
 # see http://vimdoc.sourceforge.net/htmldoc/starting.html#system-vimrc
 - name: install systemwide vimrc config
-  template:
-    src: "etc/vimrc.j2"
-    dest: "/etc/vimrc"
+  ansible.builtin.template:
+    src: "etc/vim/vimrc.local.j2"
+    dest: "/etc/vimrc.local"
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
diff --git a/tasks/create_users_groups.yml b/tasks/create_users_groups.yml
index b04e9a04e0a5945e41ec26472952814c23a31c1d..85603de7f0f097dce3be86cada838eb46a7e7235 100644
--- a/tasks/create_users_groups.yml
+++ b/tasks/create_users_groups.yml
@@ -1,34 +1,32 @@
 ---
 - name: create sysadmin groups
-  group:
+  ansible.builtin.group:
     name: "{{ item.name }}"
     state: "{{ item.state | default('present') }}"
     system: "{{ item.system | default('false') }}"
   loop: "{{ vault_groups | flatten(levels=1) }}"
 
 - name: create individual primary user group (HUMAN USERS)
-  group:
+  ansible.builtin.group:
     name: "{{ item.key }}"
     state: "{{ item.value.state }}"
     gid: "{{ item.value.uid }}"
-  with_dict: "{{ human_users }}"
+  loop: "{{ human_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
 
 - name: create individual primary user group (ROBOT USERS)
-  group:
+  ansible.builtin.group:
     name: "{{ item.key }}"
     state: "{{ item.value.state }}"
     gid: "{{ item.value.uid }}"
-  with_dict: "{{ robot_users }}"
+  loop: "{{ robot_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
 
 # do NOT run this before skel configuration has been rolled out!
 - name: create HUMAN users
-  user:
+  ansible.builtin.user:
     comment: "{{ item.value.comment }}"
-    create_home: "yes"
+    create_home: true
     group: "{{ item.key }}"
     groups: "{{ item.value.groups }}"
     home: "/home/{{ item.key }}/"
@@ -38,34 +36,21 @@
     state: "{{ item.value.state | default('present') }}"
     uid: "{{ item.value.uid }}"
     update_password: on_create
-  with_dict: "{{ human_users }}"
+  loop: "{{ human_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
+  no_log: true
 
 - name: lock HUMAN users
-  user:
+  ansible.builtin.user:
     name: "{{ item.key }}"
     password_lock: "{{ item.value.password_lock }}"
-  with_dict: "{{ human_users }}"
+  loop: "{{ human_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
-
-- name: copy fresh default bash configs for HUMAN users from /etc/skel/
-  copy:
-    src: "/etc/skel/.bash_aliases"
-    dest: "/home/{{ item.key }}/.bash_aliases"
-    remote_src: true
-    owner: "{{ item.key }}"
-    group: "{{ item.key }}"
-    mode: 0644
-  with_dict: "{{ human_users }}"
-  when: ( item.value.state == "present" )
-  ignore_errors: "yes"
 
 - name: create ROBOT users
-  user:
+  ansible.builtin.user:
     comment: "{{ item.value.comment }}"
-    create_home: "yes"
+    create_home: true
     group: "{{ item.key }}"
     groups: "{{ item.value.groups }}"
     home: "/home/{{ item.key }}/"
@@ -75,14 +60,13 @@
     state: "{{ item.value.state | default('present') }}"
     uid: "{{ item.value.uid }}"
     update_password: on_create
-  with_dict: "{{ robot_users }}"
+  loop: "{{ robot_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
+  no_log: true
 
 - name: lock ROBOT users
-  user:
+  ansible.builtin.user:
     name: "{{ item.key }}"
     password_lock: "{{ item.value.password_lock }}"
-  with_dict: "{{ robot_users }}"
+  loop: "{{ robot_users | dict2items }}"
   when: ( item.value.state == "present" )
-  ignore_errors: "yes"
diff --git a/tasks/install_checkmk_plugins.yml b/tasks/install_checkmk_plugins.yml
index cfa1c01a4510895da8b68b2345ddee60137501da..e73958fee9ba104ceecda772181b4ff06ade773f 100644
--- a/tasks/install_checkmk_plugins.yml
+++ b/tasks/install_checkmk_plugins.yml
@@ -1,18 +1,11 @@
 ---
 - name: CMK installieren
-  yum:
-    name: "check-mk-agent"
-    state: present
-  when: ansible_os_family == "RedHat"
-
-- name: CMK installieren
-  apt:
+  ansible.builtin.package:
     name: "check-mk-agent"
     state: latest
-  when: ansible_os_family == "Debian"
 
 - name: Ordner für Check_MK-Plugins anlegen
-  file:
+  ansible.builtin.file:
     path: "{{ item.name }}"
     state: directory
     owner: "root"
@@ -28,7 +21,7 @@
 
 # Plugins bereitstellen
 - name: Check_MK-Plugins installieren (Eigenentwicklungen)
-  copy:
+  ansible.builtin.copy:
     src: "usr/lib/check_mk_agent/{{ item.name }}"
     dest: "/usr/lib/check_mk_agent/{{ item.name }}"
     owner: "root"
@@ -39,16 +32,16 @@
     - name: "local/600/check_netapp_nfs_shares.sh"
   when: ansible_distribution == "Debian"
 
-#- name: install SSH private key for NetApp NFS share monitoring
-#  copy:
-#    src: "{{ role_path }}/../ansible_vaults/{{ role_name }}/.ssh/id_ed25519_netapp_report"
-#    dest: "/usr/lib/check_mk_agent/.ssh/id_ed25519_netapp_report"
-#    owner: "root"
-#    group: "root"
-#    mode: 0400
+# - name: install SSH private key for NetApp NFS share monitoring
+#   copy:
+#     src: "{{ role_path }}/../ansible_vaults/{{ role_name }}/.ssh/id_ed25519_netapp_report"
+#     dest: "/usr/lib/check_mk_agent/.ssh/id_ed25519_netapp_report"
+#     owner: "root"
+#     group: "root"
+#     mode: "0400"
 
 - name: Check_MK-Plugins installieren (offizielle Plugins)
-  get_url:
+  ansible.builtin.get_url:
     url: "{{ item }}"
     dest: "/usr/lib/check_mk_agent/plugins/"
     force: "yes"
@@ -61,12 +54,12 @@
 
 # Plugins konfigurieren
 - name: configure mk_logwatch plugin
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/check_mk/logwatch.cfg"
     create: true
     owner: "root"
     group: "root"
-    mode: 0644
+    mode: "0644"
     state: present
     block: |
       # reseting cached plugin state:
@@ -93,7 +86,7 @@
        W Call Trace
 
 - name: configure NRPE/MRPE plugins
-  copy:
+  ansible.builtin.copy:
     src: "etc/check_mk/mrpe.cfg"
     dest: "/etc/check_mk/mrpe.cfg"
     owner: "root"
@@ -101,7 +94,7 @@
     mode: "0644"
 
 - name: configure check_netapp_nfs_shares plugin
-  template:
+  ansible.builtin.template:
     src: "etc/check_mk/check_netapp_nfs_shares.cfg.j2"
     dest: "/etc/check_mk/check_netapp_nfs_shares.cfg"
     owner: "root"
diff --git a/tasks/install_hotadd_scripts.yml b/tasks/install_hotadd_scripts.yml
index 0b6a575e0b35e0dfd7d441bfc29a8ed8e2b0700a..6fcf8ea715a9f4627d048997a3d78c56d301a321 100644
--- a/tasks/install_hotadd_scripts.yml
+++ b/tasks/install_hotadd_scripts.yml
@@ -1,6 +1,6 @@
 ---
 - name: HotAdd-Scripte installieren
-  copy:
+  ansible.builtin.copy:
     src: "{{ item }}"
     dest: "/{{ item }}"
     owner: "root"
@@ -11,13 +11,13 @@
     - "usr/local/bin/hot-add-ram.sh"
 
 - name: udev-Regel für HotAdd RAM installieren
-  copy:
+  ansible.builtin.copy:
     content: "{{ item.content }}"
     dest: "{{ item.dest }}"
     owner: "root"
     group: "root"
     mode: "0644"
-  with_items:
+  loop:
     - content: 'SUBSYSTEM=="memory", ACTION=="add", ATTR{state}=="offline", ATTR{state}="online"'
       dest: "/etc/udev/rules.d/90-hotplug-memory.rules"
     - content: 'SUBSYSTEM=="cpu", ACTION=="add", ATTR{online}=="0", ATTR{online}="1"'
diff --git a/tasks/install_lzip_tools.yml b/tasks/install_lzip_tools.yml
index ff0d8c5b1af52def3efdf9db0c892980ba7c64eb..652cccfc7f24345f990a9d5153bc21155174d74f 100644
--- a/tasks/install_lzip_tools.yml
+++ b/tasks/install_lzip_tools.yml
@@ -1,11 +1,11 @@
 ---
 - name: Lzip-Tools installieren
-  copy:
+  ansible.builtin.copy:
     src: "{{ item }}"
     dest: "/{{ item }}"
     owner: "root"
     group: "root"
-    mode: 0755
+    mode: "0755"
   loop:
     - "usr/local/bin/lzipcat"
     - "usr/local/bin/lzipgrep"
diff --git a/tasks/install_needrestart.yml b/tasks/install_needrestart.yml
index b8251aaf44db79af0bd548459fd0d79388f3ae49..5f517360eb0a21ed8f17b349266fb438b6b7d670 100644
--- a/tasks/install_needrestart.yml
+++ b/tasks/install_needrestart.yml
@@ -1,16 +1,14 @@
 ---
-- block:
-  - name: install needrestart
-    apt:
-      name: "needrestart"
-      state: present
+- name: install needrestart
+  ansible.builtin.apt:
+    name: "needrestart"
+    state: present
 
-  - name: configure needrestart
-    blockinfile:
-      path: "/etc/needrestart/needrestart.conf"
-      owner: "root"
-      group: "root"
-      mode: "0644"
-      block: |
-        $nrconf{'nagios-status'}->{sessions} = 0;
-  when: ansible_distribution == "Debian"
+- name: configure needrestart
+  ansible.builtin.blockinfile:
+    path: "/etc/needrestart/needrestart.conf"
+    owner: "root"
+    group: "root"
+    mode: "0644"
+    block: |
+      $nrconf{'nagios-status'}->{sessions} = 0;
diff --git a/tasks/install_packages.yml b/tasks/install_packages.yml
index 228f928cd85f0a0de53860a1e25dc75396476aaf..c2aaf0f12a884934de367e739d63147c3d81435f 100644
--- a/tasks/install_packages.yml
+++ b/tasks/install_packages.yml
@@ -1,25 +1,25 @@
 ---
 # Äquivalent von apt-get update
 - name: apt-get update
-  apt:
-    update_cache: "yes"
+  ansible.builtin.apt:
+    update_cache: true
   when: ansible_os_family == "Debian"
 
 # Äquivalent von yum update
 - name: update packages (RedHat)
   block:
     - name: yum update
-      yum:
-        update_cache: "yes"
+      ansible.builtin.yum:
+        update_cache: true
         name: '*'
         state: present
     - name: yum autoremove
-      yum:
-        autoremove: "yes"
+      ansible.builtin.yum:
+        autoremove: true
   when: ansible_os_family == "RedHat"
 
 - name: uninstall packages
-  apt:
+  ansible.builtin.apt:
     name: [
       'atop',
       'byobu',
@@ -28,12 +28,12 @@
     purge: true
   when: ansible_os_family == "Debian"
   environment:
-    - NEEDRESTART_MODE: automatically
+    NEEDRESTART_MODE: "automatically"
 
 # Installation allgemeiner Pakete
 # byobu (contains tmux), e2fsprogs (contains e2label etc.), net-tools (contains arp, ifconfig, netstat, rarp, nameif, route etc.), sysstat (contains sar, iostat, mpstat, pidstat, sadf, nfsiostat, cifsiostat), util-linux (contains blkid etc.)
 - name: Installation allgemeiner Pakete
-  package:
+  ansible.builtin.package:
     name: [
       'bmon',
       'cowsay',
@@ -68,12 +68,12 @@
     ]
     state: present
   environment:
-    - NEEDRESTART_MODE: automatically
+    NEEDRESTART_MODE: "automatically"
 
 # Installation Debian-spezifische Pakete
 # lsb-release (uncodified dependency for open-vm-tools), nfs-common (contains lockd, statd, showmount, nfsstat, gssd, idmapd, mount.nfs), procps (contains tload)
 - name: Installation Debian-spezifische Pakete
-  apt:
+  ansible.builtin.apt:
     name: [
       'acct',
       'apt-file',
@@ -102,12 +102,12 @@
     ]
     state: present
   environment:
-    - NEEDRESTART_MODE: automatically
+    NEEDRESTART_MODE: "automatically"
   when: ansible_os_family == "Debian"
 
 # Installation RedHat-spezifische Pakete
 - name: Installation RedHat-spezifische Pakete
-  yum:
+  ansible.builtin.yum:
     name: [
       'bind-utils',
       'csh',
@@ -139,5 +139,5 @@
     ]
     state: present
   environment:
-    - NEEDRESTART_MODE: automatically
+    NEEDRESTART_MODE: "automatically"
   when: ansible_os_family == "RedHat"
diff --git a/tasks/main.yml b/tasks/main.yml
index c791f416e002b6409a397170afe429fe6c5e2af2..ada2ad387cb8d081317800703e74ce1e56d7c196 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,6 +1,6 @@
 ---
 - name: include Ansible Vaults
-  include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
+  ansible.builtin.include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
   loop:
     - "check_mk.vault"
     - "cron_apt.vault"
@@ -13,13 +13,15 @@
   tags: [always]
 
 ### PAKETINSTALLATIONEN ###
-#- name: Netzwerk konfigurieren
-#  import_tasks: configure-network.yml
-#  tags: [network,dns]
+# - name: Netzwerk konfigurieren
+#   import_tasks: configure-network.yml
+#   tags: [network,dns]
 
+# We don't test for idempotence because these tasks can never be idempotent.
+# They are meant to copy fresh Backups of the SSH keys every time they are run.
 - name: Server-SSH-Schlüssel sichern
   import_tasks: backup_ssh_hostkeys.yml
-  tags: [ssh]
+  tags: [ssh, molecule-idempotence-notest]
 
 - name: SLUB-lokales Debian-Repository hinzufügen
   import_tasks: configure_package_repositories.yml
@@ -99,6 +101,7 @@
 
 - name: Needrestart installieren
   import_tasks: install_needrestart.yml
+  when: ansible_distribution == "Debian"
   tags: [apt, needrestart]
 
 - name: Check_MK-Plugins installieren
@@ -137,15 +140,16 @@
   tags: [glances]
 
 ### CGROUP FÜR CHECK_MK KONFIGURIEREN ###
-#- name: include cgroup CMK config
-#  import_tasks: cgroup_check_mk.yml
-#  tags: [always]
+# - name: include cgroup CMK config
+#   import_tasks: cgroup_check_mk.yml
+#   tags: [always]
 
 - name: NTP konfigurieren
   import_tasks: configure_ntp.yml
   when: ansible_os_family == "RedHat"
   tags: [ntp]
 
+# there's no way to get this task to become idempotent, so we have to skip the test
 - name: Flush handlers am Ende der Rolle
-  meta: flush_handlers
-  tags: [always]
+  ansible.builtin.meta: flush_handlers
+  tags: [always, molecule-idempotence-notest]
diff --git a/tasks/migrate_ntpd_to_esxi_timesync.yml b/tasks/migrate_ntpd_to_esxi_timesync.yml
index 190a01ff31ee7d198bda5d4f9c5e6e795c3c2835..a9df01be70655b1aac71e7784e93ecf03b1528a7 100644
--- a/tasks/migrate_ntpd_to_esxi_timesync.yml
+++ b/tasks/migrate_ntpd_to_esxi_timesync.yml
@@ -1,38 +1,23 @@
 ---
-- block:
-  - name: purge NTPd package
-    apt:
-      name: "ntp"
-      state: absent
+- name: purge NTPd package
+  ansible.builtin.apt:
+    name: "ntp"
+    state: absent
 
 # details can be found at sdvuda10 project, issue #2
-  - name: make sure open-vm-tools are installed
-    apt:
-      name: "open-vm-tools"
-      state: latest
-  when: ansible_os_family == "Debian"
-
-- block:
-  - name: purge NTPd package
-    yum:
-      name: "ntp"
-      state: absent
-
-# details can be found at sdvuda10 project, issue #2
-  - name: make sure open-vm-tools are installed
-    yum:
-      name: "open-vm-tools"
-      state: latest
-  when: ansible_os_family == "RedHat"
+- name: make sure open-vm-tools are installed
+  ansible.builtin.package:
+    name: "open-vm-tools"
+    state: latest
 
 - name: make sure that 'vmware-toolbox-cmd' exists
-  stat:
+  ansible.builtin.stat:
     path: "/usr/bin/vmware-toolbox-cmd"
-    follow: True
+    follow: true
   register: vmtools
 
 - name: get current ESXi timesync status
-  shell: "/usr/bin/vmware-toolbox-cmd timesync status"
+  ansible.builtin.command: "/usr/bin/vmware-toolbox-cmd timesync status"
   when:
     - vmtools.stat.exists
     - vmtools.stat.isreg
@@ -41,7 +26,7 @@
   failed_when: ( timesync.rc != 0 ) and ( timesync.rc != 69 )
 
 - name: enable ESXi timesync if necessary
-  shell: "/usr/bin/vmware-toolbox-cmd timesync enable"
+  ansible.builtin.command: "/usr/bin/vmware-toolbox-cmd timesync enable"
   when:
     - ( timesync.stdout not in "Aktiviert" ) or
       ( timesync.stdout not in "Enabled" )
diff --git a/tasks/uninstall_tsm_client.yml b/tasks/uninstall_tsm_client.yml
index 66f55145fbbf11f4196e5254177f568a9514eb96..87c6bc101c8dc29bb3f99a68df3c491f4203b1c2 100644
--- a/tasks/uninstall_tsm_client.yml
+++ b/tasks/uninstall_tsm_client.yml
@@ -4,32 +4,36 @@
     - name: prüfen, ob noch Teile des TSM-Clients installiert sind
       block:
         - name: prüfen, ob SystemD Unitfile installiert ist
-          #command: systemctl list-unit-files | grep dsmcad
-          command: systemctl list-unit-files dsmcad.service
+          ansible.builtin.command: systemctl list-unit-files dsmcad.service
           register: tsm_client_installed
           failed_when:
             - '"0 unit files listed." not in tsm_client_installed.stdout'
             - '"1 unit files listed." not in tsm_client_installed.stdout'
             - tsm_client_installed.rc >= 2
-          #ignore_errors: true
+          changed_when: false
+          # ignore_errors: true
 #        - name: debug
 #          debug:
 #            msg: "tsm_client_installed: {{ tsm_client_installed }}"
 
     - name: TSM-Client entfernen
       block:
+        - name: find out if TSM Service is enabled
+          ansible.builtin.command: systemctl is-enabled dsmcad.service
+          register: dsmcad_enabled
+          changed_when: false
         - name: TSM-Client SystemD-Service deaktivieren
-          systemd:
+          ansible.builtin.systemd:
             name: "dsmcad.service"
             enabled: false
             state: stopped
-          ignore_errors: "yes"
+          when: dsmcad_enabled.stdout == "disabled"
         - name: TSM-Client SystemD-Service entfernen
-          file:
+          ansible.builtin.file:
             path: "/etc/systemd/system/dsmcad.service"
             state: absent
         - name: TSM-Client Pakete deinstallieren
-          apt:
+          ansible.builtin.apt:
             name: [
               'gskcrypt64',
               'gskssl64',
@@ -42,14 +46,13 @@
             ]
             state: absent
         - name: TSM-Client Logfiles & Config & Systemd-Unitfile löschen
-          file:
+          ansible.builtin.file:
             path: "{{ item }}"
             state: absent
           loop:
             - "/var/log/dsm*.log"
             - "/opt/tivoli"
             - "/etc/systemd/system/dsmcad.service"
-          ignore_errors: "yes"
       when:
         - ( "0 unit files listed." not in tsm_client_installed.stdout )
   when: ansible_distribution == "Debian"
diff --git a/templates/etc/vimrc.j2 b/templates/etc/vim/vimrc.local.j2
similarity index 100%
rename from templates/etc/vimrc.j2
rename to templates/etc/vim/vimrc.local.j2