diff --git a/.ansible-lint-ignore b/.ansible-lint-ignore
new file mode 100644
index 0000000000000000000000000000000000000000..ea75a283414bef92c5e07a6ae675f66909aa1151
--- /dev/null
+++ b/.ansible-lint-ignore
@@ -0,0 +1,4 @@
+# This file contains ignores rule violations for ansible-lint
+hostkey-provider/tasks/main.yml ignore-errors
+shell/tasks/shell.yml schema[moves]
+ssh-server/tasks/main.yml no-changed-when
diff --git a/apt/defaults/main.yml b/apt/defaults/main.yml
index 8194ece7fb21366c2170421ec5ed743dc6f2c39a..890928469846b37ad6fd128b02afe19b73c30fe2 100644
--- a/apt/defaults/main.yml
+++ b/apt/defaults/main.yml
@@ -1,5 +1,4 @@
 ---
-# file: common/apt/defaults/main.yml
 
 apt_mirror: 'http://ftp.halifax.rwth-aachen.de/debian/'
 apt_mirror_security: 'http://security.debian.org/debian-security/'
diff --git a/apt/handlers/main.yml b/apt/handlers/main.yml
index 7f85265d8b294c5b73bfaadcfe2c9623bcfe134b..5f9a2a5fa15dd84d22492bb74ddc28c20995271f 100644
--- a/apt/handlers/main.yml
+++ b/apt/handlers/main.yml
@@ -1,5 +1,5 @@
 ---
-# file: roles/common/handlers/main.yml
 
 - name: update apt cache
-  apt: update_cache=yes
+  apt:
+    update_cache: true
diff --git a/apt/tasks/repositories.yml b/apt/tasks/repositories.yml
index 5c48ba2dfdb78054022e3766868b3ea45adc0a83..88ec283d74b00e64963b33b99aed86839742708e 100644
--- a/apt/tasks/repositories.yml
+++ b/apt/tasks/repositories.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/tasks/repositories.yml
 # yamllint disable rule:line-length
 
 - name: enable debian repositories
diff --git a/basic-system/handlers/main.yml b/basic-system/handlers/main.yml
index 4213194c888ebfe53d7eade3f8d4a5443bf1386d..aec32443c1fa62ac3cd8e093c8f9d1eae8db9eaa 100644
--- a/basic-system/handlers/main.yml
+++ b/basic-system/handlers/main.yml
@@ -1,11 +1,14 @@
 ---
-# file: roles/common/handlers/main.yml
 
 - name: restart rsyslogd
-  service: name=rsyslog state=restarted
+  service:
+    name: rsyslog
+    state: restarted
 
 - name: restart systemd-journald
-  systemd: name=systemd-journald.service state=restarted
+  systemd:
+    name: systemd-journald.service
+    state: restarted
 
 - name: configure journal directory
   command: systemd-tmpfiles --create --prefix /var/log/journal
@@ -14,14 +17,16 @@
   command: update-initramfs -u
 
 - name: gather network facts once again
-  setup: gather_subset=network
+  setup:
+    gather_subset: network
 
 - name: gather minimal standard facts once again
-  setup: gather_subset=min
+  setup:
+    gather_subset: min
 
 - name: Remove pve directory from etckeeper repo
-  # 303 wants us to use the ansible git module, but it doesn’t
+  # ansible-lint wants us to use the ansible git module, but it doesn’t
   # provide that feature
-  shell:  # noqa 303
+  shell:  # noqa: command-instead-of-module
     chdir: /etc
     cmd: git rm --cached -r -- pve && git commit -m "'Remove pve directory'"
diff --git a/basic-system/tasks/logging.yml b/basic-system/tasks/logging.yml
index f3753264469286871264d49bd3fbaab7015b4931..3692cfbd2dbec62dae14fc685286a12d776e16cf 100644
--- a/basic-system/tasks/logging.yml
+++ b/basic-system/tasks/logging.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/task/logging.yml
 
 - name: restrict dmesg access to only root
   sysctl:
