Initialize sf-jobs repository
diff --git a/roles/upload-pages/README.rst b/roles/upload-pages/README.rst
new file mode 100644
index 0000000..05b2581
--- /dev/null
+++ b/roles/upload-pages/README.rst
@@ -0,0 +1,28 @@
+Publish contents of ``{{ zuul.executor.work_root }}/pages/`` dir using
+rsync over ssh to a remote fileserver that has previously been added to
+the inventory by :zuul:role:`add-fileserver`.
+
+The contents is published on the static webserver using a
+Apache virtual host definition.
+
+This uploads pages to a static webserver using SSH.
+
+**Role Variables**
+
+.. zuul:rolevar:: zuul_pagesserver_root
+   :default: /var/www/pages/
+
+   The root path to the pages on the static webserver.
+
+.. zuul:rolevar:: zuul_pagesvhosts_root
+   :default: /etc/httpd/pages.d/
+
+   The root path where apache's vhost files are stored on the static webserver.
+
+.. zuul:rolevar:: vhost_name
+
+   The vhost name to use to fill the vhost template.
+
+.. zuul:rolevar:: fqdn
+
+   The fqdn to use to fill the vhost template.
diff --git a/roles/upload-pages/tasks/main.yaml b/roles/upload-pages/tasks/main.yaml
new file mode 100644
index 0000000..ad73f34
--- /dev/null
+++ b/roles/upload-pages/tasks/main.yaml
@@ -0,0 +1,44 @@
+- name: Create project pages directory
+  file:
+    path: "{{ zuul_pagesserver_root }}/{{ zuul.project.name }}"
+    state: directory
+    recurse: yes
+    mode: '0775'
+
+- name: Check fqdn and vhost_name variables
+  fail:
+    msg: "Fail as fqdn and vhost_name are not set"
+  when: (not vhost_name is defined or not vhost_name or
+         not fqdn is defined or not fqdn)
+
+- name: Check for letsencrypt TLS certificate
+  stat:
+    path: "/etc/letsencrypt/pem/{{ vhost_name }}.{{ fqdn }}.pem"
+  register: tls_letsencrypt_cert
+
+- name: Check for static TLS certificate
+  stat:
+    path: "/etc/pki/tls/certs/{{ vhost_name }}.{{ fqdn }}.crt"
+  register: tls_static_cert
+
+- name: Create vhost file
+  template:
+    src: templates/vhost.conf.j2
+    dest: "{{ zuul_pagesvhosts_root }}/pages-{{ zuul.project.name | regex_replace('/', '_') | regex_replace('\\.\\.', '') }}.conf"
+    mode: '0644'
+  register: apache_conf
+
+- name: Upload website to publication server
+  synchronize:
+    src: "{{ zuul.executor.log_root }}/pages/"
+    dest: "{{ zuul_pagesserver_root }}/{{ zuul.project.name }}/"
+    delete: yes
+    recursive: yes
+  no_log: true
+
+# pageuser must be authorized to reload
+# httpd via sudo. However using become and systemd
+# facility fails to match the sudoerd rule.
+- name: reload httpd
+  command: sudo /bin/systemctl reload httpd
+  when: apache_conf is changed
diff --git a/roles/upload-pages/templates/vhost.conf.j2 b/roles/upload-pages/templates/vhost.conf.j2
new file mode 100644
index 0000000..0ec2e82
--- /dev/null
+++ b/roles/upload-pages/templates/vhost.conf.j2
@@ -0,0 +1,40 @@
+<VirtualHost *:80>
+    ServerName {{ vhost_name }}.{{ fqdn }}
+
+    Alias /.well-known/acme-challenge /etc/letsencrypt/challenges/{{ vhost_name }}.{{ fqdn }}
+    <Directory /etc/letsencrypt/challenges/{{ vhost_name }}.{{ fqdn }}>
+      Require all granted
+    </Directory>
+
+{% if tls_letsencrypt_cert.stat.exists or tls_static_cert.stat.exists %}
+    RewriteEngine On
+    RewriteCond %{HTTPS} off
+    RewriteCond %{REMOTE_HOST} !{{ vhost_name }}.{{ fqdn }}$
+    RewriteCond %{REQUEST_URI} !\.well-known/acme-challenge
+    RewriteRule (.*) https://{{ vhost_name }}.{{ fqdn }}%{REQUEST_URI} [R=301,L]
+{% endif %}
+
+    Alias / /var/www/pages/{{ zuul.project.name }}/
+    CustomLog "logs/{{ vhost_name }}.{{ fqdn }}_access_log" combined
+    ErrorLog "logs/{{ vhost_name }}.{{ fqdn }}_error_log"
+</VirtualHost>
+
+{% if tls_letsencrypt_cert.stat.exists or tls_static_cert.stat.exists %}
+<VirtualHost *:443>
+    ServerName {{ vhost_name }}.{{ fqdn }}
+    SSLEngine on
+    {% if tls_letsencrypt_cert.stat.exists %}
+    SSLCertificateFile /etc/letsencrypt/pem/{{ vhost_name }}.{{ fqdn }}.pem
+    SSLCertificateChainFile /etc/letsencrypt/pem/lets-encrypt-x3-cross-signed.pem
+    SSLCertificateKeyFile /etc/letsencrypt/private/{{ vhost_name }}.{{ fqdn }}.key
+    {% else %}
+    SSLCertificateFile /etc/pki/tls/certs/{{ vhost_name }}.{{ fqdn }}.crt
+    SSLCertificateChainFile /etc/pki/tls/certs/{{ vhost_name }}.{{ fqdn }}-chain.crt
+    SSLCertificateKeyFile  /etc/pki/tls/private/{{ vhost_name }}.{{ fqdn }}.key
+    {% endif %}
+
+    Alias / /var/www/pages/{{ vhost_name }}.{{ fqdn }}/
+    CustomLog "logs/{{ vhost_name }}.{{ fqdn }}_access_log" combined
+    ErrorLog "logs/{{ vhost_name }}.{{ fqdn }}_error_log"
+</VirtualHost>
+{% endif %}
diff --git a/roles/upload-pages/vars/main.yaml b/roles/upload-pages/vars/main.yaml
new file mode 100644
index 0000000..275da4e
--- /dev/null
+++ b/roles/upload-pages/vars/main.yaml
@@ -0,0 +1,2 @@
+zuul_pagesserver_root: /var/www/pages/
+zuul_pagesvhosts_root: /etc/httpd/pages.d/