diff --git a/.ansible-lint b/.ansible-lint
index 9c9323e8df825eddaf10a9188e0fd26583c5dde0..f18a6472898d714ef85310cdbe01e39500977528 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/.gitignore b/.gitignore
index 4ac23e9cce338de22920f9cde3b2bbcc9afed1cb..ed783329d62a4c1e481a467fe674eb420a2b81dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,8 @@ Thumbs.db
 
 *.retry
 *.vault
+inventory.*
+inv.*
 
 # Vim #
 #######
@@ -73,7 +75,14 @@ tags
 .vagrant/
 *.box
 
-# Backups #
-###########
+# Temporary/Build/Backup #
+##########################
 
 backups/
+build/
+
+# CONFIDENTIAL #
+################
+
+ssh_host_*
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..df4e06fd565598f55e903013288871e8cdc4d12b
--- /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_SERVER_HARDENING}" > ../lza_server_hardening.pass
+    - export ANSIBLE_VAULT_PASSWORD_FILE=../lza_server_hardening.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/.yamllint b/.yamllint
index 7c0f15cf782b192e2b80d4eadb9dcbf92b4553bf..88276760562cb58bb9bc47fa890e8a3df1125b9b 100644
--- a/.yamllint
+++ b/.yamllint
@@ -1,7 +1,5 @@
 ---
-# based on documentation available at
-# https://yamllint.readthedocs.io/en/stable/rules.html
-
+# Based on ansible-lint config
 extends: default
 
 rules:
@@ -11,13 +9,25 @@ rules:
   brackets:
     max-spaces-inside: 1
     level: error
-  comments:
-    min-spaces-from-content: 4
+  colons:
+    max-spaces-after: -1
+    level: error
+  commas:
+    max-spaces-after: -1
+    level: error
+  comments: disable
   comments-indentation: disable
-  document-end: disable
-  document-start:
-    level: warning
-  octal-values:
-    forbid-explicit-octal: false
+  document-start: disable
+  empty-lines:
+    max: 3
+    level: error
+  hyphens:
+    level: error
+  indentation: disable
+  key-duplicates: enable
   line-length: disable
-  truthy: enable
+  new-line-at-end-of-file: disable
+  new-lines:
+    type: unix
+  trailing-spaces: disable
+  truthy: disable
diff --git a/ansible.cfg b/ansible.cfg
index c43f9b371180b41e2d08a9e1cd93f0d006db0469..5c000f20e1d1fbafd17212410f72a3d6564051d3 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, ../lza_server_hardening.pass, ../slub_osquery.pass
+# vault_identity_list = ../lza_install_common.pass, ../lza_server_hardening.pass, ../slub_osquery.pass
+vault_identity_list = ../lza_server_hardening.pass
 
 # Path to default inventory file
 # Administrators can override this by using the "-i <inventoryfile>" CLI
diff --git a/handlers/main.yml b/handlers/main.yml
index ada4777b88b9f2b831d5e8813e0527bda260b597..702d7665e33c0ec536cabeff03f0920c9f5540de 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -2,7 +2,7 @@
 - name: save iptables rules (Debian)
   block:
     - name: Ordner für iptables-Config erstellen
-      file:
+      ansible.builtin.file:
         path: "/etc/iptables"
         state: directory
         owner: "root"
@@ -10,19 +10,20 @@
         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"
     - name: save iptables rules
-      command: 'netfilter-persistent save'
+      ansible.builtin.command: 'netfilter-persistent save'
       listen: "save iptables rules"
+      changed_when: false
   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"
@@ -30,33 +31,34 @@
         mode: 0600
       listen: "save iptables rules"
     - name: save rules
-      command: /usr/sbin/iptables-save        # noqa 303
+      ansible.builtin.command: /usr/sbin/iptables-save        # noqa 303
       listen: "save iptables rules"
+      changed_when: false
   when: ansible_os_family == "RedHat"
 
 - name: activate kernel parameter changes
-  command: sysctl -p
-  ignore_errors: true
+  ansible.builtin.command: sysctl -p
+  changed_when: false
 
 - name: restart fail2ban.service
-  service:
+  ansible.builtin.service:
     name: "fail2ban"
     state: restarted
 
 - name: restart sshd
-  service:
+  ansible.builtin.service:
     name: "sshd"
     state: restarted
 
 - name: restart auditd.service
-  service:
+  ansible.builtin.service:
     name: "auditd"
     state: restarted
   when: ansible_os_family == "Debian"
   listen: restart auditd.service
 
 - name: restart auditd.service
-  service:
+  ansible.builtin.service:
     name: "auditd"
     state: restarted
     use: "service"
@@ -64,13 +66,13 @@
   listen: restart auditd.service
 
 - name: restart clamav-daemon service
-  service:
+  ansible.builtin.service:
     name: "clamav-daemon"
     state: restarted
   when: ansible_os_family == "Debian"
 
 - name: restart clamd service