@@ -24,7 +23,9 @@
     - journal
 
 - name: ensure systemd journal is persistent
-  file:
+  # Not setting permissions here is okay because the handler will correct it via
+  # tmpfiles
+  file:  # noqa: risky-file-permissions
     path: /var/log/journal
     state: directory
   when:
diff --git a/basic-system/tasks/main.yml b/basic-system/tasks/main.yml
index 0781cb0fd57a2f2883a450e189d33c3dc4e34924..349bee9927a178024c0d90afc85cde6f49859798 100644
--- a/basic-system/tasks/main.yml
+++ b/basic-system/tasks/main.yml
@@ -32,6 +32,9 @@
     path: /etc/.gitignore
     line: /pve
     create: true
+    owner: root
+    group: root
+    mode: "0600"
   when: etckeeper and pve_present.stat.exists
   notify:
     - Remove pve directory from etckeeper repo
diff --git a/basic-system/tasks/network.yml b/basic-system/tasks/network.yml
index 35b4795c7c70946621180614b58269c0d4f5a0c6..dd9f6cffc856351b15b0b206c841e2a67bd438a2 100644
--- a/basic-system/tasks/network.yml
+++ b/basic-system/tasks/network.yml
@@ -52,6 +52,9 @@
   template:
     src: hosts.j2
     dest: /etc/hosts
+    owner: root
+    group: root
+    mode: "0644"
   when: not hosts_precious
   notify:
     - gather network facts once again
diff --git a/hostkey-provider/handlers/main.yml b/hostkey-provider/handlers/main.yml
index c45157784f8c0eea62f12b740279f416ab62f7b8..de09f51945c8f60772424ee1b7037c80ea422d0e 100644
--- a/hostkey-provider/handlers/main.yml
+++ b/hostkey-provider/handlers/main.yml
@@ -1,4 +1,5 @@
 ---
 
 - name: re-read local facts for new hostkeys
-  setup: filter=ansible_local
+  setup:
+    filter: ansible_local
diff --git a/hostkey-provider/tasks/main.yml b/hostkey-provider/tasks/main.yml
index 2256bfe820c8ec642b5869ee21b9e22571ca0e54..7e29307c79890613e5577c72438aba9a80dee5dc 100644
--- a/hostkey-provider/tasks/main.yml
+++ b/hostkey-provider/tasks/main.yml
@@ -6,6 +6,9 @@
     section: collected
     option: "{{ item.key }}"
     value: "{{ item.value }}"
+    owner: root
+    group: root
+    mode: "0644"
   with_items:
     - {key: 'ipv4', value: "{{ ansible_all_ipv4_addresses|join(',') }}"}
     - {key: 'ipv6', value: "{{ ansible_all_ipv6_addresses|join(',') }}"}
diff --git a/localization/handlers/main.yml b/localization/handlers/main.yml
index cc5308cff87ca7a36fe39cdf5b26045da29a1a9f..edcbd1db563047a76d0faa7eb5b25d2effb8456b 100644
--- a/localization/handlers/main.yml
+++ b/localization/handlers/main.yml
@@ -1,11 +1,14 @@
 ---
-# file: roles/common/handlers/main.yml
 
 - name: restart ntpd
-  service: name=ntp state=restarted
+  service:
+    name: ntp
+    state: restarted
 
 - name: restart timesyncd
-  service: name=systemd-timesyncd state=restarted
+  service:
+    name: systemd-timesyncd
+    state: restarted
 
 - name: update timezone
   command: dpkg-reconfigure --frontend noninteractive tzdata
diff --git a/localization/tasks/ntpd.yml b/localization/tasks/ntpd.yml
index fe7922cef8c159c07e5fe16e6a06fc16e8b428dd..68e48909dcad7460c4d54126ae1648323b68bfba 100644
--- a/localization/tasks/ntpd.yml
+++ b/localization/tasks/ntpd.yml
@@ -1,5 +1,4 @@
 ---
