diff --git a/framadate/defaults/main.yml b/framadate/defaults/main.yml
deleted file mode 100644
index 0a872887bf620501cd3d7c4b42ec87458d090b22..0000000000000000000000000000000000000000
--- a/framadate/defaults/main.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-
-framadate_name: "framadate"
diff --git a/framadate/tasks/main.yml b/framadate/tasks/main.yml
deleted file mode 100644
index 7603adbbf0260be7665a6316ce73aed6e4974635..0000000000000000000000000000000000000000
--- a/framadate/tasks/main.yml
+++ /dev/null
@@ -1,52 +0,0 @@
----
-
-- name: install packages
-  apt:
-    name:
-      - php
-      - php-mbstring
-      - php-mysql
-      - php-intl
-      - composer
-
-- name: ensure a group for framadate exist
-  group:
-    name: "{{framadate_name}}"
-    state: present
-    system: true
-
-- name: ensure a user for framadate exist
-  user:
-    name: "{{framadate_name}}"
-    group: "{{framadate_name}}"
-    state: present
-    system: true
-    shell: /usr/bin/nologin
-    home: "/var/www/{{framadate_name}}"
-    createhome: false
-
-- name: ensure the directory for framadate exist
-  file:
-    path: "/var/www/{{framadate_name}}"
-    state: directory
-    owner: "{{framadate_name}}"
-    group: "{{framadate_name}}"
-    mode: '0755'
-
-- name: create the mysql database
-  mysql_db:
-    name: "{{framadate_name}}"
-    state: present
-    login_user: root
-    login_password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql')}}"
-  no_log: true
-
-- name: create mysql db user
-  mysql_user:
-    name: "{{framadate_name}}"
-    password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql-{{framadate_name}} create=true length=20')}}"
-    state: present
-    login_user: root
-    login_password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql')}}"
-    priv: "{{framadate_name}}.*:ALL"
-  no_log: true
diff --git a/phpwebapps/defaults/main.yml b/phpwebapps/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23ed46688efb7cbeb31f64490f58c221fbc58f18
--- /dev/null
+++ b/phpwebapps/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+
+phpwebapps: []
+
+# phpwebapps:
+#   - name: termine
+#     directory: /var/www/termine (default)
+#     packages: ["php-mbstring", "php-intl"]
+#     url: "https://example.com/download/genericphpsoftware.zip
+#     checksum: "sha256:abc123…"
diff --git a/phpwebapps/files/check-phpwebapp-update.sh b/phpwebapps/files/check-phpwebapp-update.sh
new file mode 100644
index 0000000000000000000000000000000000000000..eb49d8e465981aa3e73923e5789f8aa653b3618f
--- /dev/null
+++ b/phpwebapps/files/check-phpwebapp-update.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+if [ "$#" -ne 4 ]; then
+    echo "Usage: $0 Name URL pattern version" >&2
+    exit 1
+fi
+
+software=$1
+url=$2
+pattern=$3
+version=$4
+
+page=$(curl "$url" 2>/dev/null)
+if [[ $? -ne 0 ]]; then
+	echo "Querying ${url} (for ${software}) failed."
+	exit 1
+fi
+match=$(echo $page | grep -Po "${pattern}")
+if [[ $? -ne 0 ]]; then
+	echo "Version pattern '${pattern}' not found in ${url} (for ${software})."
+	exit 1
+fi
+echo $match | grep -qF $version
+if [[ $? -ne 0 ]]; then
+	echo "${software} requires update: \"${version}\" does not match \"${match}\"."
+	exit 0
+fi
+exit 0
diff --git a/phpwebapps/tasks/main.yml b/phpwebapps/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d884ebb8214919981c0ec94d71c5228defe47d7c
--- /dev/null
+++ b/phpwebapps/tasks/main.yml
@@ -0,0 +1,120 @@
+---
+
+- name: install packages
+  apt:
+    name:
+      - php
+      - php-mbstring
+      - php-mysql
+      - php-intl
+      - composer
+
+- name: install additional packages for the php sites
+  apt:
+    name: "{{item.packages}}"
+  loop: "{{phpwebapps}}"
+  when: item.packages is defined
+  loop_control:
+    label: "{{item.name}}"
+
+- name: ensure groups for the php sites exist
+  group:
+    name: "{{item.name}}"
+    state: present
+    system: true
+  loop: "{{phpwebapps}}"
+  loop_control:
+    label: "{{item.name}}"
+
+- name: ensure groups for the php sites exist
+  user:
+    name: "{{item.name}}"
+    group: "{{item.name}}"
+    state: present
+    system: true
+    shell: /usr/bin/nologin
+    home: "{{item.directory|default('/var/www/' + item.name)}}"
+    createhome: false
+  loop: "{{phpwebapps}}"
+
+- name: ensure directories for the php sites exist
+  file:
+    path: "{{item.directory|default('/var/www/' + item.name)}}"
+    state: directory
+    owner: "{{item.name}}"
+    group: "{{item.name}}"
+    mode: '0755'
+  loop: "{{phpwebapps}}"
+  loop_control:
+    label: "{{item.name}}"
+
+- name: create the mysql database
+  mysql_db:
+    name: "{{item.name}}"
+    state: present
+    login_user: root
+    login_password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql')}}"
+  no_log: true
+  loop: "{{phpwebapps}}"
+  loop_control:
+    label: "{{item.name}}"
+
+- name: create mysql db user
+  mysql_user:
+    name: "{{item.name}}"
+    password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql-{{item.name}} create=true length=20')}}"
+    state: present
+    login_user: root
+    login_password: "{{lookup('passwordstore', 'db/{{ansible_hostname}}-mysql')}}"
+    priv: "{{item.name}}.*:ALL"
+  no_log: true
+  loop: "{{phpwebapps}}"
+  loop_control:
+    label: "{{item.name}}"
+
+- name: download the software
+  get_url:
+    url: "{{item.url}}"
+    dest: "/tmp/{{item.name}}{{item.url|splitext|last}}"
+    checksum: "{{item.checksum}}"
+  loop: "{{phpwebapps}}"
+  when: item.url is defined and item.checksum is defined
+  loop_control:
+    label: "{{item.name}}"
+
+- name: unpack the software
+  unarchive:
+    src: "/tmp/{{item.name}}{{item.url|splitext|last}}"
+    dest: "{{item.directory|default('/var/www/' + item.name)}}"
+    remote_src: true
+    owner: "{{item.name}}"
+    group: "www-data"
+    mode: '0755'
+  loop: "{{phpwebapps}}"
+  when: item.url is defined and item.checksum is defined
+  loop_control:
+    label: "{{item.name}}"
+
+- name: install update-check-script
+  copy:
+    src: check-phpwebapp-update.sh
+    dest: /usr/local/bin/
+    owner: root
+    group: root
+    mode: '0755'
+  loop: "{{phpwebapps}}"
+  when: item.update_check is defined
+  loop_control:
+    label: "{{item.name}}"
+
+- name: regularly check for updates
+  template:
+    src: crontab.j2
+    dest: "/etc/cron.daily/phpwebapp-{{item.name}}"
+    owner: root
+    group: root
+    mode: '0755'
+  loop: "{{phpwebapps}}"
+  when: item.update_check is defined
+  loop_control:
+    label: "{{item.name}}"
diff --git a/phpwebapps/templates/crontab.j2 b/phpwebapps/templates/crontab.j2
new file mode 100644
index 0000000000000000000000000000000000000000..f266bf1b7bde4465235889c6174ab3e2d9c0079d
--- /dev/null
+++ b/phpwebapps/templates/crontab.j2
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+/usr/local/bin/check-phpwebapp-update.sh '{{item.name}}' '{{item.update_check.url}}' '{{item.update_check.pattern}}' '{{item.update_check.current_version}}'