-  service:
+  ansible.builtin.service:
     name: "clamd@{{ ansible_hostname }}.service"
     state: restarted
   when: ansible_os_family == "RedHat"
diff --git a/meta/main.yml b/meta/main.yml
index a798dd19ae204aea14bcacdd531686ec5b116ada..cf10295c1aa49f3acb1822f61194efd47b930d87 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -1,16 +1,24 @@
 ---
 galaxy_info:
-  author: Jörg Sachse
-  description: role to deploy a hardened install of Debian for use in the SLUBarchiv digital preservation repository
+  author: Jörg Sachse <Joerg.Sachse@slub-dresden.de>
   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
-  license: public domain
-  min_ansible_version: 2.4
-  # 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:
-  #
+  description: role to deploy a hardened 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: GPLv3
+    # 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/
   #
@@ -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.
-# dependencies: []
+        - "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..3841ab1f6fbdfc4b16f9491b776a826e19fa583c
--- /dev/null
+++ b/molecule/default
@@ -0,0 +1 @@
+./virtualbox
\ No newline at end of file
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
deleted file mode 100644
index 489d40a96cbb06271b80a3bf523c9c11dbaff012..0000000000000000000000000000000000000000
--- a/molecule/default/converge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - {role: "ansible_lza_server_hardening", become: true}
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
deleted file mode 100644
index f435b3fbc0469994de5444a0ddbaed3bf8f947e2..0000000000000000000000000000000000000000
--- a/molecule/default/molecule.yml
+++ /dev/null
@@ -1,36 +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-server-hardening-debian
-    box: debian/buster64
-    memory: 512
-    cpus: 1
-provisioner:
-  name: ansible
-  log: true
-  config_options:
-    defaults:
-      vault_identity_list: "@$HOME/.ansible/roles/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass"
-  lint: |
-    set -e
-    ansible-lint
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  lint: |
-    set -e
-    flake8
-  options:
-    v: 1
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
deleted file mode 100644
index ee22e991c214c4a8fc91b2057dcd25d23093bc8b..0000000000000000000000000000000000000000
--- a/molecule/default/prepare.yml
+++ /dev/null
@@ -1,39 +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}
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 39da80ab2f779c2ea1dc20daba64d1c3b2999201..0000000000000000000000000000000000000000
--- a/molecule/default/tests/test_default.py
+++ /dev/null
@@ -1,6 +0,0 @@
-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..4a49614c4cf2b38ebb289314914c7ddc165d0350
--- /dev/null
+++ b/molecule/resources/playbooks/converge.yml
@@ -0,0 +1,17 @@
+---
+- name: Converge
+  hosts: all
+  pre_tasks:
+    - name: update apt cache
+      ansible.builtin.apt:
+        update_cache: true
+        upgrade: dist
+      become: true
+      when: ansible_os_family == "Debian"
+    - name: update yum cache
+      ansible.builtin.yum:
+        update_cache: true
+      become: true
+      when: ansible_os_family == "RedHat"
+  roles:
+    - {role: "ansible_lza_server_hardening", 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/molecule.yml b/molecule/virtualbox/molecule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6f084c72c2e0306fefe762a8d2a4909f686c0e54
--- /dev/null
+++ b/molecule/virtualbox/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: yes
+      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_server_hardening.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.example
similarity index 100%
rename from requirements.yml
rename to requirements.yml.example
diff --git a/site.yml b/site.yml
index 9dd4661785f1221abab9925826f3bfb15428ab66..5cac9570f0012b5ded67b118591c93bbc30abc41 100644
--- a/site.yml
+++ b/site.yml
@@ -24,5 +24,4 @@
   strategy: linear
 
   roles:
-    - { role: ansible_lza_install_common, become: true }
     - { role: ansible_lza_server_hardening, become: true }
diff --git a/tasks/configure-fail2ban.yml b/tasks/configure_fail2ban.yml
similarity index 89%
rename from tasks/configure-fail2ban.yml
rename to tasks/configure_fail2ban.yml
index 7208bc8b17deae587ce3cdd3a263e4857ca46f96..8836af2218c3e8d554448c152db1529f499cbca1 100644
--- a/tasks/configure-fail2ban.yml
+++ b/tasks/configure_fail2ban.yml
@@ -1,17 +1,17 @@
 ---
 ### Fail2Ban einrichten ###
 - name: fail2ban IDS installieren
-  package:
+  ansible.builtin.package:
     name: "fail2ban"
     state: present
   tags: [apt, yum]
 
 # neue Konfiguration einspielen
 - name: Konfiguration fuer fail2ban einspielen (1/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/jail.local"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: 0644
@@ -80,10 +80,10 @@
   notify: restart fail2ban.service
 
 - name: Konfiguration fuer fail2ban einspielen (2/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/filter.d/f2b-loop.conf"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -100,22 +100,23 @@
   notify: restart fail2ban.service
 
 - name: see if fail2ban.local exists
-  stat:
+  ansible.builtin.stat:
     path: "/etc/fail2ban/fail2ban.local"
   register: old_fail2ban_local
 
 - name: fail2ban.local bereinigen
-  file:
+  ansible.builtin.file:
     path: "/etc/fail2ban/fail2ban.local"
     state: absent
   when: old_fail2ban_local.stat.exists
+  changed_when: false    # we cannot make this task idempotent in any other way, because we previously deleted the file to get a clean state
   notify: restart fail2ban.service
 
 - name: Konfiguration fuer fail2ban einspielen (4/4)
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/fail2ban/fail2ban.local"
     backup: "no"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -131,11 +132,12 @@
       pidfile = /var/run/fail2ban/fail2ban.pid
       dbfile = /var/lib/fail2ban/fail2ban.sqlite3
       dbpurgeage = 86400
+  changed_when: false    # we cannot make this task idempotent in any other way, because we previously deleted the file to get a clean state
   notify: restart fail2ban.service
 
 # Ordner für Check_MK-Plugin anlegen
 - name: Ordner für Check_MK-Plugin anlegen
-  file:
+  ansible.builtin.file:
     path: "/usr/lib/check_mk_agent/plugins"
     state: directory
     owner: "root"
@@ -144,7 +146,7 @@
 
 # Plugin bereitstellen
 - name: Check_MK-Plugin installieren (fail2ban-Zustand)
-  copy:
+  ansible.builtin.copy:
     src: "usr/lib/check_mk_agent/plugins/check_fail2ban_status.sh"
     dest: "/usr/lib/check_mk_agent/plugins/check_fail2ban_status.sh"
     owner: "root"
diff --git a/tasks/configure-home-permissions.yml b/tasks/configure_home_permissions.yml
similarity index 87%
rename from tasks/configure-home-permissions.yml
rename to tasks/configure_home_permissions.yml
index 3624d1aa33d5ac1a0a87046723b7df2660f6540c..0343af69633d822597c08c70558e1f63ca6133e4 100644
--- a/tasks/configure-home-permissions.yml
+++ b/tasks/configure_home_permissions.yml
@@ -2,16 +2,16 @@
 - name: $HOME-Verzeichnisse von Usern mit gleichen Gruppen schützen
   block:
     - name: $HOME-Verzeichnisse sammeln
-      find:
+      ansible.builtin.find:
         file_type: directory
         paths: "/home/"
         excludes: 'import'
       register: ls_out
     - name: striktere Berechtigungen für Homeverzeichnisse setzen
-      file:
+      ansible.builtin.file:
         path: "{{ item.path }}/"
         mode: "0700"
-      with_items:
+      loop:
         # - "{{ ls_out.files | difference(['import','zih']) }}"
         - "{{ ls_out.files }}"
       when: item.path not in "import"
diff --git a/tasks/configure-iptables.yml b/tasks/configure_iptables.yml
similarity index 90%
rename from tasks/configure-iptables.yml
rename to tasks/configure_iptables.yml
index 75a32a8126f7cb9ea22d650c20f19bcd88dc853b..00b634e368c70212f01b31a681716e049fb637e3 100644
--- a/tasks/configure-iptables.yml
+++ b/tasks/configure_iptables.yml
@@ -1,6 +1,6 @@
 ---
 - name: stop and disable firewalld on RHEL systems (until we have a decent config)
-  systemd:
+  ansible.builtin.systemd:
     name: "{{ item.service }}"
     state: "{{ item.state }}"
     enabled: "{{ item.enabled }}"
@@ -11,7 +11,7 @@
   when: ansible_os_family == "RedHat"
 
 # - name: clean IPtables rules (1)
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "INPUT"
 #     ip_version: "{{ item }}"
 #     policy: "ACCEPT"
@@ -20,19 +20,17 @@
 #     - "ipv6"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 #
 # - name: clean IPtables rules (2)
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "INPUT"
 #     flush: "true"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 
 
 - name: Allow related and established IPv4 connections
-  iptables:
+  ansible.builtin.iptables:
     chain: "INPUT"
     ctstate: "ESTABLISHED,RELATED"
     jump: "ACCEPT"
@@ -42,7 +40,7 @@
     - save iptables rules
 
 # - name: Allow related and established IPv4 connections
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "OUTPUT"
 #     comment: 'allow related and established connections'
 #     ctstate: "ESTABLISHED,RELATED"
@@ -52,7 +50,7 @@
 #     - save iptables rules
 
 - name: Allow all loop back traffic
-  iptables:
+  ansible.builtin.iptables:
     action: "insert"
     chain: "INPUT"
     comment: 'allow all loop back traffic'
@@ -62,7 +60,7 @@
     - save iptables rules
 
 # - name: Allow all loop back traffic
-#   iptables:
+#   ansible.builtin.iptables:
 #     action: "insert"
 #     chain: "OUTPUT"
 #     comment: 'allow all loop back traffic'
@@ -73,7 +71,7 @@
 
 # Set default policy for INPUT chain
 - name: iptables-Policy für INPUT-Chain setzen
-  iptables:
+  ansible.builtin.iptables:
     chain: "INPUT"
     ip_version: "{{ item }}"
     policy: "DROP"
@@ -82,11 +80,10 @@
     - "ipv6"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
 
 ## Set default policy for OUTPUT chain
 # - name: iptables-Policy für OUTPUT-Chain setzen
-#   iptables:
+#   ansible.builtin.iptables:
 #     chain: "OUTPUT"
 #     ip_version: "{{ item }}"
 #     policy: "DROP"
@@ -95,11 +92,10 @@
 #     - "ipv6"
 #   notify:
 #     - save iptables rules
-#   tags: [molecule-notest]
 
 # Configure specific rules - Chain INPUT
 - name: iptables-Regeln (IPv4) setzen - Chain INPUT
-  iptables:
+  ansible.builtin.iptables:
     action: "insert"
     chain: "INPUT"
     comment: "{{ item.comment | default(omit) }}"
@@ -116,17 +112,16 @@
     source_port: "{{ item.src_port | default(omit) }}"
     state: "{{ item.state }}"
     table: "filter"
-  loop: "{{ vault_iptables_input|flatten(levels=1) }}"
+  loop: "{{ vault_iptables_input | flatten(levels=1) }}"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
 # http://shouldiblockicmp.com
 
 # TODO: Outgoing iptables Regeln erstellen, und bloß keine vergessen!!!
 
 ## Configure specific rules - Chain OUTPUT
 #  - name: iptables-Regeln (IPv4) setzen - Chain OUTPUT
-#    iptables:
+#    ansible.builtin.iptables:
 #      action: "insert"
 #      chain: OUTPUT
 #      comment: "{{ item.comment }}"
@@ -146,11 +141,10 @@
 #    loop: "{{ vault_iptables_output|flatten(levels=1) }}"
 #    notify:
 #      - save iptables rules
-#    tags: [molecule-notest]
 
 # THESE NEED TO BE THE LAST RULES IN IPTABLES' RULE LIST!!!
 - name: iptables-Regeln (IPv4) setzen - REJECT
-  iptables:
+  ansible.builtin.iptables:
     action: "append"
     chain: "{{ item.chain }}"
     jump: "REJECT"
@@ -163,4 +157,3 @@
     - chain: "FORWARD"
   notify:
     - save iptables rules
-  tags: [molecule-notest]
diff --git a/tasks/configure-kernel-parameters.yml b/tasks/configure_kernel_parameters.yml
similarity index 99%
rename from tasks/configure-kernel-parameters.yml
rename to tasks/configure_kernel_parameters.yml
index daf4440a12d6b6a5ee10fd4af7e84f918a94ec74..8dae33c7b3208679f85b94ee0ce494a3a9dcb9c5 100644
--- a/tasks/configure-kernel-parameters.yml
+++ b/tasks/configure_kernel_parameters.yml
@@ -3,7 +3,7 @@
 - name: set custom kernel parameters
   block:
     - name: write kernel parameter changes
-      blockinfile:
+      ansible.builtin.blockinfile:
         insertafter: EOF
         marker: '### {mark} ANSIBLE MANAGED BLOCK - SERVER HARDENING'
         path: "/etc/sysctl.conf"
diff --git a/tasks/configure-pam.yml b/tasks/configure_pam.yml
similarity index 93%
rename from tasks/configure-pam.yml
rename to tasks/configure_pam.yml
index b2ffa7a023095695663534cab9a27c65955ff83c..de65f78c46f1cde8a6884899f9dc8842993cb7f1 100644
--- a/tasks/configure-pam.yml
+++ b/tasks/configure_pam.yml
@@ -2,9 +2,9 @@
 # Documentation: http://www.linux-pam.org/Linux-PAM-html/sag-module-reference.html "The Linux-PAM System Administrators' Guide - Chapter 6. A reference guide for available modules"
 
 - name: configure 'su' usage restrictions through PAM
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/pam.d/su"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
@@ -18,9 +18,9 @@
         account    requisite  pam_time.so
 
 - name: configure times for certain system actions
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/security/time.conf"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
@@ -42,9 +42,9 @@
         login;tty*;operator;!Al2300-0500
 
 - name: configure login actions
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/pam.d/login"
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     insertafter: EOF
diff --git a/tasks/configure-portable-storage.yml b/tasks/configure_portable_storage.yml
similarity index 90%
rename from tasks/configure-portable-storage.yml
rename to tasks/configure_portable_storage.yml
index df8abdf18c8b5473962a42004be738a41e8d854d..d4d9558027e6dea9b50f1682061fa16773ae078a 100644
--- a/tasks/configure-portable-storage.yml
+++ b/tasks/configure_portable_storage.yml
@@ -1,15 +1,15 @@
 ---
 - name: disable USB storage support
-  lineinfile:
+  ansible.builtin.lineinfile:
     state: present
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0700"
     insertafter: EOF
     path: "/etc/modprobe.d/{{ item.path }}"
     line: "{{ item.line }}"
-  with_items:
+  loop:
     - path: "disable-usb-storage.conf"
       line: "blacklist usb-storage"
       # Alternative: "fake install" (https://linuxtechlab.com/disable-usb-storage-linux/)
diff --git a/tasks/configure-root.yml b/tasks/configure_root.yml
similarity index 64%
rename from tasks/configure-root.yml
rename to tasks/configure_root.yml
index 09c75f5a7d7747788f425ef3fad90b546de75fd8..f501d773a830081fe96adea78662050bc6c123e1 100644
--- a/tasks/configure-root.yml
+++ b/tasks/configure_root.yml
@@ -1,9 +1,9 @@
 ---
 - name: prevent root login
-  user:
+  ansible.builtin.user:
     name: "root"
     shell: "/bin/bash"
     comment: "root user disabled"
-    # local: "yes"
+    # local: true
     local: false
-    password_lock: "yes"
+    password_lock: true
diff --git a/tasks/configure-ssh-hardening.yml b/tasks/configure_ssh_hardening.yml
similarity index 99%
rename from tasks/configure-ssh-hardening.yml
rename to tasks/configure_ssh_hardening.yml
index e7e235eb9517eb58984c611afba6dedd210eddcc..cbc8cdc448d0889e748ccfebe29cd7d40ad8e069 100644
--- a/tasks/configure-ssh-hardening.yml
+++ b/tasks/configure_ssh_hardening.yml
@@ -2,7 +2,7 @@
 - name: Konfiguration für OpenSSH einspielen - gehärtete Config
   ansible.builtin.blockinfile:
     path: "/etc/ssh/sshd_config"
-    backup: "yes"
+    backup: true
     insertbefore: "### BEGIN ANSIBLE MANAGED BLOCK - SFTP SERVER"
     marker: "### {mark} ANSIBLE MANAGED BLOCK - HARDENED SSH SERVER"
     validate: /usr/sbin/sshd -T -f %s
diff --git a/tasks/configure-umask.yml b/tasks/configure_umask.yml
similarity index 85%
rename from tasks/configure-umask.yml
rename to tasks/configure_umask.yml
index f5a52331338ecf8d6e97bcbeaa930536857db948..66f7b1cfa94d3bc2683476f77dc704edfeb5a7d9 100644
--- a/tasks/configure-umask.yml
+++ b/tasks/configure_umask.yml
@@ -1,8 +1,8 @@
 ---
 - name: set more secure umask (block everything for 'other' users)
-  blockinfile:
+  ansible.builtin.blockinfile:
     state: present
-    create: "yes"
+    create: true
     owner: "root"
     group: "root"
     mode: "0644"
@@ -15,15 +15,15 @@
 - name: libpam-umask installieren (Debian)
   block:
     - name: Paket installieren
-      apt:
+      ansible.builtin.apt:
         name: "libpam-umask"
         state: present
       tags: [apt]
 
     - name: Standard-umask mit PAM anpassen
-      blockinfile:
+      ansible.builtin.blockinfile:
         path: "/etc/pam.d/common-session"
-        create: "yes"
+        create: true
         owner: "root"
         group: "root"
         insertafter: EOF
@@ -37,14 +37,14 @@
   tags: [apt]
 
 - name: set default login umask
-  lineinfile:
+  ansible.builtin.lineinfile:
     path: "/etc/login.defs"
     regex: "{{ umask[ansible_os_family] }}"
     line: "UMASK           026"
   vars:
-    - umask:
-        Debian: "UMASK		022"
-        RedHat: "UMASK           077"
+    umask:
+      Debian: "UMASK           022"
+      RedHat: "UMASK           077"
 
 # umask could also be set in:
 # - /etc/profile.d/umask (Setting umask in profile.d sets it for all users who
diff --git a/tasks/install-debsecan.yml b/tasks/install-debsecan.yml
deleted file mode 100644
index 8ae3ea1560c4d30d1e52b0201e3d79911c94603a..0000000000000000000000000000000000000000
--- a/tasks/install-debsecan.yml
+++ /dev/null
@@ -1,26 +0,0 @@
----
-- block:
-    - name: include vars debsecan
-      include_vars: debsecan_preseed.yml
-
-    - name: install debsecan package
-      apt:
-        name: "debsecan"
-        state: present
-
-    - name: reconfigure debsecan package (dpkg-reconfigure)
-      debconf:
-        name: "debsecan"
-        question: "{{ item.question }}"
-        value: "{{ item.value }}"
-        vtype: "{{ item.vtype }}"
-      loop: "{{ debsecan_dpkg | flatten(levels=1) }}"
-
-    - name: reconfigure debsecan package (/etc/default/debsecan)
-      template:
-        src: "debsecan.j2"
-        dest: "/etc/default/debsecan"
-        owner: "root"
-        group: "root"
-        mode: "0644"
-        force: "yes"
diff --git a/tasks/install-auditd.yml b/tasks/install_auditd.yml
similarity index 89%
rename from tasks/install-auditd.yml
rename to tasks/install_auditd.yml
index 62ecb17bab5625f7f656d65c2d0ca87f90eb87c0..ce245c1746953bb9dd1fb3f744aa888179cde0e3 100644
--- a/tasks/install-auditd.yml
+++ b/tasks/install_auditd.yml
@@ -2,21 +2,21 @@
 # install & configure auditd
 
 - name: install auditd package (Debian)
-  apt:
+  ansible.builtin.apt:
     name: "auditd"
     state: present
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: install auditd package (RedHat)
-  yum:
+  ansible.builtin.yum:
     name: "audit"
     state: present
   when: ansible_os_family == "RedHat"
   tags: [yum]
 
 - name: configure auditd rules
-  copy:
+  ansible.builtin.copy:
     src: "etc/audit/rules.d/audit.rules"
     dest: "/etc/audit/rules.d/audit.rules"
     mode: "0640"
diff --git a/tasks/install-clamav.yml b/tasks/install_clamav.yml
similarity index 92%
rename from tasks/install-clamav.yml
rename to tasks/install_clamav.yml
index 1ea310d180a32144ad7f293fc4657ae681957b4a..dfa9548be2a272cfcec44f60cb71410d796045f8 100644
--- a/tasks/install-clamav.yml
+++ b/tasks/install_clamav.yml
@@ -2,20 +2,20 @@
 # based on https://www.golinuxcloud.com/steps-install-configure-clamav-antivirus-centos-linux/
 
 - name: include vars clamav
-  include_vars: "clamav.yml"
+  ansible.builtin.include_vars: "clamav.yml"
   tags: [apt]
 
 
 
 - name: install clamav packages (Debian)
-  apt:
+  ansible.builtin.apt:
     name: "clamav-daemon"
     state: present
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: install clamav packages (RedHat)
-  yum:
+  ansible.builtin.yum:
     name: [
       'clamav-server',
       'clamav',
@@ -34,9 +34,10 @@
 
 
 - name: create ClamAV log directory
-  file:
+  ansible.builtin.file:
     path: "/var/log/clamav/"
     state: directory
+    mode: "0755"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
 
@@ -44,12 +45,12 @@
 
 # clamav-freshclam.service und clamav-daemon.service laufen nach der Installation sofort los
 - name: configure freshclam
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/freshclam.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
-    create: "yes"
+    create: true
     block: |
       # Automatically created by the clamav-freshclam postinst
       # Comments will get lost when you reconfigure the clamav-freshclam package
@@ -92,13 +93,14 @@
 #  when: ansible_os_family == "RedHat"
 
 - name: install Freshclam timer
-  copy:
+  ansible.builtin.copy:
     src: "etc/systemd/system/clamav-freshclam.timer"
     dest: "/etc/systemd/system/clamav-freshclam.timer"
+    mode: "0644"
   when: ansible_os_family == "RedHat"
 
 - name: start and enable Freshclam timer
-  systemd:
+  ansible.builtin.systemd:
     service: "clamav-freshclam.timer"
     enabled: true
     state: started
@@ -107,7 +109,7 @@
 
 
 - name: configure ClamD
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/{{ 'clamd' if ansible_os_family == 'Debian' else ansible_hostname }}.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamscan' }}"
@@ -137,7 +139,7 @@
   notify: restart clamd service
 
 - name: configure ClamD exclude paths
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "{{ clamav_cfg_path }}/{{ 'clamd' if ansible_os_family == 'Debian' else ansible_hostname }}.conf"
     mode: "0444"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamscan' }}"
@@ -154,12 +156,12 @@
     - restart clamd service
 
 - name: configure ClamD to refresh rkhunter after DB updates
-  blockinfile:
+  ansible.builtin.blockinfile:
     name: "/usr/local/bin/refresh_rkhunter.sh"
     mode: "0755"
     owner: "{{ 'clamav' if ansible_os_family == 'Debian' else 'clamupdate' }}"
     group: "adm"
-    create: "yes"
+    create: true
     insertafter: EOF
     block: |
       #!/usr/bin/env bash
@@ -170,14 +172,15 @@
       fi
 
 - name: copy systemd service
-  copy:
+  ansible.builtin.copy:
    src: "/usr/lib/systemd/system/clamd@.service"
    dest: "/etc/systemd/system/"
+   mode: "0644"
    remote_src: true
   when: ansible_os_family == "RedHat"
 
 - name: enable ClamD systemd service
-  systemd:
+  ansible.builtin.systemd:
     service: "clamd@{{ ansible_hostname }}.service"
     enabled: true
     state: "started"
diff --git a/tasks/install_debsecan.yml b/tasks/install_debsecan.yml
new file mode 100644
index 0000000000000000000000000000000000000000..52ff55871fe89a9071b684b1f8fe362bf885f755
--- /dev/null
+++ b/tasks/install_debsecan.yml
@@ -0,0 +1,25 @@
+---
+- name: include vars debsecan
+  ansible.builtin.include_vars: debsecan_preseed.yml
+
+- name: install debsecan package
+  ansible.builtin.apt:
+    name: "debsecan"
+    state: present
+
+- name: reconfigure debsecan package (dpkg-reconfigure)
+  ansible.builtin.debconf:
+    name: "debsecan"
+    question: "{{ item.question }}"
+    value: "{{ item.value }}"
+    vtype: "{{ item.vtype }}"
+  loop: "{{ debsecan_dpkg | flatten(levels=1) }}"
+
+- name: reconfigure debsecan package (/etc/default/debsecan)
+  ansible.builtin.template:
+    src: "debsecan.j2"
+    dest: "/etc/default/debsecan"
+    owner: "root"
+    group: "root"
+    mode: "0644"
+    force: true
diff --git a/tasks/install-rkhunter.yml b/tasks/install_rkhunter.yml
similarity index 69%
rename from tasks/install-rkhunter.yml
rename to tasks/install_rkhunter.yml
index 410c1c7a2db95b51381b10ce35baa1402cd997f6..4a2dd994226437852ee2c2ef07526cd7cb8a0ec8 100644
--- a/tasks/install-rkhunter.yml
+++ b/tasks/install_rkhunter.yml
@@ -2,33 +2,40 @@
 # install rkhunter 1.4.4 due to CVE-2017-7480 (https://www.cvedetails.com/cve/CVE-2017-7480/)
 # version restriction can be removed once a higher version becomes available
 - name: install rkhunter
-  package:
+  ansible.builtin.package:
     name: "rkhunter"
     state: present
   tags: [apt]
 
+- name: create rkhunter log dir
+  ansible.builtin.file:
+    path: "/var/log/rkhunter/"
+    state: directory
+    owner: "root"
+    group: "root"
+    mode: "0750"
+
+- name: check if rkhunter logfile exists
+  ansible.builtin.stat:
+    path: "/var/log/rkhunter/rkhunter.log"
+  register: rkhunter_log_exists
+
 - name: create /var/log/rkhunter/rkhunter.log if it doesn't exist, so logrotate doesn't fail
-  file:
-    path: "{{ item.path }}"
-    state: "{{ item.state }}"
+  ansible.builtin.file:
+    path: "/var/log/rkhunter/rkhunter.log"
+    state: touch    # "touch" is never idempotent, so we have to rely on a precheck to see if we need to run this task
     owner: "root"
     group: "root"
-    mode: "{{ item.mode }}"
-  loop:
-    - path: "/var/log/rkhunter/"
-      mode: "0750"
-      state: directory
-    - path: "/var/log/rkhunter/rkhunter.log"
-      mode: "0640"
-      state: touch
+    mode: "0640"
+  when: not rkhunter_log_exists.stat.exists
 
 - name: configure /etc/default/rkhunter
-  lineinfile:
+  ansible.builtin.lineinfile:
     path: "/etc/default/rkhunter"
     regexp: "{{ item.regexp }}"
     line: "{{ item.line }}"
     # validate: rkhunter --config-check --configfile %s
-  with_items:
+  loop:
     # - regexp: '^# APT_AUTOGEN="false"'
     #   line: 'APT_AUTOGEN="yes"'
     - regexp: '^APT_AUTOGEN="false"'
@@ -38,7 +45,7 @@
   when: ansible_distribution == "Debian"
 
 - name: create rkhunter config directory
-  file:
+  ansible.builtin.file:
     path: "/etc/rkhunter.d"
     state: "directory"
     owner: "root"
@@ -46,9 +53,10 @@
     mode: 0755
 
 - name: configure /etc/rkhunter.d/rkhunter.local.conf
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/rkhunter.d/rkhunter.local.conf"
-    create: "yes"
+    mode: "0644"
+    create: true
     block: |
       # Default: UPDATE_MIRRORS=0
       UPDATE_MIRRORS=1
@@ -103,20 +111,34 @@
       WEB_CMD=""
     # validate: rkhunter --config-check --configfile %s
 
+- name: find rkhunter systemd units so we don't have to hardcode their names in the loops
+  ansible.builtin.find:
+    path: "/etc/systemd/user/"
+    pattern: "rkhunter.*"
+  register: rkhunter_units
+
+- name: check if rkhunter Systemd units are already disabled
+  ansible.builtin.command: "systemctl is-enabled rkhunter.{{ item.path | basename }}"
+  loop: "{{ rkhunter_units.files }}"
+  register: rkhunter_disabled
+  changed_when: false
+  failed_when:
+    - rkhunter_disabled.stdout != "enabled"
+    - rkhunter_disabled.stdout != "disabled"
+    - '"No such file or directory" not in rkhunter_disabled.stderr"'
+
 - name: stop & disable RKhunter service unit & timer
-  systemd:
-    name: "rkhunter.{{ item }}"
+  ansible.builtin.systemd:
+    name: "{{ item.item.path }}"
     enabled: "false"
     state: stopped
-  loop:
-    - "service"
-    - "timer"
-  when: ansible_distribution == "Debian"
-  tags: [molecule-notest]
-  ignore_errors: "yes"
+  loop: "{{ rkhunter_disabled.results }}"
+  when:
+    - ansible_distribution == "Debian"
+    - item.stdout != "disabled"
 
 - name: remove Rkhunter service unit & timer
-  file:
+  ansible.builtin.file:
     path: "/etc/systemd/user/rkhunter.{{ item }}"
     state: absent
   loop:
@@ -126,7 +148,7 @@
 
 # Unitfiles neu einlesen (implizit mit enable), Services sofort starten & automatisch bei jedem Booten starten
 # - name: Service automatisch bei jedem Booten starten - rkhunter.service
-#   command: systemctl enable "{{ item }}"
+#   ansible.builtin.command: systemctl enable "{{ item }}"
 #   loop:
 #     - "/etc/systemd/user/rkhunter.service"
 #     - "/etc/systemd/user/rkhunter.timer"
diff --git a/tasks/main.yml b/tasks/main.yml
index ada0022fe45161bb37617c8d4666f1141d6c89b6..52ab1ea8cd73e0ee6ad13bfafd65d8a676745020 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:
     - "fail2ban.vault"
     - "iptables.vault"
@@ -13,56 +13,56 @@
 
 # install hardened server configuration
 - name: configure root
-  import_tasks: configure-root.yml
+  ansible.builtin.import_tasks: "configure_root.yml"
   tags: [users]
 
 - name: configure kernel parameters
-  import_tasks: ./configure-kernel-parameters.yml
+  ansible.builtin.import_tasks: "configure_kernel_parameters.yml"
   tags: [kernel]
 
 - name: configure portable storage drivers
-  import_tasks: ./configure-portable-storage.yml
+  ansible.builtin.import_tasks: "configure_portable_storage.yml"
   tags: [usb]
 
 - name: configute SSH hardening
-  import_tasks: ./configure-ssh-hardening.yml
+  ansible.builtin.import_tasks: "configure_ssh_hardening.yml"
   tags: [ssh]
 
 - name: configure fail2ban
-  import_tasks: ./configure-fail2ban.yml
+  ansible.builtin.import_tasks: "configure_fail2ban.yml"
   tags: [fail2ban, ssh]
 
 - name: configure $HOME permissions
-  import_tasks: ./configure-home-permissions.yml
+  ansible.builtin.import_tasks: "configure_home_permissions.yml"
   tags: [users]
 
 - name: configure PAM
-  import_tasks: ./configure-pam.yml
+  ansible.builtin.import_tasks: "configure_pam.yml"
   tags: [pam]
 
 - name: install iptables filter rules
-  import_tasks: ./configure-iptables.yml
+  ansible.builtin.import_tasks: "configure_iptables.yml"
   tags: [iptables]
 
 - name: include rkhunter install task
-  import_tasks: ./install-rkhunter.yml
+  ansible.builtin.import_tasks: "install_rkhunter.yml"
   tags: [rkhunter]
 
 - name: include ClamAV install task
-  import_tasks: ./install-clamav.yml
+  ansible.builtin.import_tasks: "install_clamav.yml"
   tags: [clamav]
 
 - name: include auditd install task
-  import_tasks: ./install-auditd.yml
+  ansible.builtin.import_tasks: "install_auditd.yml"
   tags: [auditd]
 
 - name: install debsecan
-  import_tasks: ./install-debsecan.yml
+  ansible.builtin.import_tasks: "install_debsecan.yml"
   when: ansible_os_family == "Debian"
   tags: [debsecan]
 
 - name: install security-related packages (debian-goodies, debsums, libpam-cracklib, libpam-tmpdir)
-  apt:
+  ansible.builtin.apt:
     name: [
       'debian-goodies',
       'debsums',
@@ -74,12 +74,13 @@
   tags: [apt]
 
 - name: autoclean & autoremove
-  apt:
-    autoclean: "yes"
-    autoremove: "yes"
+  ansible.builtin.apt:
+    autoclean: true
+    autoremove: true
   when: ansible_os_family == "Debian"
   tags: [apt]
 
 - name: Flush handlers am Ende der Rolle
-  meta: flush_handlers
+  ansible.builtin.meta: flush_handlers
+  changed_when: false
   tags: [always]