-# file: localization/tasks/ntpd.yml
 
 - name: ensure ntpd is installed
   apt:
@@ -29,6 +28,9 @@
   template:
     src: ntp.conf.j2
     dest: /etc/ntp.conf
+    owner: root
+    group: root
+    mode: "0644"
   notify:
     - restart ntpd
   tags:
diff --git a/localization/tasks/timesyncd.yml b/localization/tasks/timesyncd.yml
index 0dfa0127ed1f1b2b645a75045e22575c2ff67248..384edb9aff755851b8f11fd09ded9d30099b047f 100644
--- a/localization/tasks/timesyncd.yml
+++ b/localization/tasks/timesyncd.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/tasks/ntp.yml
 
 - name: ensure there is no ntpd interfering
   apt:
@@ -13,6 +12,9 @@
   template:
     src: timesyncd.conf.j2
     dest: /etc/systemd/timesyncd.conf
+    owner: root
+    group: root
+    mode: "0644"
   notify:
     - restart timesyncd
   tags:
diff --git a/shell/handlers/main.yml b/shell/handlers/main.yml
index 4a2bbc150b6fc5bbc127f9c9e364db42dc43b84b..193f48183d1aadc0bfd9d217500af9a87b613271 100644
--- a/shell/handlers/main.yml
+++ b/shell/handlers/main.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/handlers/main.yml
 
 - name: rerun depmod
   command: depmod -ae
diff --git a/shell/tasks/shell.yml b/shell/tasks/shell.yml
index a0f791104088b4bb106b99650c2ef2c9375df25b..bf794cd252304601ede685bd3727c4ae63429dfb 100644
--- a/shell/tasks/shell.yml
+++ b/shell/tasks/shell.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/tasks/shell.yml
 
 - name: ensure installation of basic shell commands
   apt:
@@ -69,7 +68,9 @@
     - shell
 
 - name: ensure we have an up-to-date version of grml-zsh-config
-  get_url:
+  # Skip risky-file-permissions check because this runs locally, and we don’t
+  # even know who the local user running Ansible is.
+  get_url:  # noqa: risky-file-permissions
     url: https://www.archlinux.org/packages/extra/any/grml-zsh-config/download/
     dest: "{{ role_path }}/files/"
   delegate_to: localhost
@@ -99,6 +100,9 @@
   copy:
     src: /etc/skel/.zshrc
     dest: /root/.zshrc
+    owner: root
+    group: root
+    mode: "0644"
     remote_src: true
   tags:
     - config
diff --git a/shell/tasks/sudo.yml b/shell/tasks/sudo.yml
index 5f65bd4fd045b3e1ee708fabbbf05f4f1b386aed..517f00484e075c7e619ed5a7a6db530ca618563e 100644
--- a/shell/tasks/sudo.yml
+++ b/shell/tasks/sudo.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/tasks/sudo.yml
 
 - name: ensure sudo is installed
   apt:
diff --git a/ssh-server/defaults/main.yml b/ssh-server/defaults/main.yml
index 90fc1a5aaa49bb7f049355695ea3b4aa7e4db421..ac01ab4090816dcedd61cf0aa677d3cc60505b25 100644
--- a/ssh-server/defaults/main.yml
+++ b/ssh-server/defaults/main.yml
@@ -1,5 +1,4 @@
 ---
-# file: common/ssh-server/defaults/main.yml
 
 ssh_authorized_keys: "{{ inventory_dir }}/files/keys"
 ssh_mkhomedir: true
diff --git a/ssh-server/tasks/main.yml b/ssh-server/tasks/main.yml
index ef491fdfec4e73bb932dfc437aabb84a387af5a5..c547c9a5c6e7d0a1c59331828148ca7b4a3e15a7 100644
--- a/ssh-server/tasks/main.yml
+++ b/ssh-server/tasks/main.yml
@@ -1,5 +1,4 @@
 ---
-# file: roles/common/tasks/sshd.yml
 
 - name: ensure sshd is installed
   apt: