From 51d29a872397c77633a50ea60f24dd1d05315721 Mon Sep 17 00:00:00 2001
From: Maarten de Waard <maarten@greenhost.nl>
Date: Mon, 14 Jun 2021 12:42:45 +0000
Subject: [PATCH] Try Flux 2

---
 .gitignore                                    |   1 +
 .gitlab-ci.yml                                | 291 +++++++++---------
 ansible/group_vars/all/oas.yml                |   3 +
 ansible/group_vars/all/settings.yml.example   |  24 --
 ansible/install-openappstack.yml              |   4 -
 ansible/roles/apps/tasks/core.yml             |  30 --
 ansible/roles/apps/tasks/nginx.yml            |   0
 .../apps/templates/settings/ingress.yaml      |  22 --
 .../templates/settings/single-sign-on.yaml    | 127 --------
 .../roles/compatibility-checks/tasks/main.yml |   2 -
 ansible/roles/local-flux/defaults/main.yml    |   2 -
 ansible/roles/local-flux/files/Chart.yaml     |   6 -
 ansible/roles/local-flux/files/git-hook.sh    |   3 -
 ansible/roles/local-flux/files/nginx.yaml     |  70 -----
 ansible/roles/local-flux/files/values.yaml    |   6 -
 ansible/roles/local-flux/tasks/main.yml       |  70 -----
 ansible/roles/setup-kubernetes/tasks/flux.yml |  43 +++
 ansible/roles/setup-kubernetes/tasks/main.yml |   1 +
 charts/oas-secrets/.helmignore                |  23 ++
 charts/oas-secrets/Chart.yaml                 |  28 ++
 charts/oas-secrets/templates/NOTES.txt        |   1 +
 charts/oas-secrets/templates/_helpers.tpl     |  84 +++++
 .../oas-alertmanager-basic-auth.yaml          |  12 +
 .../oas-kube-prometheus-stack-variables.yaml  |   7 +
 .../templates/oas-nextcloud-variables.yaml    |  12 +
 .../templates/oas-oauth-variables.yaml        |  11 +
 .../templates/oas-prometheus-basic-auth.yaml  |  11 +
 .../templates/oas-rocketchat-variables.yaml   |   8 +
 .../oas-single-sign-on-variables.yaml         |  10 +
 .../templates/oas-wordpress-variables.yaml    |   9 +
 charts/oas-secrets/values.yaml                |   6 +
 docs/advanced_installation.rst                |   4 +
 docs/design.md                                |   2 +
 docs/installation_instructions.rst            |   9 +-
 flux2/README.md                               |  37 +++
 .../apps/monitoring/eventrouter-release.yaml  |  27 ++
 .../kube-prometheus-stack-release.yaml        | 278 +++++++++++++++++
 flux2/apps/monitoring/kustomization.yaml      |  11 +
 flux2/apps/monitoring/loki-configmap.yaml     |  19 ++
 flux2/apps/monitoring/loki-release.yaml       |  63 ++++
 flux2/apps/monitoring/promtail-release.yaml   |  55 ++++
 flux2/apps/monitoring/pvc.yaml                |  13 +
 flux2/apps/nextcloud/kustomization.yaml       |   6 +
 flux2/apps/nextcloud/pvc.yaml                 |  26 ++
 flux2/apps/nextcloud/release.yaml             | 198 ++++++++++++
 flux2/apps/rocketchat/kustomization.yaml      |   5 +
 flux2/apps/rocketchat/release.yaml            | 141 +++++++++
 flux2/apps/velero/kustomization.yaml          |   6 +
 flux2/apps/velero/release.yaml                | 127 ++++++++
 flux2/apps/wordpress/kustomization.yaml       |   7 +
 flux2/apps/wordpress/pvc.yaml                 |  26 ++
 flux2/apps/wordpress/release.yaml             | 103 +++++++
 flux2/cluster/base/core.yaml                  |  23 ++
 flux2/cluster/base/infrastructure.yaml        |  14 +
 flux2/cluster/base/monitoring.yaml            |  30 ++
 .../cluster/optional/nextcloud/nextcloud.yaml |  30 ++
 .../optional/rocketchat/rocketchat.yaml       |  30 ++
 flux2/cluster/optional/velero/velero.yaml     |  26 ++
 .../cluster/optional/wordpress/wordpress.yaml |  25 ++
 flux2/cluster/test/all-optional.yaml          |  16 +
 flux2/cluster/test/base.yaml                  |  14 +
 .../base/cluster-issuer/cluster-issuer.yaml   |  18 ++
 .../base/cluster-issuer/kustomization.yaml    |   6 +
 flux2/core/base/metallb/kustomization.yaml    |   6 +
 flux2/core/base/metallb/release.yaml          |  27 ++
 .../base/single-sign-on/kustomization.yaml    |   7 +
 flux2/core/base/single-sign-on/pvc.yaml       |  12 +
 flux2/core/base/single-sign-on/release.yaml   | 149 +++++++++
 .../cert-manager/kustomization.yaml           |   6 +
 .../infrastructure/cert-manager/release.yaml  |  47 +++
 .../local-path-provisioner/kustomization.yaml |   6 +
 .../local-path-provisioner/release.yaml       |  37 +++
 .../namespaces/cert-manager.yaml              |   4 +
 .../namespaces/kustomization.yaml             |   8 +
 flux2/infrastructure/namespaces/oas-apps.yaml |   4 +
 flux2/infrastructure/namespaces/oas.yaml      |   4 +
 flux2/infrastructure/namespaces/velero.yaml   |   4 +
 flux2/infrastructure/nginx/kustomization.yaml |   6 +
 flux2/infrastructure/nginx/release.yaml       |  42 +++
 .../infrastructure/secrets/kustomization.yaml |   8 +
 flux2/infrastructure/secrets/release.yaml     |  16 +
 flux2/infrastructure/sources/bitnami.yaml     |   8 +
 flux2/infrastructure/sources/grafana.yaml     |   8 +
 flux2/infrastructure/sources/helm-stable.yaml |   8 +
 .../infrastructure/sources/ingress-nginx.yaml |   8 +
 flux2/infrastructure/sources/jetstack.yaml    |   8 +
 .../infrastructure/sources/kustomization.yaml |  16 +
 .../sources/local-path-provisioner.yaml       |  16 +
 flux2/infrastructure/sources/nextcloud.yaml   |  16 +
 .../sources/prometheus-community.yaml         |   8 +
 .../sources/single-sign-on.yaml               |  16 +
 .../infrastructure/sources/vmware-tanzu.yaml  |   8 +
 flux2/infrastructure/sources/wordpress.yaml   |  17 +
 install/ci-write-variable-files.sh            |  46 +++
 install/install-nextcloud.sh                  |  14 +
 install/install-openappstack.sh               |  17 +
 install/install-rocketchat.sh                 |  11 +
 install/install-velero.sh                     |  11 +
 install/install-wordpress.sh                  |  11 +
 .../kustomization.yaml                        |  29 ++
 install/nextcloud-values-override.yaml        |  10 +
 openappstack/__main__.py                      |   2 -
 openappstack/cluster.py                       |  73 +++--
 test/behave/features/environment.py           |   2 +-
 ....feature => kube-prometheus-stack.feature} |   6 +-
 test/behave/features/steps/steps.py           |  10 +-
 test/pytest.ini                               |   1 +
 test/pytest/test_app_deployments.py           |  73 ++++-
 test/pytest/test_certs.py                     |   2 +-
 test/pytest/test_prometheus.py                |   2 +-
 110 files changed, 2614 insertions(+), 558 deletions(-)
 delete mode 100644 ansible/roles/apps/tasks/nginx.yml
 delete mode 100644 ansible/roles/apps/templates/settings/ingress.yaml
 delete mode 100644 ansible/roles/apps/templates/settings/single-sign-on.yaml
 delete mode 100644 ansible/roles/local-flux/defaults/main.yml
 delete mode 100644 ansible/roles/local-flux/files/Chart.yaml
 delete mode 100644 ansible/roles/local-flux/files/git-hook.sh
 delete mode 100644 ansible/roles/local-flux/files/nginx.yaml
 delete mode 100644 ansible/roles/local-flux/files/values.yaml
 delete mode 100644 ansible/roles/local-flux/tasks/main.yml
 create mode 100644 ansible/roles/setup-kubernetes/tasks/flux.yml
 create mode 100644 charts/oas-secrets/.helmignore
 create mode 100644 charts/oas-secrets/Chart.yaml
 create mode 100644 charts/oas-secrets/templates/NOTES.txt
 create mode 100644 charts/oas-secrets/templates/_helpers.tpl
 create mode 100644 charts/oas-secrets/templates/oas-alertmanager-basic-auth.yaml
 create mode 100644 charts/oas-secrets/templates/oas-kube-prometheus-stack-variables.yaml
 create mode 100644 charts/oas-secrets/templates/oas-nextcloud-variables.yaml
 create mode 100644 charts/oas-secrets/templates/oas-oauth-variables.yaml
 create mode 100644 charts/oas-secrets/templates/oas-prometheus-basic-auth.yaml
 create mode 100644 charts/oas-secrets/templates/oas-rocketchat-variables.yaml
 create mode 100644 charts/oas-secrets/templates/oas-single-sign-on-variables.yaml
 create mode 100644 charts/oas-secrets/templates/oas-wordpress-variables.yaml
 create mode 100644 charts/oas-secrets/values.yaml
 create mode 100644 flux2/README.md
 create mode 100644 flux2/apps/monitoring/eventrouter-release.yaml
 create mode 100644 flux2/apps/monitoring/kube-prometheus-stack-release.yaml
 create mode 100644 flux2/apps/monitoring/kustomization.yaml
 create mode 100644 flux2/apps/monitoring/loki-configmap.yaml
 create mode 100644 flux2/apps/monitoring/loki-release.yaml
 create mode 100644 flux2/apps/monitoring/promtail-release.yaml
 create mode 100644 flux2/apps/monitoring/pvc.yaml
 create mode 100644 flux2/apps/nextcloud/kustomization.yaml
 create mode 100644 flux2/apps/nextcloud/pvc.yaml
 create mode 100644 flux2/apps/nextcloud/release.yaml
 create mode 100644 flux2/apps/rocketchat/kustomization.yaml
 create mode 100644 flux2/apps/rocketchat/release.yaml
 create mode 100644 flux2/apps/velero/kustomization.yaml
 create mode 100644 flux2/apps/velero/release.yaml
 create mode 100644 flux2/apps/wordpress/kustomization.yaml
 create mode 100644 flux2/apps/wordpress/pvc.yaml
 create mode 100644 flux2/apps/wordpress/release.yaml
 create mode 100644 flux2/cluster/base/core.yaml
 create mode 100644 flux2/cluster/base/infrastructure.yaml
 create mode 100644 flux2/cluster/base/monitoring.yaml
 create mode 100644 flux2/cluster/optional/nextcloud/nextcloud.yaml
 create mode 100644 flux2/cluster/optional/rocketchat/rocketchat.yaml
 create mode 100644 flux2/cluster/optional/velero/velero.yaml
 create mode 100644 flux2/cluster/optional/wordpress/wordpress.yaml
 create mode 100644 flux2/cluster/test/all-optional.yaml
 create mode 100644 flux2/cluster/test/base.yaml
 create mode 100644 flux2/core/base/cluster-issuer/cluster-issuer.yaml
 create mode 100644 flux2/core/base/cluster-issuer/kustomization.yaml
 create mode 100644 flux2/core/base/metallb/kustomization.yaml
 create mode 100644 flux2/core/base/metallb/release.yaml
 create mode 100644 flux2/core/base/single-sign-on/kustomization.yaml
 create mode 100644 flux2/core/base/single-sign-on/pvc.yaml
 create mode 100644 flux2/core/base/single-sign-on/release.yaml
 create mode 100644 flux2/infrastructure/cert-manager/kustomization.yaml
 create mode 100644 flux2/infrastructure/cert-manager/release.yaml
 create mode 100644 flux2/infrastructure/local-path-provisioner/kustomization.yaml
 create mode 100644 flux2/infrastructure/local-path-provisioner/release.yaml
 create mode 100644 flux2/infrastructure/namespaces/cert-manager.yaml
 create mode 100644 flux2/infrastructure/namespaces/kustomization.yaml
 create mode 100644 flux2/infrastructure/namespaces/oas-apps.yaml
 create mode 100644 flux2/infrastructure/namespaces/oas.yaml
 create mode 100644 flux2/infrastructure/namespaces/velero.yaml
 create mode 100644 flux2/infrastructure/nginx/kustomization.yaml
 create mode 100644 flux2/infrastructure/nginx/release.yaml
 create mode 100644 flux2/infrastructure/secrets/kustomization.yaml
 create mode 100644 flux2/infrastructure/secrets/release.yaml
 create mode 100644 flux2/infrastructure/sources/bitnami.yaml
 create mode 100644 flux2/infrastructure/sources/grafana.yaml
 create mode 100644 flux2/infrastructure/sources/helm-stable.yaml
 create mode 100644 flux2/infrastructure/sources/ingress-nginx.yaml
 create mode 100644 flux2/infrastructure/sources/jetstack.yaml
 create mode 100644 flux2/infrastructure/sources/kustomization.yaml
 create mode 100644 flux2/infrastructure/sources/local-path-provisioner.yaml
 create mode 100644 flux2/infrastructure/sources/nextcloud.yaml
 create mode 100644 flux2/infrastructure/sources/prometheus-community.yaml
 create mode 100644 flux2/infrastructure/sources/single-sign-on.yaml
 create mode 100644 flux2/infrastructure/sources/vmware-tanzu.yaml
 create mode 100644 flux2/infrastructure/sources/wordpress.yaml
 create mode 100755 install/ci-write-variable-files.sh
 create mode 100755 install/install-nextcloud.sh
 create mode 100755 install/install-openappstack.sh
 create mode 100755 install/install-rocketchat.sh
 create mode 100755 install/install-velero.sh
 create mode 100755 install/install-wordpress.sh
 create mode 100644 install/installation-kustomization/kustomization.yaml
 create mode 100644 install/nextcloud-values-override.yaml
 rename test/behave/features/{prometheus-stack.feature => kube-prometheus-stack.feature} (87%)

diff --git a/.gitignore b/.gitignore
index 138ce5dfc..8682767ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
 /test/behave/behave.ini
 /test/behave/rerun_failing.features
 /clusters
+/install/installation-kustomization/*.txt
 
 # Ignore files created during tests
 /test/behave/**/screenshots/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b2f0e1f66..b0c586f77 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -37,7 +37,6 @@ include:
   artifacts:
     paths:
       - clusters
-      - ./enabled_apps/$APP
     expire_in: 1 month
     when: always
     reports:
@@ -90,9 +89,9 @@ include:
 .nextcloud_rules:
   rules:
     - changes:
-        - flux/**/$APP*.yaml
-        - ansible/roles/apps/templates/settings/$APP.yaml
-        - ansible/roles/apps/tasks/$APP.yaml
+        - flux2/apps/$APP/*.yaml
+        - flux2/cluster/optional/$APP/*.yaml
+        - install/install-${APP}.sh
         - test/behave/features/$APP.feature
     - if: '$TRIGGER_JOBS =~ /enable-nextcloud/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-nextcloud/'
@@ -113,9 +112,9 @@ include:
 .rocketchat_rules:
   rules:
     - changes:
-        - flux/**/$APP*.yaml
-        - ansible/roles/apps/templates/settings/$APP.yaml
-        - ansible/roles/apps/tasks/$APP.yaml
+        - flux2/apps/$APP/*.yaml
+        - flux2/cluster/optional/$APP/*.yaml
+        - install/install-${APP}.sh
         - test/behave/features/$APP.feature
     - if: '$TRIGGER_JOBS =~ /enable-rocketchat/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-rocketchat/'
@@ -124,9 +123,9 @@ include:
 .single_sign_on_rules:
   rules:
     - changes:
-        - flux/**/$APP*.yaml
-        - ansible/roles/apps/templates/settings/$APP.yaml
-        - ansible/roles/apps/tasks/$APP.yaml
+        - flux2/core/base/$APP/*.yaml
+        - install/install-openappstack.sh
+        - test/behave/features/$APP.feature
     - if: '$TRIGGER_JOBS =~ /enable-single-sign-on/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-single-sign-on/'
     - if: '$CI_COMMIT_BRANCH == "master"'
@@ -134,9 +133,9 @@ include:
 .wordpress_rules:
   rules:
     - changes:
-        - flux/**/$APP*.yaml
-        - ansible/roles/apps/templates/settings/$APP.yaml
-        - ansible/roles/apps/tasks/$APP.yaml
+        - flux2/apps/$APP/*.yaml
+        - flux2/cluster/optional/$APP/*.yaml
+        - install/install-${APP}.sh
         - test/behave/features/$APP.feature
     - if: '$TRIGGER_JOBS =~ /enable-wordpress/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-wordpress/'
@@ -151,9 +150,11 @@ include:
 stages:
   - build
   - create-vps
-  - enable-apps
   - setup-cluster
-  - helm-release
+  - kustomization
+  - base-helm-release
+  - install-apps
+  - apps-helm-release
   - apps-ready
   - certs
   - health-test
@@ -276,88 +277,6 @@ create-vps:
     auto_stop_in: 1 week
   interruptible: true
 
-# Stage: enable-apps
-# ==================
-#
-# Checks if application needs to get installed
-
-.enable_app_template:
-  stage: enable-apps
-  before_script:
-    - *debug_information
-  script:
-    - |
-      [ ! -d ./enabled_apps ] && mkdir enabled_apps || /bin/true
-      touch ./enabled_apps/$APP
-  needs:
-    - job: create-vps
-  extends:
-    - .report_artifacts
-  interruptible: true
-
-enable-cert-manager:
-  variables:
-    APP: "cert-manager"
-  extends:
-    - .enable_app_template
-    - .cert_manager_rules
-
-enable-eventrouter:
-  variables:
-    APP: "eventrouter"
-  extends:
-    - .enable_app_template
-    - .eventrouter_rules
-
-enable-loki:
-  variables:
-    APP: "loki"
-  extends:
-    - .enable_app_template
-    - .loki_rules
-
-enable-promtail:
-  variables:
-    APP: "promtail"
-  extends:
-    - .enable_app_template
-    - .promtail_rules
-
-enable-nextcloud:
-  variables:
-    APP: "nextcloud"
-  extends:
-    - .enable_app_template
-    - .nextcloud_rules
-
-enable-prometheus-stack:
-  variables:
-    APP: "prometheus-stack"
-  extends:
-    - .enable_app_template
-    - .prometheus_stack_rules
-
-enable-rocketchat:
-  variables:
-    APP: "rocketchat"
-  extends:
-    - .enable_app_template
-    - .rocketchat_rules
-
-enable-single-sign-on:
-  variables:
-    APP: "single-sign-on"
-  extends:
-    - .enable_app_template
-    - .single_sign_on_rules
-
-enable-wordpress:
-  variables:
-    APP: "wordpress"
-  extends:
-    - .enable_app_template
-    - .wordpress_rules
-
 
 # Stage: setup-cluster
 # ====================
@@ -382,16 +301,18 @@ setup-openappstack:
     - *debug_information
     # Copy inventory files to ansible folder for use in install-apps step
     - chmod 700 ansible
-    # For every app we add the fully qualified path to an array named enabled_applications which we then save as settings.yml
-    # Refer to ansible/group_vars/all/settings.yml.example to see an example
-    - for app in enabled_apps/*; do yq -i eval ".enabled_applications += [\"$(basename $app)\"]" clusters/${CI_COMMIT_REF_SLUG}/group_vars/all/settings.yml; done
     - cp clusters/${CI_COMMIT_REF_SLUG}/inventory.yml ansible/
     - cp clusters/${CI_COMMIT_REF_SLUG}/group_vars/all/settings.yml ansible/group_vars/all/
     # Set up cluster
-    - python3 -m openappstack $HOSTNAME install --install-kubernetes
+    # TODO: I set --no-install-openappstack to skip the old installation procedure, should be removed eventually
+    - python3 -m openappstack $HOSTNAME install --install-kubernetes --no-install-openappstack
+    - scp -r ./install root@${SUBDOMAIN}.${DOMAIN}:/tmp/install
+    - ssh ${SUBDOMAIN}.${DOMAIN} -l root "/usr/bin/env /tmp/install/ci-write-variable-files.sh $IP_ADDRESS ${SUBDOMAIN}.${DOMAIN}"
+    - ssh ${SUBDOMAIN}.${DOMAIN} -l root "/usr/bin/env /tmp/install/install-openappstack.sh"
+    # TODO: Should also be removed or made up-to-date
     # Show versions of installed apps/binaries
-    - cd ansible
-    - ansible master -m shell -a 'oas-version-info.sh 2>&1'
+    # - cd ansible
+    # - ansible master -m shell -a 'oas-version-info.sh 2>&1'
   extends:
     - .ssh_setup
     - .report_artifacts
@@ -399,17 +320,31 @@ setup-openappstack:
   interruptible: true
 
 
-# Stage: helm-release
+# Stage: kustomization
 # ====================
 #
-# Tests if all helmreleases are in `deployed` state
-
-
-.helm-release:
-  stage: helm-release
+# Tests if all kustomizations are ready
+.kustomization-ready:
+  stage: kustomization
   needs:
     - job: setup-openappstack
     - job: test-dns
+  script:
+    - *debug_information
+    - cd ansible/
+    - export KUBECONFIG="${PWD}/../clusters/${HOSTNAME}/secrets/kube_config_cluster.yml"
+    - pytest -v -s -m 'kustomizations' --app="$APP" --connection=ansible --ansible-inventory=${CLUSTER_DIR}/inventory.yml --hosts='ansible://*' --reruns 120 --reruns-delay 20
+  extends:
+    - .ssh_setup
+  interruptible: true
+
+base-kustomizations-ready:
+  variables:
+    APP: "base"
+  extends:
+    - .kustomization-ready
+
+.helm-release:
   script:
     - *debug_information
     - cd ansible/
@@ -419,74 +354,149 @@ setup-openappstack:
     - .ssh_setup
   interruptible: true
 
+# Stage: base-helm-release
+# ==================
+#
+# Checks helmreleases for oas base are ready
+
+.base-helm-release:
+  stage: base-helm-release
+  needs:
+    - job: base-kustomizations-ready
+    - job: setup-openappstack
+    - job: test-dns
+  extends:
+    - .helm-release
+
 cert-manager-helm-release:
   variables:
     APP: "cert-manager"
   extends:
-    - .helm-release
+    - .base-helm-release
     - .cert_manager_rules
 
 eventrouter-helm-release:
   variables:
     APP: "eventrouter"
   extends:
-    - .helm-release
+    - .base-helm-release
     - .eventrouter_rules
 
 local-path-provisioner-helm-release:
   variables:
     APP: "local-path-provisioner"
   extends:
-    - .helm-release
+    - .base-helm-release
     - .local_path_provisioner_rules
 
 loki-helm-release:
   variables:
     APP: "loki"
   extends:
-    - .helm-release
+    - .base-helm-release
     - .loki_rules
 
 promtail-helm-release:
   variables:
     APP: "promtail"
   extends:
-    - .helm-release
+    - .base-helm-release
     - .promtail_rules
 
-nextcloud-helm-release:
+kube-prometheus-stack-helm-release:
   variables:
-    APP: "nextcloud"
+    APP: "kube-prometheus-stack"
   extends:
-    - .helm-release
-    - .nextcloud_rules
+    - .base-helm-release
+    - .prometheus_stack_rules
 
-prometheus-stack-helm-release:
+single-sign-on-helm-release:
   variables:
-    APP: "prometheus-stack"
+    APP: "single-sign-on"
   extends:
-    - .helm-release
-    - .prometheus_stack_rules
+    - .base-helm-release
+    - .single_sign_on_rules
 
-rocketchat-helm-release:
+# Stage: install-apps
+# ==================
+#
+# Checks if application needs to get installed
+
+.enable_app_template:
+  stage: install-apps
+  script:
+    - *debug_information
+    - ssh ${SUBDOMAIN}.${DOMAIN} -l root "/usr/bin/env /tmp/install/install-${APP}.sh"
+  extends:
+    - .ssh_setup
+  interruptible: true
+
+enable-nextcloud:
+  variables:
+    APP: "nextcloud"
+  extends:
+    - .enable_app_template
+    - .nextcloud_rules
+
+enable-rocketchat:
   variables:
     APP: "rocketchat"
   extends:
-    - .helm-release
+    - .enable_app_template
     - .rocketchat_rules
 
-single-sign-on-helm-release:
+enable-wordpress:
   variables:
-    APP: "single-sign-on"
+    APP: "wordpress"
+  extends:
+    - .enable_app_template
+    - .wordpress_rules
+
+# Stage: apps-helm-release
+# ====================
+#
+# Tests if all helm releases are ready
+
+.apps-helm-release:
+  stage: apps-helm-release
   extends:
     - .helm-release
-    - .single_sign_on_rules
+  interruptible: true
+
+nextcloud-helm-release:
+  variables:
+    APP: "nextcloud"
+  needs:
+    - job: base-kustomizations-ready
+    - job: setup-openappstack
+    - job: test-dns
+    - job: enable-nextcloud
+  extends:
+    - .apps-helm-release
+    - .nextcloud_rules
+
+rocketchat-helm-release:
+  variables:
+    APP: "rocketchat"
+  needs:
+    - job: base-kustomizations-ready
+    - job: setup-openappstack
+    - job: test-dns
+    - job: enable-rocketchat
+  extends:
+    - .apps-helm-release
+    - .rocketchat_rules
 
 wordpress-helm-release:
   variables:
     APP: "wordpress"
+  needs:
+    - job: base-kustomizations-ready
+    - job: setup-openappstack
+    - job: test-dns
+    - job: enable-wordpress
   extends:
-    - .helm-release
+    - .apps-helm-release
     - .wordpress_rules
 
 
@@ -495,7 +505,6 @@ wordpress-helm-release:
 #
 # Tests apps for readiness state
 
-
 .apps-ready:
   stage: apps-ready
   script:
@@ -567,11 +576,11 @@ nextcloud-ready:
     - .apps-ready
     - .nextcloud_rules
 
-prometheus-stack-ready:
+kube-prometheus-stack-ready:
   variables:
-    APP: "prometheus-stack"
+    APP: "kube-prometheus-stack"
   needs:
-    - job: prometheus-stack-helm-release
+    - job: kube-prometheus-stack-helm-release
     - job: setup-openappstack
   extends:
     - .apps-ready
@@ -632,11 +641,11 @@ nextcloud-cert:
     - .apps-cert
     - .nextcloud_rules
 
-prometheus-stack-cert:
+kube-prometheus-stack-cert:
   variables:
-    APP: "prometheus-stack"
+    APP: "kube-prometheus-stack"
   needs:
-    - job: prometheus-stack-ready
+    - job: kube-prometheus-stack-ready
     - job: setup-openappstack
   extends:
     - .apps-cert
@@ -689,11 +698,11 @@ testinfra:
     - .general_rules
   interruptible: true
 
-prometheus-stack-alerts:
+kube-prometheus-stack-alerts:
   stage: health-test
   variables:
     # APP var is used in job specific rules (i.e. .prometheus_stack_rules)
-    APP: "prometheus-stack"
+    APP: "kube-prometheus-stack"
   allow_failure: true
   script:
     - *debug_information
@@ -703,7 +712,7 @@ prometheus-stack-alerts:
     - .ssh_setup
     - .prometheus_stack_rules
   needs:
-    - job: prometheus-stack-ready
+    - job: kube-prometheus-stack-ready
     - job: setup-openappstack
   interruptible: true
 
@@ -729,11 +738,11 @@ prometheus-stack-alerts:
     - .ssh_setup
   interruptible: true
 
-prometheus-stack-behave:
+kube-prometheus-stack-behave:
   variables:
-    APP: "prometheus-stack"
+    APP: "kube-prometheus-stack"
   needs:
-    - job: prometheus-stack-cert
+    - job: kube-prometheus-stack-cert
     - job: setup-openappstack
   extends:
     - .behave
diff --git a/ansible/group_vars/all/oas.yml b/ansible/group_vars/all/oas.yml
index 91ea82101..758f08f77 100644
--- a/ansible/group_vars/all/oas.yml
+++ b/ansible/group_vars/all/oas.yml
@@ -49,6 +49,9 @@ hydra_system_secret: "{{ lookup('password', '{{ cluster_dir }}/secrets/hydra_sys
 
 # Application versions
 
+flux:
+  version: 0.14.2
+
 k3s:
   version: 'v1.20.4+k3s1'
   # args to start the k3s server with
diff --git a/ansible/group_vars/all/settings.yml.example b/ansible/group_vars/all/settings.yml.example
index 379dae021..c99ace170 100644
--- a/ansible/group_vars/all/settings.yml.example
+++ b/ansible/group_vars/all/settings.yml.example
@@ -45,22 +45,6 @@ backup:
     # Prefix that's added to backup filenames.
     prefix: "test-instance"
 
-flux:
-  # Specify which git repo and branch should be followed by the auto-update
-  # mechanism (flux). Not used if local_flux is set to true.
-  repo: "https://open.greenhost.net/openappstack/openappstack"
-  branch: "master"
-  # You can add extra parameters to the flux helm install command
-  # see https://github.com/fluxcd/flux/blob/master/chart/flux/README.md
-  # for reference
-  extra_opts: ""
-  # chart version
-  version: "1.9.0"
-  # If true, let the auto-update mechanism (flux) follow a cluster-local git
-  # repo, not the one specified above
-  local_flux: false
-
-
 # Additional custom flux installation, needs to be enabled under `enabled_applications` below.
 # see https://docs.openappstack.net/en/latest/customization.html for details
 # and https://github.com/fluxcd/flux/tree/master/chart/flux#configuration for
@@ -74,14 +58,6 @@ flux:
 #     branch: "master"
 #   …
 
-
-helm_operator:
-  # You can add extra parameters to the helm-operator helm install command
-  # see https://docs.fluxcd.io/projects/helm-operator/en/stable/references/chart/
-  # for reference
-  extra_opts: ""
-  version: "1.2.0"
-
 # A whitelist of applications that will be enabled.
 enabled_applications:
   # System components, necessary for the system to function.
diff --git a/ansible/install-openappstack.yml b/ansible/install-openappstack.yml
index 3a13f78c8..5589966d9 100644
--- a/ansible/install-openappstack.yml
+++ b/ansible/install-openappstack.yml
@@ -15,7 +15,3 @@
     - import_role:
         name: apps
       tags: ['apps']
-    - import_role:
-        name: local-flux
-      tags: ['flux']
-      when: flux.local_flux
diff --git a/ansible/roles/apps/tasks/core.yml b/ansible/roles/apps/tasks/core.yml
index 4104905b9..a9153f6b0 100644
--- a/ansible/roles/apps/tasks/core.yml
+++ b/ansible/roles/apps/tasks/core.yml
@@ -108,36 +108,6 @@
     resource_definition: "{{ lookup('file', 'local-path-provisioner_hr.yaml') | from_yaml }}"
   when: "'local-path-provisioner' in enabled_applications"
 
-- name: Create Kubernetes secret with nginx-ingress settings
-  tags:
-    - config
-    - flux
-    - nginx
-  vars:
-    flux_secret:
-      name: "ingress"
-      namespace: "oas"
-  include_tasks:
-    file: flux_secret.yml
-    apply:
-      tags:
-        - config
-        - flux
-        - nginx
-
-# We have to install nginx-ingress before other charts so that the ingress
-# validation webhook exists before it is used.
-# It will still be managed by flux afterwards.
-- name: Create ingress HelmResource
-  tags:
-    - config
-    - flux
-    - nginx
-  k8s:
-    state: present
-    resource_definition: "{{ lookup('file', 'ingress_hr.yaml') | from_yaml }}"
-  when: "'ingress' in enabled_applications"
-
 - name: Install flux
   tags:
     - flux
diff --git a/ansible/roles/apps/tasks/nginx.yml b/ansible/roles/apps/tasks/nginx.yml
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ansible/roles/apps/templates/settings/ingress.yaml b/ansible/roles/apps/templates/settings/ingress.yaml
deleted file mode 100644
index 2fe5af242..000000000
--- a/ansible/roles/apps/templates/settings/ingress.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-controller:
-  image:
-    # Disable image digest validation until flux supports it
-    # https://github.com/fluxcd/flux/issues/3189
-    digest: ''
-  service:
-    ## Set external traffic policy to: "Local" to preserve source IP on
-    ## providers supporting it
-    ## Ref: https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer
-    externalTrafficPolicy: Local
-  config:
-    # http://nginx.org/en/docs/http/ngx_http_access_module.html
-    # comma separated list of CIDRs, e.g. 10.0.0.0/24,172.10.0.1.
-    # By default we allow all access from everywhere
-    whitelist-source-range: '0.0.0.0/0'
-  resources:
-    limits:
-      cpu: 200m
-      memory: 1Gi
-    requests:
-      cpu: 100m
-      memory: 64Mi
diff --git a/ansible/roles/apps/templates/settings/single-sign-on.yaml b/ansible/roles/apps/templates/settings/single-sign-on.yaml
deleted file mode 100644
index 8ed680d39..000000000
--- a/ansible/roles/apps/templates/settings/single-sign-on.yaml
+++ /dev/null
@@ -1,127 +0,0 @@
-singleSignOnHost: &SSO_HOST "sso.{{ domain }}"
-
-userpanel:
-  applicationName: &USER_PANEL user-panel
-  ingress:
-    host: "admin.{{ domain }}"
-
-userbackend:
-  applications:
-    - name: *USER_PANEL
-      description: Administration interface to manage user accounts
-    - name: &NEXTCLOUD nextcloud
-      description: "Nextcloud Files offers an on-premise Universal File Access and sync platform with powerful collaboration capabilities and desktop, mobile and web interfaces."
-    - name: &WORDPRESS wordpress
-      description: "WordPress website hosting."
-    - name: &ROCKETCHAT rocketchat
-      description: "Communicate and collaborate using team chat and switch to video or audio calls with screen sharing for more efficient teamwork."
-    - name: &GRAFANA grafana
-      description: "Grafana allows you to query, visualize, alert on and understand metrics generated by OpenAppStack. It can be used to create explore and share dashboards."
-  username: "{{ userbackend_admin_username }}"
-  password: "{{ userbackend_admin_password }}"
-  email: "{{ userbackend_admin_email }}"
-  postgres:
-    password: "{{ userbackend_postgres_password }}"
-  persistence:
-    enabled: true
-    size: 1Gi
-    existingClaim: single-sign-on-userbackend
-  podAnnotations:
-    # Let the backup system include nextcloud database data.
-    backup.velero.io/backup-volumes: "database"
-
-hydra:
-  hydra:
-    config:
-      urls:
-        self:
-          issuer: "https://sso.{{ domain }}"
-        login: "https://sso.{{ domain }}/login"
-        consent: "https://sso.{{ domain }}/consent"
-      secrets:
-        system: "{{ hydra_system_secret }}"
-      dsn: "memory"
-  ingress:
-    public:
-      enabled: true
-      annotations:
-        kubernetes.io/tls-acme: "true"
-      hosts:
-        - host: *SSO_HOST
-          paths: ["/"]
-      tls:
-        - hosts:
-          - *SSO_HOST
-          secretName: hydra-public.tls
-    admin:
-      enabled: false
-
-oAuthClients:
-- clientName: *USER_PANEL
-  clientSecret: "{{ userpanel_oauth_client_secret }}"
-  redirectUri: "https://admin.{{ domain }}/callback"
-  scopes: "openid profile email openappstack_roles"
-  clientUri: "https://admin.{{ domain }}"
-  clientLogoUri: "https://admin.{{ domain }}/favicon.ico"
-  tokenEndpointAuthMethod: "client_secret_basic"
-  responseTypes:
-    - "token"
-  grantTypes:
-    - "implicit"
-- clientName: *NEXTCLOUD
-  clientSecret: "{{ nextcloud_oauth_client_secret }}"
-  redirectUri: "https://files.{{ domain }}/apps/sociallogin/custom_oidc/oas"
-  scopes: "openid profile email openappstack_roles"
-  clientUri: "https://files.{{ domain }}"
-  clientLogoUri: "https://files.{{ domain }}/core/img/favicon-touch.png"
-  tokenEndpointAuthMethod: "client_secret_post"
-  responseTypes:
-    - "code"
-    - "id_token"
-  grantTypes:
-    - "authorization_code"
-    - "refresh_token"
-    - "client_credentials"
-- clientName: *WORDPRESS
-  clientSecret: "{{ wordpress_oauth_client_secret }}"
-  redirectUri: "https://www.{{ domain }}/wp-admin/admin-ajax.php?action=openid-connect-authorize"
-  scopes: "openid profile email openappstack_roles offline_access"
-  clientUri: "https://www.{{ domain }}"
-  clientLogoUri: "https://www.{{ domain }}/wp-admin/images/wordpress-logo.svg"
-  tokenEndpointAuthMethod: "client_secret_post"
-  responseTypes:
-    - "code"
-    - "id_token"
-  grantTypes:
-    - "authorization_code"
-    - "refresh_token"
-    - "client_credentials"
-    - "implicit"
-- clientName: *ROCKETCHAT
-  clientSecret: "{{ rocketchat_oauth_client_secret }}"
-  redirectUri: "https://chat.{{ domain }}/_oauth/openappstack"
-  scopes: "openid profile email openappstack_roles"
-  clientUri: "https://chat.{{ domain }}"
-  clientLogoUri: "https://chat.{{ domain }}/images/logo/logo.svg"
-  tokenEndpointAuthMethod: "client_secret_post"
-  responseTypes:
-    - "code"
-    - "id_token"
-  grantTypes:
-    - "authorization_code"
-    - "refresh_token"
-    - "client_credentials"
-- clientName: *GRAFANA
-  clientSecret: "{{ grafana_oauth_client_secret }}"
-  redirectUri: "https://grafana.{{ domain }}/login/generic_oauth"
-  scopes: "openid profile email openappstack_roles"
-  clientUri: "https://grafana.{{ domain }}"
-  clientLogoUri: "https://grafana.{{ domain }}/public/img/grafana_icon.svg"
-  tokenEndpointAuthMethod: "client_secret_post"
-  responseTypes:
-    - "code"
-    - "id_token"
-  grantTypes:
-    - "authorization_code"
-    - "refresh_token"
-    - "client_credentials"
diff --git a/ansible/roles/compatibility-checks/tasks/main.yml b/ansible/roles/compatibility-checks/tasks/main.yml
index f94767412..a5e01ef1c 100644
--- a/ansible/roles/compatibility-checks/tasks/main.yml
+++ b/ansible/roles/compatibility-checks/tasks/main.yml
@@ -23,7 +23,5 @@
     - admin_email
     - acme_staging
     - backup
-    - flux
-    - helm_operator
     - enabled_applications
     - outgoing_mail
diff --git a/ansible/roles/local-flux/defaults/main.yml b/ansible/roles/local-flux/defaults/main.yml
deleted file mode 100644
index 066d0e626..000000000
--- a/ansible/roles/local-flux/defaults/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
----
-repo: "/var/lib/OpenAppStack/local-flux"
diff --git a/ansible/roles/local-flux/files/Chart.yaml b/ansible/roles/local-flux/files/Chart.yaml
deleted file mode 100644
index 65f31ac16..000000000
--- a/ansible/roles/local-flux/files/Chart.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: v1
-name: local-flux
-version: 0.1.0
-description: |
-  Sets up an nginx deployment to serve a git repo (stored on local
-  disk) via http
diff --git a/ansible/roles/local-flux/files/git-hook.sh b/ansible/roles/local-flux/files/git-hook.sh
deleted file mode 100644
index dac4c5319..000000000
--- a/ansible/roles/local-flux/files/git-hook.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-exec git update-server-info
diff --git a/ansible/roles/local-flux/files/nginx.yaml b/ansible/roles/local-flux/files/nginx.yaml
deleted file mode 100644
index a61cd82cd..000000000
--- a/ansible/roles/local-flux/files/nginx.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
-apiVersion: v1
-kind: PersistentVolume
-metadata:
-  name: {{ .Release.Name }}-data
-  labels:
-    type: local
-spec:
-  storageClassName: ""
-  capacity:
-    storage: 10Mi
-  accessModes:
-    - ReadOnlyMany
-  hostPath:
-    path: "{{ .Values.local_flux_directory }}"
----
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  namespace: oas
-  name: {{ .Release.Name }}-data
-spec:
-  storageClassName: ""
-  # Make sure this claims uses the persistent volume describe above.
-  volumeName: {{ .Release.Name }}-data
-  accessModes:
-    - ReadOnlyMany
-  resources:
-    requests:
-      storage: 10Mi
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  namespace: oas
-  name: {{ .Release.Name }}
-spec:
-  selector:
-    matchLabels:
-      app: {{ .Release.Name }}
-  replicas: 1
-  template:
-    metadata:
-      labels:
-        app: {{ .Release.Name }}
-    spec:
-      containers:
-      - name: {{ .Release.Name }}-nginx
-        image: nginx:{{ .Values.nginx.image.tag }}
-        ports:
-        - containerPort: 80
-        volumeMounts:
-        - mountPath: "/usr/share/nginx/html"
-          name: {{ .Release.Name }}-data
-      volumes:
-      - name: {{ .Release.Name }}-data
-        persistentVolumeClaim:
-          claimName: {{ .Release.Name }}-data
----
-apiVersion: v1
-kind: Service
-metadata:
-  namespace: oas
-  name: {{ .Release.Name }}
-spec:
-  selector:
-    app: {{ .Release.Name }}
-  ports:
-    - protocol: TCP
-      port: 80
-      targetPort: 80
diff --git a/ansible/roles/local-flux/files/values.yaml b/ansible/roles/local-flux/files/values.yaml
deleted file mode 100644
index c9d504f4b..000000000
--- a/ansible/roles/local-flux/files/values.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-# Directory on node where the HelmRelease files are stored.
-local_flux_directory: "/var/lib/OpenAppStack/local-flux"
-
-nginx:
-  image:
-    tag: "1.17-alpine"
diff --git a/ansible/roles/local-flux/tasks/main.yml b/ansible/roles/local-flux/tasks/main.yml
deleted file mode 100644
index d117864e9..000000000
--- a/ansible/roles/local-flux/tasks/main.yml
+++ /dev/null
@@ -1,70 +0,0 @@
----
-
-- name: Copy HelmRelease files to server's local flux repo
-  tags:
-    - flux
-  synchronize:
-    src: "../../../flux/"
-    dest: "{{ repo }}"
-    delete: yes
-    rsync_opts:
-      - "--exclude=.git"
-  register: helmreleases
-  become: true
-
-- name: Create local flux repo
-  tags:
-    - flux
-  command: git init "{{ repo }}"
-  args:
-    creates: "{{ repo }}/.git"
-  become: true
-
-- name: Enable post-update hook in local flux repo
-  tags:
-    - flux
-  copy:
-    src: "git-hook.sh"
-    dest: "{{ repo }}/.git/hooks/{{ item }}"
-    mode: "0755"
-  with_items:
-    - "post-update"
-    - "post-commit"
-  become: true
-
-- name: Add HelmRelease files to local flux commit
-  tags:
-    - flux
-  shell: git add -A . && git -c "user.name=OpenAppStack automation" -c "user.email=tech@openappstack.net" commit --allow-empty --author="OpenAppStack automation <>" -m "Local flux via ansible"
-  args:
-    chdir: "{{ repo }}"
-  when: helmreleases.changed
-  become: true
-
-- name: Create local-flux helm chart directory
-  tags:
-    - flux
-  file:
-    path: "/var/lib/OpenAppStack/source/local-flux/templates"
-    state: directory
-  become: true
-
-- name: Copy local-flux helm chart to server
-  tags:
-    - flux
-  copy:
-    src: "{{ item.file }}"
-    dest: "/var/lib/OpenAppStack/source/local-flux/{{ item.subdir }}/{{ item.file }}"
-  with_items:
-    - file: "nginx.yaml"
-      subdir: "templates"
-    - file: "values.yaml"
-      subdir: "."
-    - file: "Chart.yaml"
-      subdir: "."
-  become: true
-
-- name: Install local-flux helm chart
-  tags:
-    - flux
-  shell: helm upgrade --install --namespace=oas local-flux /var/lib/OpenAppStack/source/local-flux
diff --git a/ansible/roles/setup-kubernetes/tasks/flux.yml b/ansible/roles/setup-kubernetes/tasks/flux.yml
new file mode 100644
index 000000000..da25a7a31
--- /dev/null
+++ b/ansible/roles/setup-kubernetes/tasks/flux.yml
@@ -0,0 +1,43 @@
+---
+- name: Get current flux version
+  tags:
+    - flux
+  shell: flux --version | awk {'print $3'}
+  failed_when: false
+  register: flux_version
+  changed_when: false
+
+- name: Show current flux version
+  tags:
+    - flux
+  debug:
+    msg: 'Show current flux version is: {{ flux_version.stdout }}'
+
+- name: Download flux archive
+  tags:
+    - flux
+  get_url:
+    url: 'https://github.com/fluxcd/flux2/releases/download/v{{ flux.version }}/flux_{{ flux.version }}_linux_amd64.tar.gz'
+    dest: '/tmp/flux_{{ flux.version }}_linux_amd64.tar.gz'
+    mode: '0755'
+    checksum: 'sha256:https://github.com/fluxcd/flux2/releases/download/v{{ flux.version }}/flux_{{ flux.version }}_checksums.txt'
+  become: true
+  when: flux_version.stdout != flux.version
+
+- name: Unarchive downloaded flux archive
+  tags:
+    - flux
+  unarchive:
+    src: '/tmp/flux_{{ flux.version }}_linux_amd64.tar.gz'
+    dest: /usr/local/bin/
+    remote_src: yes
+  when: flux_version.stdout != flux.version
+
+- name: Install flux into cluster
+  tags:
+    - flux
+  shell: |
+    flux install \
+    --network-policy=false \
+    --watch-all-namespaces=true \
+    --namespace=flux-system
diff --git a/ansible/roles/setup-kubernetes/tasks/main.yml b/ansible/roles/setup-kubernetes/tasks/main.yml
index 1fa0eddc3..563e90aa4 100644
--- a/ansible/roles/setup-kubernetes/tasks/main.yml
+++ b/ansible/roles/setup-kubernetes/tasks/main.yml
@@ -1,3 +1,4 @@
 ---
 - import_tasks: k3s.yml
 - import_tasks: krew.yml
+- import_tasks: flux.yml
diff --git a/charts/oas-secrets/.helmignore b/charts/oas-secrets/.helmignore
new file mode 100644
index 000000000..0e8a0eb36
--- /dev/null
+++ b/charts/oas-secrets/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/charts/oas-secrets/Chart.yaml b/charts/oas-secrets/Chart.yaml
new file mode 100644
index 000000000..eb2ebc114
--- /dev/null
+++ b/charts/oas-secrets/Chart.yaml
@@ -0,0 +1,28 @@
+---
+apiVersion: v2
+name: secrets
+description: |
+  A helm chart to generate secrets that are needed by other helm charts. Values
+  inside the secret will only be generated if they do not exist yet, so it is
+  safe to run `helm upgrade` on this chart.
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.8
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+# appVersion: "0.1.0"
diff --git a/charts/oas-secrets/templates/NOTES.txt b/charts/oas-secrets/templates/NOTES.txt
new file mode 100644
index 000000000..5e349b352
--- /dev/null
+++ b/charts/oas-secrets/templates/NOTES.txt
@@ -0,0 +1 @@
+Secrets generated
diff --git a/charts/oas-secrets/templates/_helpers.tpl b/charts/oas-secrets/templates/_helpers.tpl
new file mode 100644
index 000000000..d791991ea
--- /dev/null
+++ b/charts/oas-secrets/templates/_helpers.tpl
@@ -0,0 +1,84 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "secrets.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "secrets.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "secrets.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "secrets.labels" -}}
+helm.sh/chart: {{ include "secrets.chart" . }}
+{{ include "secrets.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "secrets.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "secrets.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "secrets.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "secrets.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+Returns a secret if it already in Kubernetes, otherwise it creates
+it randomly.
+Special thanks to @iTaybb: https://github.com/helm/charts/issues/5167#issuecomment-840698657
+*/}}
+{{- define "getOrGeneratePass" }}
+{{- $len := (default 16 .Length) | int -}}
+{{- $obj := (lookup "v1" .Kind .Namespace .Name).data -}}
+{{- $val := "" -}}
+{{- if $obj }}
+{{- $val := (index $obj .Key) -}}
+{{- end -}}
+{{- if $val }}
+{{- $val -}}
+{{- else if (eq (lower .Kind) "secret") -}}
+{{- randAlphaNum $len | b64enc -}}
+{{- else -}}
+{{- randAlphaNum $len -}}
+{{- end -}}
+{{- end }}
\ No newline at end of file
diff --git a/charts/oas-secrets/templates/oas-alertmanager-basic-auth.yaml b/charts/oas-secrets/templates/oas-alertmanager-basic-auth.yaml
new file mode 100644
index 000000000..f4471fda9
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-alertmanager-basic-auth.yaml
@@ -0,0 +1,12 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  namespace: "oas"
+  name: "oas-alertmanager-basic-auth"
+data:
+  {{- $pass := (include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-alertmanager-basic-auth" "Key" "pass")) | b64dec -}}
+  {{/* Readable version of the password for humans who want to log in */}}
+  pass: {{ $pass | b64enc }}
+  {{/* Encoded version of the password for nginx ingress */}}
+  auth: {{ htpasswd "admin" $pass | b64enc }}
diff --git a/charts/oas-secrets/templates/oas-kube-prometheus-stack-variables.yaml b/charts/oas-secrets/templates/oas-kube-prometheus-stack-variables.yaml
new file mode 100644
index 000000000..0781a9b19
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-kube-prometheus-stack-variables.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-kube-prometheus-stack-variables
+data:
+  grafana_admin_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-kube-prometheus-stack-variables" "Key" "grafana_admin_password") }}"
diff --git a/charts/oas-secrets/templates/oas-nextcloud-variables.yaml b/charts/oas-secrets/templates/oas-nextcloud-variables.yaml
new file mode 100644
index 000000000..017cc8083
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-nextcloud-variables.yaml
@@ -0,0 +1,12 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-nextcloud-variables
+data:
+  nextcloud_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "nextcloud_password") }}"
+  nextcloud_mariadb_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "nextcloud_mariadb_password") }}"
+  nextcloud_mariadb_root_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "nextcloud_mariadb_root_password") }}"
+  onlyoffice_jwt_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "onlyoffice_jwt_secret") }}"
+  onlyoffice_postgresql_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "onlyoffice_postgresql_password") }}"
+  onlyoffice_rabbitmq_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-nextcloud-variables" "Key" "onlyoffice_rabbitmq_password") }}"
diff --git a/charts/oas-secrets/templates/oas-oauth-variables.yaml b/charts/oas-secrets/templates/oas-oauth-variables.yaml
new file mode 100644
index 000000000..097959278
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-oauth-variables.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-oauth-variables
+data:
+  userpanel_oauth_client_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-oauth-variables" "Key" "userpanel_oauth_client_secret") }}"
+  nextcloud_oauth_client_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-oauth-variables" "Key" "nextcloud_oauth_client_secret") }}"
+  wordpress_oauth_client_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-oauth-variables" "Key" "wordpress_oauth_client_secret") }}"
+  rocketchat_oauth_client_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-oauth-variables" "Key" "rocketchat_oauth_client_secret") }}"
+  grafana_oauth_client_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-oauth-variables" "Key" "grafana_oauth_client_secret") }}"
diff --git a/charts/oas-secrets/templates/oas-prometheus-basic-auth.yaml b/charts/oas-secrets/templates/oas-prometheus-basic-auth.yaml
new file mode 100644
index 000000000..f094270a2
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-prometheus-basic-auth.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  namespace: "oas"
+  name: "oas-prometheus-basic-auth"
+data:
+  {{- $pass := (include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-prometheus-basic-auth" "Key" "pass")) | b64dec -}}
+  {{/* Readable version of the password for humans who want to log in */}}
+  pass: {{ $pass | b64enc }}
+  {{/* Encoded version of the password for nginx ingress */}}
+  auth: {{ htpasswd "admin" $pass | b64enc }}
diff --git a/charts/oas-secrets/templates/oas-rocketchat-variables.yaml b/charts/oas-secrets/templates/oas-rocketchat-variables.yaml
new file mode 100644
index 000000000..747ed200b
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-rocketchat-variables.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-rocketchat-variables
+data:
+  mongodb_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-rocketchat-variables" "Key" "mongodb_password") }}"
+  mongodb_root_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-rocketchat-variables" "Key" "mongodb_root_password") }}"
+  rocketchat_admin_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-rocketchat-variables" "Key" "rocketchat_admin_password") }}"
diff --git a/charts/oas-secrets/templates/oas-single-sign-on-variables.yaml b/charts/oas-secrets/templates/oas-single-sign-on-variables.yaml
new file mode 100644
index 000000000..55b29a84b
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-single-sign-on-variables.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-single-sign-on-variables
+data:
+  userbackend_admin_username: {{ b64enc "admin" }}
+  userbackend_admin_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-single-sign-on-variables" "Key" "userbackend_admin_password") }}"
+  userbackend_postgres_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-single-sign-on-variables" "Key" "userbackend_postgres_password") }}"
+  hydra_system_secret: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-single-sign-on-variables" "Key" "hydra_system_secret") }}"
diff --git a/charts/oas-secrets/templates/oas-wordpress-variables.yaml b/charts/oas-secrets/templates/oas-wordpress-variables.yaml
new file mode 100644
index 000000000..37df56499
--- /dev/null
+++ b/charts/oas-secrets/templates/oas-wordpress-variables.yaml
@@ -0,0 +1,9 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: oas-wordpress-variables
+data:
+  wordpress_admin_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-wordpress-variables" "Key" "wordpress_admin_password") }}"
+  wordpress_mariadb_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-wordpress-variables" "Key" "wordpress_mariadb_password") }}"
+  wordpress_mariadb_root_password: "{{ include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" "oas-wordpress-variables" "Key" "wordpress_mariadb_root_password") }}"
diff --git a/charts/oas-secrets/values.yaml b/charts/oas-secrets/values.yaml
new file mode 100644
index 000000000..449682fa4
--- /dev/null
+++ b/charts/oas-secrets/values.yaml
@@ -0,0 +1,6 @@
+# The domain OpenAppStack runs on. There needs to be an A record to
+# oas.example.com and an additional record *.oas.example.com, that both point to
+# this machine
+domain: oas.example.com
+# The email address of the cluster administrator
+admin_email: admin@oas.example.com
\ No newline at end of file
diff --git a/docs/advanced_installation.rst b/docs/advanced_installation.rst
index 1da5d8f1c..be70becfa 100644
--- a/docs/advanced_installation.rst
+++ b/docs/advanced_installation.rst
@@ -66,6 +66,10 @@ helm-operator pod in order to apply the config change to prometheus:
 Deploying custom apps with an extra flux instance
 =================================================
 
+**NOTE**: Since we updated to flux 2, an extra flux instance is not necessary
+anymore. This documentation will be updated soon, but ignore what it says below
+for now, unless you are running version 0.5.0 or below.
+
 Openappstack uses `Flux <https://fluxcd.io>`__ to deploy and auto-update
 applications. See the `Automatic updates <design.html#automatic-updates>`__
 section in the design doc for more information abouot Flux.
diff --git a/docs/design.md b/docs/design.md
index 282954dc7..f441ee8b1 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -164,6 +164,8 @@ This Kubernetes secret contains two keys:
 
 ### Local development
 
+**NOTE**: Local flux was removed. Documentation will be updated soon.
+
 When developing OpenAppStack, it's nice to be able to change the application
 versions for your development cluster only, without affecting production
 clusters. One way to do that is to set the `flux_source.repo` and/or
diff --git a/docs/installation_instructions.rst b/docs/installation_instructions.rst
index e9232fe99..77b9656e5 100644
--- a/docs/installation_instructions.rst
+++ b/docs/installation_instructions.rst
@@ -2,7 +2,7 @@ OpenAppStack installation instructions
 ======================================
 
 This document describes how you can install OpenAppStack on a VPS. The
-installation process wil set up a "Kubernetes cluster" which runs
+installation process will set up a "Kubernetes cluster" which runs
 several open source applications. More information about the
 applications can be found on the `OpenAppStack
 website <https://openappstack.net/>`__.
@@ -36,8 +36,9 @@ guide][https://openappstack.net/contact.html).
 Prerequisites
 -------------
 
--  During these instructions, you are asked to create a VPS, or have a
-   bare metal server ready. The server should meet these requirements:
+During these instructions, you are asked to create a VPS, or have a
+bare metal server ready. The server should meet these requirements:
+
 -  Current Debian stable "buster"
 -  A public IP address
 -  The ability to create DNS records for this IP
@@ -123,7 +124,7 @@ Setting up OpenAppStack happens in five steps:
    Optionally add settings and credentials to enable backups of your
    cluster to external S3 storage.
 
-3. **Optional: Confifure outgoing email**
+3. **Optional: Configure outgoing email**
 
    Optionally add settings and credentials to enable outgoing emails
    from applications to your users
diff --git a/flux2/README.md b/flux2/README.md
new file mode 100644
index 000000000..576cd6c21
--- /dev/null
+++ b/flux2/README.md
@@ -0,0 +1,37 @@
+This folder contains folders that can be reconciled with Flux v2. If you want to
+add this repository as a Flux 2 kustomization, first add the `cluster/base`
+directory as a kustomization in flux, then add `cluster/optional/X` if you want
+any of the optional components.
+
+
+## Folder structure
+
+```
+flux2
+├── apps     # Resources to install specific applications
+│   ├── monitoring  # Prometheus, Grafana and log aggregation
+│   ├── nextcloud   # Nextcloud
+│   ├── rocketchat  # RocketChat
+│   ├── velero      # Velero, backup system
+│   └── wordpress   # WordPress CMS
+├── cluster  # Folders that can be added as Flux Kustomization
+│   ├── base        # First kustomization that should be applied, adds
+│   │   │           # repositories, LE, Nginx, single sign-on, 
+│   │   ├── infrastructure.yaml  # Applies `flux2/infrastructure` folder
+│   │   ├── core.yaml            # Applies `flux2/core/` folder
+│   │   └── monitoring.yaml      # Applies `flux2/apps/monitoring` folder
+│   └── optional    # Kustomizations that can be applied to add applications
+│       └── ... # 1 folder per app in `apps/`
+├── core     # Resources that need variables from `oas-cluster-variables` secret
+│   └── base
+│       ├── cluster-issuer # `cert-manager` is part of `infrastructure`, this only adds the cluster-issuer
+│       ├── metallb        # Load balancer
+│       └── single-sign-on # Single sign-on system
+└── infrastructure  # First to be installed, essential OAS resources
+    ├── cert-manager           # Let's Encrypt certificate generator
+    ├── local-path-provisioner # (default) storage class to safe files on disk
+    ├── namespaces             # namespaces used by OAS
+    ├── nginx    # Ingress
+    ├── secrets  # Auto-generated secrets for applications
+    └── sources  # Helm repositories needed for HelmReleases from other folders
+```
diff --git a/flux2/apps/monitoring/eventrouter-release.yaml b/flux2/apps/monitoring/eventrouter-release.yaml
new file mode 100644
index 000000000..6ace3f9b9
--- /dev/null
+++ b/flux2/apps/monitoring/eventrouter-release.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: eventrouter
+  namespace: oas
+spec:
+  releaseName: eventrouter
+  chart:
+    spec:
+      chart: eventrouter
+      version: 0.3.2
+      sourceRef:
+        kind: HelmRepository
+        name: helm-stable
+        namespace: flux-system
+  interval: 40m
+  values:
+    sink: stdout
+    resources:
+      limits:
+        memory: 200Mi
+        cpu: 200m
+      requests:
+        memory: 100Mi
+        cpu: 100m
+    
\ No newline at end of file
diff --git a/flux2/apps/monitoring/kube-prometheus-stack-release.yaml b/flux2/apps/monitoring/kube-prometheus-stack-release.yaml
new file mode 100644
index 000000000..a9b592c89
--- /dev/null
+++ b/flux2/apps/monitoring/kube-prometheus-stack-release.yaml
@@ -0,0 +1,278 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: kube-prometheus-stack
+  namespace: oas
+spec:
+  releaseName: kube-prometheus-stack
+  chart:
+    spec:
+      chart: kube-prometheus-stack
+      version: 15.4.2
+      sourceRef:
+        kind: HelmRepository
+        name: prometheus-community
+        namespace: flux-system
+  interval: 40m
+  install:
+    timeout: 10m
+  values:
+    # https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml
+
+    # From: https://github.com/cablespaghetti/k3s-monitoring/blob/master/kube-prometheus-stack-values.yaml
+    # Disable etcd monitoring. See https://github.com/cablespaghetti/k3s-monitoring/issues/4
+    kubeEtcd:
+      enabled: false
+
+    # Disable kube-controller-manager and kube-scheduler monitoring. See https://github.com/cablespaghetti/k3s-monitoring/issues/2
+    kubeControllerManager:
+      enabled: false
+    kubeScheduler:
+      enabled: false
+
+    alertmanager:
+      ingress:
+        enabled: true
+        annotations:
+          nginx.ingress.kubernetes.io/auth-type: basic
+          nginx.ingress.kubernetes.io/auth-secret: oas-alertmanager-basic-auth
+          nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
+          kubernetes.io/tls-acme: "true"
+        pathType: ImplementationSpecific
+        hosts:
+          - "alertmanager.${domain}"
+        tls:
+          - secretName: alertmanager-tls
+            hosts:
+              - "alertmanager.${domain}"
+      config:
+        global:
+          smtp_from: "${outgoing_mail_from_address}"
+          smtp_smarthost: "${outgoing_mail_smtp_host}:${outgoing_mail_smtp_port}"
+          smtp_auth_username: "${outgoing_mail_smtp_user}"
+          smtp_auth_password: "${outgoing_mail_smtp_password}"
+        route:
+          group_by: ['job']
+          group_wait: 30s
+          group_interval: 5m
+          repeat_interval: 1h
+          receiver: email
+          routes:
+          - match:
+              # This is an alert meant to ensure that the entire alerting pipeline is functional.
+              # This alert is always firing, therefore it should always be firing in Alertmanager
+              # and always fire against a receiver. There are integrations with various notification
+              # mechanisms that send a notification when this alert is not firing. For example the
+              # "DeadMansSnitch" integration in PagerDuty.
+              alertname: Watchdog
+            receiver: 'null'
+
+        receivers:
+        - name: 'null'
+        - name: email
+          email_configs:
+          - send_resolved: true
+            to: ${admin_email}
+
+        # Inhibition rules allow to mute a set of alerts given that another alert is firing.
+        # We use this to mute any warning-level notifications if the same alert is already critical.
+        inhibit_rules:
+        - source_match:
+            severity: 'critical'
+          target_match:
+            severity: 'warning'
+          # Apply inhibition if the alertname is the same.
+          equal: ['alertname', 'namespace']
+
+      alertmanagerSpec:
+    #    replicas: 3
+    #    podAntiAffinity: "soft"
+        storage:
+          volumeClaimTemplate:
+            spec:
+              accessModes: ["ReadWriteOnce"]
+              resources:
+                requests:
+                  storage: 1Gi
+    #    resources:
+    #      limits:
+    #        cpu: 500m
+    #        memory: 64Mi
+    #      requests:
+    #        cpu: 25m
+    #        memory: 32Mi
+    #    priorityClassName: high-priority
+
+
+    prometheus:
+      # https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus
+      prometheusSpec:
+        scrapeInterval: "3m"
+        evaluationInterval: "3m"
+        retention: "30d"
+
+    #    replicas: 2
+    #    podAntiAffinity: "hard"
+        storageSpec:
+          volumeClaimTemplate:
+            spec:
+              accessModes: ["ReadWriteOnce"]
+              resources:
+                requests:
+                  storage: 10Gi
+
+        resources:
+          limits:
+            cpu: 500m
+            memory: 1.5Gi
+          requests:
+            cpu: 200m
+            memory: 1Gi
+
+      ingress:
+        enabled: true
+        annotations:
+          nginx.ingress.kubernetes.io/auth-type: basic
+          nginx.ingress.kubernetes.io/auth-secret: oas-prometheus-basic-auth
+          nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
+          kubernetes.io/tls-acme: "true"
+        pathType: ImplementationSpecific
+        hosts:
+          - "prometheus.${domain}"
+        tls:
+          - secretName: prometheus-tls
+            hosts:
+              - "prometheus.${domain}"
+
+    #
+    #  service:
+    #    sessionAffinity: "ClientIP"
+    #
+
+    grafana:
+      # https://github.com/grafana/helm-charts/tree/main/charts/grafana
+      adminPassword: "${grafana_admin_password}"
+      grafana.ini:
+        server:
+          root_url: "https://grafana.${domain}"
+        auth.generic_oauth:
+          name: OpenAppStack
+          enabled: true
+          client_id: grafana
+          client_secret: "${grafana_oauth_client_secret}"
+          scopes: "openid profile email openappstack_roles"
+          auth_url: "https://sso.${domain}/oauth2/auth"
+          token_url: "https://sso.${domain}/oauth2/token"
+          api_url: "https://sso.${domain}/userinfo"
+          role_attribute_path: contains(openappstack_roles[*], 'admin') && 'Admin' || 'Editor'
+      ingress:
+        enabled: true
+        annotations:
+          kubernetes.io/tls-acme: "true"
+        pathType: ImplementationSpecific
+        hosts:
+          - "grafana.${domain}"
+        tls:
+          - secretName: grafana-tls
+            hosts:
+              - "grafana.${domain}"
+      persistence:
+        enabled: true
+        existingClaim: "grafana"
+      podAnnotations:
+        backup.velero.io/backup-volumes: "storage"
+
+      # This allows us to pick up the Loki datasource
+      # sidecar:
+      #   datasources:
+      #     enabled: true
+      #     label: grafana_datasource
+      #   # Make a configmap with the label `grafana_dashboard` to add dashboards to
+      #   # Grafana.
+      #   dashboards:
+      #     enabled: true
+      #     lablel: grafana_dashboard
+
+      # dashboardProviders:
+      #   dashboardproviders.yaml:
+      #     apiVersion: 1
+      #     providers:
+      #     - name: 'default'
+      #       orgId: 1
+      #       folder: ''
+      #       type: file
+      #       disableDeletion: false
+      #       editable: true
+      #       options:
+      #         path: /var/lib/grafana/dashboards
+      # dashboards:
+      #   default:
+      #     kube-dash:
+      #       gnetId: 11074
+      #       revision: 2
+      #       datasource: Prometheus
+      #     loki-dash:
+      #       gnetId: 10880
+      #       revision: 1
+      #       datasource: Loki
+
+      # datasources:
+      #  datasources.yaml:
+      #    apiVersion: 1
+      #    datasources:
+      #    - name: Prometheus
+      #      type: prometheus
+      #      url: http://prometheus-server
+      #      access: proxy
+      #      isDefault: true
+
+      plugins:
+        - grafana-piechart-panel
+
+      resources:
+        limits:
+          cpu: 400m
+          memory: 256Mi
+        requests:
+          cpu: 200m
+          memory: 128Mi
+    #
+    #  sidecar:
+    #    resources:
+    #      limits:
+    #        cpu: 100m
+    #        memory: 128Mi
+    #      requests:
+    #        cpu: 5m
+    #        memory: 64Mi
+
+    prometheusOperator:
+      resources:
+        limits:
+          cpu: 400m
+          memory: 256Mi
+        requests:
+          cpu: 200m
+          memory: 128Mi
+    #  priorityClassName: high-priority
+
+    prometheus-node-exporter:
+      resources:
+        limits:
+          cpu: 400m
+          memory: 64Mi
+        requests:
+          cpu: 100m
+          memory: 32Mi
+    #  priorityClassName: high-priority
+
+    kube-state-metrics:
+      resources:
+        limits:
+          cpu: 200m
+          memory: 128Mi
+        requests:
+          cpu: 100m
+          memory: 64Mi
+    #  priorityClassName: high-priority
diff --git a/flux2/apps/monitoring/kustomization.yaml b/flux2/apps/monitoring/kustomization.yaml
new file mode 100644
index 000000000..31f9e6520
--- /dev/null
+++ b/flux2/apps/monitoring/kustomization.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas
+resources:
+  - pvc.yaml
+  - eventrouter-release.yaml
+  - loki-release.yaml
+  - promtail-release.yaml
+  - kube-prometheus-stack-release.yaml
+  - loki-configmap.yaml
diff --git a/flux2/apps/monitoring/loki-configmap.yaml b/flux2/apps/monitoring/loki-configmap.yaml
new file mode 100644
index 000000000..e28b3b1cc
--- /dev/null
+++ b/flux2/apps/monitoring/loki-configmap.yaml
@@ -0,0 +1,19 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  labels:
+    app: loki
+    grafana_datasource: "1"
+    release: loki
+  name: loki-datasource
+  namespace: oas
+data:
+  loki-stack-datasource.yaml: |-
+    apiVersion: 1
+    datasources:
+      - name: Loki
+        type: loki
+        access: proxy
+        url: http://loki:3100
+        version: 1
diff --git a/flux2/apps/monitoring/loki-release.yaml b/flux2/apps/monitoring/loki-release.yaml
new file mode 100644
index 000000000..df4409e10
--- /dev/null
+++ b/flux2/apps/monitoring/loki-release.yaml
@@ -0,0 +1,63 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: loki
+  namespace: oas
+spec:
+  releaseName: loki
+  chart:
+    spec:
+      chart: loki
+      version: 2.5.0
+      sourceRef:
+        kind: HelmRepository
+        name: grafana
+        namespace: flux-system
+  interval: 40m
+  values:
+    resources:
+      limits:
+        cpu: 800m
+        memory: 180Mi
+      requests:
+        cpu: 400m
+        memory: 90Mi
+    persistence:
+      enabled: true
+      accessModes:
+        - ReadWriteOnce
+      size: 10Gi
+      annotations: {}
+      # existingClaim:
+    config:
+      # https://github.com/grafana/loki/blob/main/cmd/loki/loki-local-config.yaml
+      # https://grafana.com/docs/loki/latest/operations/storage/retention
+      schema_config:
+        configs:
+          - from: 2021-05-31
+            store: boltdb-shipper
+            object_store: filesystem
+            schema: v11
+            index:
+              prefix: index_
+              period: 24h
+      storage_config:
+        boltdb_shipper:
+          active_index_directory: /data/loki/boltdb-shipper-active
+          cache_location: /data/loki/boltdb-shipper-cache
+          cache_ttl: 24h         # Can be increased for faster performance over longer query periods, uses more disk space
+          shared_store: filesystem
+        filesystem:
+          directory: /data/loki/chunks
+      compactor:
+        working_directory: /data/loki/boltdb-shipper-compactor
+        shared_store: filesystem
+      limits_config:
+        reject_old_samples: true
+        reject_old_samples_max_age: 168h  # 7 days
+      chunk_store_config:
+        max_look_back_period: 672h  # 28 days
+      table_manager:
+        retention_deletes_enabled: true
+        retention_period: 672h  # 28 days
diff --git a/flux2/apps/monitoring/promtail-release.yaml b/flux2/apps/monitoring/promtail-release.yaml
new file mode 100644
index 000000000..f5eacf743
--- /dev/null
+++ b/flux2/apps/monitoring/promtail-release.yaml
@@ -0,0 +1,55 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: promtail
+  namespace: oas
+spec:
+  releaseName: promtail
+  chart:
+    spec:
+      chart: promtail
+      version: 3.5.1
+      sourceRef:
+        kind: HelmRepository
+        name: grafana
+        namespace: flux-system
+  interval: 40m
+  values:
+    initContainer:
+      enabled: true
+      fsInotifyMaxUserInstances: 512
+    resources:
+      limits:
+        cpu: 400m
+        memory: 256Mi
+      requests:
+        cpu: 200m
+        memory: 128Mi
+    config:
+      lokiAddress: http://loki:3100/loki/api/v1/push
+      # https://github.com/grafana/helm-charts/blob/main/charts/promtail/values.yaml#L217
+      snippets:
+        # https://grafana.com/docs/loki/latest/clients/promtail/pipelines/
+        pipelineStages:
+          - cri: {}
+          - match:
+              selector: '{app="eventrouter"}'
+              stages:
+                - json:
+                    expressions:
+                      event_verb: verb
+                      event_kind: event.involvedObject.kind
+                      event_reason: event.reason
+                      event_namespace: event.involvedObject.namespace
+                      event_name: event.metadata.name
+                      event_source_host: event.source.host
+                      event_source_component: event.source.component
+                - labels:
+                    event_verb:
+                    event_kind:
+                    event_reason:
+                    event_namespace:
+                    event_name:
+                    event_source_host:
+                    event_source_component:
diff --git a/flux2/apps/monitoring/pvc.yaml b/flux2/apps/monitoring/pvc.yaml
new file mode 100644
index 000000000..c8c46cda1
--- /dev/null
+++ b/flux2/apps/monitoring/pvc.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: grafana
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 2Gi
+  storageClassName: local-path
diff --git a/flux2/apps/nextcloud/kustomization.yaml b/flux2/apps/nextcloud/kustomization.yaml
new file mode 100644
index 000000000..25df9d0a2
--- /dev/null
+++ b/flux2/apps/nextcloud/kustomization.yaml
@@ -0,0 +1,6 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas-apps
+resources:
+  - pvc.yaml
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/apps/nextcloud/pvc.yaml b/flux2/apps/nextcloud/pvc.yaml
new file mode 100644
index 000000000..e0954a9c0
--- /dev/null
+++ b/flux2/apps/nextcloud/pvc.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: nextcloud-files
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 2Gi
+  storageClassName: local-path
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: nextcloud-mariadb
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 512Mi
+  storageClassName: local-path
diff --git a/flux2/apps/nextcloud/release.yaml b/flux2/apps/nextcloud/release.yaml
new file mode 100644
index 000000000..6be2ea944
--- /dev/null
+++ b/flux2/apps/nextcloud/release.yaml
@@ -0,0 +1,198 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: nextcloud
+  namespace: oas-apps
+spec:
+  releaseName: nc
+  chart:
+    spec:
+      chart: .
+      # NOTE: Change the GitRepository yaml file if you want a different version
+      sourceRef:
+        kind: GitRepository
+        name: nextcloud
+        namespace: flux-system
+  interval: 40m
+  install:
+    timeout: 30m
+  values:
+    nextcloud:
+      nextcloud:
+        host: "files.${domain}"
+        password: "${nextcloud_password}"
+        mail:
+          enabled: ${outgoing_mail_enabled}
+          fromAddress: "${outgoing_mail_from_prefix}"
+          domain: "${outgoing_mail_domain}"
+          smtp:
+            host: "${outgoing_mail_smtp_host}"
+            secure: "tls"
+            port: "${outgoing_mail_smtp_port}"
+            name: "${outgoing_mail_smtp_user}"
+            password: "${outgoing_mail_smtp_password}"
+            authtype: "${outgoing_mail_smtp_authtype}"
+      cronjob:
+        # Set curl to accept insecure connections when acme staging is used
+        curlInsecure: "${acme_staging}"
+
+      ingress:
+        enabled: true
+        annotations:
+          # Tell cert-manager to automatically get a TLS certificate
+          kubernetes.io/tls-acme: "true"
+          # Set max body size high to allow big NextCloud uploads
+          nginx.ingress.kubernetes.io/proxy-body-size: 1G
+          nginx.ingress.kubernetes.io/server-snippet: |-
+            server_tokens off;
+            proxy_hide_header X-Powered-By;
+            rewrite ^/.well-known/webfinger /public.php?service=webfinger last;
+            rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+            rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json;
+            location = /.well-known/carddav {
+              return 301 $scheme://$host/remote.php/dav;
+            }
+            location = /.well-known/caldav {
+              return 301 $scheme://$host/remote.php/dav;
+            }
+            location = /robots.txt {
+              allow all;
+              log_not_found off;
+              access_log off;
+            }
+        hosts:
+          - "files.${domain}"
+        tls:
+          - hosts:
+              - "files.${domain}"
+            secretName: oas-nextcloud-files
+
+      # Use 2 GB of storage for NC storage (maybe make configurable later?)
+      persistence:
+        enabled: true
+        existingClaim: "nextcloud-files"
+
+      podAnnotations:
+        # Let the backup system include nextcloud data.
+        backup.velero.io/backup-volumes: "nextcloud-data"
+
+      # Explicitly disable use of internal database
+      internalDatabase:
+        enabled: false
+
+      livenessProbe:
+        initialDelaySeconds: 300
+        failureThreshold: 20
+      readinessProbe:
+        initialDelaySeconds: 300
+
+      resources:
+        limits:
+          cpu: 500m
+          memory: 512Mi
+        requests:
+          cpu: 200m
+          memory: 256Mi
+
+      # Enable and configure MariaDB chart
+      mariadb:
+        db:
+          password: "${nextcloud_mariadb_password}"
+        enabled: true
+        master:
+          annotations:
+            # Let the backup system include nextcloud database data.
+            backup.velero.io/backup-volumes: "data"
+          persistence:
+            ## Enable PostgreSQL persistence using Persistent Volume Claims.
+            enabled: true
+            existingClaim: "nextcloud-mariadb"
+          resources:
+            limits:
+              cpu: 200m
+              memory: 512Mi
+            requests:
+              cpu: 100m
+              memory: 256Mi
+        replication:
+          enabled: false
+        rootUser:
+          password: "${nextcloud_mariadb_root_password}"
+
+    setupApps:
+      backoffLimit: 20
+
+    onlyoffice:
+      resources:
+        limits:
+          cpu: 800m
+          memory: 2Gi
+        requests:
+          cpu: 200m
+          memory: 1Gi
+      server_name: "office.${domain}"
+      ingress:
+        enabled: true
+        annotations:
+          # Tell cert-manager to automatically get a TLS certificate
+          kubernetes.io/tls-acme: "true"
+        paths:
+          - "/"
+        hosts:
+          - "office.${domain}"
+        tls:
+          - hosts:
+              - "office.${domain}"
+            secretName: oas-nextcloud-office
+      jwtSecret: "${onlyoffice_jwt_secret}"
+
+    postgresql:
+      postgresqlPassword: "${onlyoffice_postgresql_password}"
+      resources:
+        limits:
+          cpu: 200m
+          memory: 256Mi
+        requests:
+          cpu: 100m
+          memory: 128Mi
+
+    rabbitmq:
+      rabbitmq:
+        password: "${onlyoffice_rabbitmq_password}"
+      persistence:
+        enabled: false
+      resources:
+        limits:
+          cpu: 500m
+          memory: 512Mi
+        requests:
+          cpu: 200m
+          memory: 256Mi
+      livenessProbe:
+        initialDelaySeconds: 180
+        failureThreshold: 10
+
+    redis:
+      cluster:
+        enabled: false
+      master:
+        persistence:
+          enabled: false
+        resources:
+          limits:
+            cpu: 100m
+            memory: 64Mi
+          requests:
+            cpu: 50m
+            memory: 32Mi
+
+    sociallogin:
+      server_name: "sso.${domain}"
+      client_id: nextcloud
+      client_secret: "${nextcloud_oauth_client_secret}"
+      groups_claim: "openappstack_roles"
+  valuesFrom:
+    - kind: ConfigMap
+      name: nextcloud-values-override
+      optional: true
diff --git a/flux2/apps/rocketchat/kustomization.yaml b/flux2/apps/rocketchat/kustomization.yaml
new file mode 100644
index 000000000..069d8f571
--- /dev/null
+++ b/flux2/apps/rocketchat/kustomization.yaml
@@ -0,0 +1,5 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas-apps
+resources:
+  - release.yaml
diff --git a/flux2/apps/rocketchat/release.yaml b/flux2/apps/rocketchat/release.yaml
new file mode 100644
index 000000000..881d815d5
--- /dev/null
+++ b/flux2/apps/rocketchat/release.yaml
@@ -0,0 +1,141 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: rocketchat
+  namespace: oas-apps
+spec:
+  releaseName: rocketchat
+  chart:
+    spec:
+      chart: rocketchat
+      version: 2.0.10
+      sourceRef:
+        kind: HelmRepository
+        name: helm-stable
+        namespace: flux-system
+  interval: 20m
+  install:
+    timeout: 15m
+  values:
+    # Hostname for Rocket.chat
+    host: "chat.${domain}"
+
+    # Extra environment variables for Rocket.Chat. Used with tpl function, so this
+    # needs to be a string
+    extraEnv: |
+      - name: ADMIN_USERNAME
+        value: admin
+      - name: ADMIN_PASS
+        value: "${rocketchat_admin_password}"
+      - name: ADMIN_EMAIL
+        value: "${admin_email}"
+        # Set setup wizard to completed. The setup wizard, that allows you to
+        # create a different admin user, gets skipped.
+      - name: OVERWRITE_SETTING_Show_Setup_Wizard
+        value: completed
+      - name: E2E_Enable
+        value: "true"
+      - name: Accounts_RegistrationForm
+        value: Disabled
+      - name: Accounts_RegistrationForm_LinkReplacementText
+        value: "Create a new account at admin.${domain} to add users"
+      # Custom OAuth rules:
+      - name: Accounts_OAuth_Custom_Openappstack
+        value: "true"
+      - name: Accounts_OAuth_Custom_Openappstack_url
+        value: https://sso.${domain}
+      - name: Accounts_OAuth_Custom_Openappstack_token_path
+        value: /oauth2/token
+      - name: Accounts_OAuth_Custom_Openappstack_token_sent_via
+        value: payload
+      - name: Accounts_OAuth_Custom_Openappstack_identity_token_sent_via
+        value: payload
+      - name: Accounts_OAuth_Custom_Openappstack_identity_path
+        value: /userinfo
+      - name: Accounts_OAuth_Custom_Openappstack_authorize_path
+        value: /oauth2/auth
+      - name: Accounts_OAuth_Custom_Openappstack_scope
+        value: openid profile openappstack_roles email
+      - name: Accounts_OAuth_Custom_Openappstack_id
+        value: rocketchat
+      - name: Accounts_OAuth_Custom_Openappstack_secret
+        value: ${rocketchat_oauth_client_secret}
+      - name: Accounts_OAuth_Custom_Openappstack_login_style
+        value: redirect
+      - name: Accounts_OAuth_Custom_Openappstack_button_label_text
+        value: Login via OpenAppStack
+      - name: Accounts_OAuth_Custom_Openappstack_button_label_color
+        value: "#FFFFFF"
+      - name: Accounts_OAuth_Custom_Openappstack_button_color
+        value: "#1d74f5"
+      - name: Accounts_OAuth_Custom_Openappstack_username_field
+        value: preferred_username
+      - name: Accounts_OAuth_Custom_Openappstack_name_field
+        value: preferred_username
+      - name: Accounts_OAuth_Custom_Openappstack_roles_claim
+        value: openappstack_roles
+      - name: Accounts_OAuth_Custom_Openappstack_merge_roles
+        value: "true"
+      - name: Accounts_OAuth_Custom_Openappstack_merge_users
+        value: "true"
+      - name: Accounts_OAuth_Custom_Openappstack_show_button
+        value: "true"
+
+    livenessProbe:
+      initialDelaySeconds: 180
+      failureThreshold: 20
+    readinessProbe:
+      initialDelaySeconds: 60
+      timeoutSeconds: 10
+
+    ingress:
+      enabled: true
+      annotations:
+        # Tell cert-manager to automatically get a TLS certificate
+        kubernetes.io/tls-acme: "true"
+      tls:
+        - hosts:
+            - "chat.${domain}"
+          secretName: oas-rocketchat
+
+    persistence:
+      enabled: true
+      size: 1Gi
+      # FIXME: This valuee leads to an unused PVC, which helm-controller does
+      # not like.
+      # existingClaim: "rocketchat-data"
+
+    podAnnotations:
+      # Let the backup system include rocketchat data.
+      backup.velero.io/backup-volumes: "rocket-data"
+
+    resources:
+      limits:
+        cpu: 400m
+        memory: 1024Mi
+      requests:
+        cpu: 100m
+        memory: 768Mi
+
+    mongodb:
+      mongodbRootPassword: ${mongodb_root_password}
+      mongodbPassword: ${mongodb_password}
+      podAnnotations:
+        # Let the backup system include rocketchat data stored in mongodb.
+        backup.velero.io/backup-volumes: "datadir"
+      persistence:
+        enabled: true
+        # FIXME: This value is ignored by the chart currently in use
+        # existingClaim: "rocketchat-mongodb"
+      resources:
+        limits:
+          cpu: 600m
+          memory: 1024Mi
+        requests:
+          cpu: 300m
+          memory: 768Mi
+
+    image:
+      tag: 3.15.0
+      pullPolicy: IfNotPresent
diff --git a/flux2/apps/velero/kustomization.yaml b/flux2/apps/velero/kustomization.yaml
new file mode 100644
index 000000000..c95bcc7fb
--- /dev/null
+++ b/flux2/apps/velero/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: velero
+resources:
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/apps/velero/release.yaml b/flux2/apps/velero/release.yaml
new file mode 100644
index 000000000..3cbdba999
--- /dev/null
+++ b/flux2/apps/velero/release.yaml
@@ -0,0 +1,127 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: velero
+  namespace: velero
+spec:
+  releaseName: velero
+  chart:
+    spec:
+      chart: velero
+      version: 2.11.0
+      sourceRef:
+        kind: HelmRepository
+        name: vmware-tanzu
+        namespace: flux-system
+  interval: 40m
+  values:
+    # Init containers to add to the Velero deployment's pod spec. At least one
+    # plugin provider image is required.
+    initContainers:
+      - name: velero-plugin-for-aws
+        image: velero/velero-plugin-for-aws:v1.1.0
+        imagePullPolicy: IfNotPresent
+        volumeMounts:
+          - mountPath: /target
+            name: plugins
+
+    # Settings for Velero's prometheus metrics. Enabled by default.
+    metrics:
+      enabled: true
+      scrapeInterval: 30s
+
+      # Pod annotations for Prometheus
+      podAnnotations:
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "8085"
+        prometheus.io/path: "/metrics"
+
+      serviceMonitor:
+        enabled: false
+        additionalLabels: {}
+
+    # Install CRDs as a templates. Enabled by default.
+    installCRDs: true
+
+    ##
+    ## Parameters for the `default` BackupStorageLocation and VolumeSnapshotLocation,
+    ## and additional server settings.
+    ##
+    configuration:
+      # Cloud provider being used (e.g. aws, azure, gcp).
+      # We don't use aws, but ceph which is S3-compatible.
+      provider: aws
+
+      # Parameters for the `default` BackupStorageLocation. See
+      # https://velero.io/docs/v1.0.0/api-types/backupstoragelocation/
+      backupStorageLocation:
+        # Cloud provider where backups should be stored. Usually should
+        # match `configuration.provider`. Required.
+        # The name "default" seems to be special: backups that don't have a
+        # location specified will use this one.
+        name: default
+        # Provider for the backup storage location. If omitted
+        # `configuration.provider` will be used instead.
+        # provider:
+        # Bucket to store backups in. Required.
+        bucket: ${backup_s3_bucket}
+        # Prefix within bucket under which to store backups. Optional.
+        prefix: ${backup_s3_prefix}
+        # Additional provider-specific configuration. See link above
+        # for details of required/optional fields for your provider.
+        config:
+          s3ForcePathStyle: true
+          s3Url: ${backup_s3_url}
+          region: ${backup_s3_region}
+
+    rbac:
+      # Whether to create the Velero role and role binding to give all permissions to the namespace to Velero.
+      create: true
+      # Whether to create the cluster role binding to give administrator permissions to Velero
+      clusterAdministrator: true
+
+    # Information about the Kubernetes service account Velero uses.
+    serviceAccount:
+      server:
+        create: true
+        name:
+        annotations:
+
+    # Info about the secret to be used by the Velero deployment, which
+    # should contain credentials for the cloud provider IAM account you've
+    # set up for Velero.
+    credentials:
+      useSecret: true
+      secretContents:
+        cloud: |
+          [default]
+          aws_access_key_id=${backup_s3_aws_access_key_id}
+          aws_secret_access_key=${backup_s3_aws_secret_access_key}
+
+    # Whether to create backupstoragelocation crd, if false => do not create a default backup location
+    backupsEnabled: true
+    # Whether to create volumesnapshotlocation crd, if false => disable snapshot feature
+    snapshotsEnabled: false
+
+    # Whether to deploy the restic daemonset.
+    deployRestic: true
+
+    restic:
+      podVolumePath: /var/lib/kubelet/pods
+      privileged: true
+
+    # Backup schedules to create.
+    schedules:
+      # This is just a name, can be anything.
+      nightly:
+        # Every night at 3:30.
+        schedule: "30 3 * * *"
+        template:
+          # Backups are stored for 60 days (1440 hours).
+          ttl: "1440h"
+          includedNamespaces:
+            # We include all namespaces.
+            - '*'
+
+    configMaps: {}
diff --git a/flux2/apps/wordpress/kustomization.yaml b/flux2/apps/wordpress/kustomization.yaml
new file mode 100644
index 000000000..277eff447
--- /dev/null
+++ b/flux2/apps/wordpress/kustomization.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas-apps
+resources:
+  - pvc.yaml
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/apps/wordpress/pvc.yaml b/flux2/apps/wordpress/pvc.yaml
new file mode 100644
index 000000000..adafcae0b
--- /dev/null
+++ b/flux2/apps/wordpress/pvc.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: wordpress-files
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 2Gi
+  storageClassName: local-path
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: wordpress-mariadb
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 512Mi
+  storageClassName: local-path
\ No newline at end of file
diff --git a/flux2/apps/wordpress/release.yaml b/flux2/apps/wordpress/release.yaml
new file mode 100644
index 000000000..61fc9aafc
--- /dev/null
+++ b/flux2/apps/wordpress/release.yaml
@@ -0,0 +1,103 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: wordpress
+  namespace: oas-apps
+spec:
+  releaseName: wordpress
+  chart:
+    spec:
+      chart: .
+      # NOTE: Change the GitRepository yaml file if you want a different version
+      sourceRef:
+        kind: GitRepository
+        name: wordpress
+        namespace: flux-system
+  interval: 40m
+  install:
+    timeout: 30m
+  values:
+    wordpress:
+      config:
+        db:
+          prefix: wp_
+        adm:
+          usid: admin
+          pssw: "${wordpress_admin_password}"
+      site:
+        # NOTE: Make sure you use underscore and that the localisation is in full caps
+        locale: en_US
+        url: "https://www.${domain}"
+        title: "OpenAppStack website"
+    
+    persistence:
+      existingClaim: wordpress-files
+    podAnnotations:
+      backup.velero.io/backup-volumes: "wordpress-wp-uploads"
+    
+    openid_connect_settings:
+      enabled: true
+      client_secret: ${wordpress_oauth_client_secret}
+      endpoint_login: https://sso.${domain}/oauth2/auth
+      endpoint_userinfo: https://sso.${domain}/userinfo
+      endpoint_token: https://sso.${domain}/oauth2/token
+      endpoint_end_session: ""
+      # After our SSO supports it, we should set this as the logout URL
+      # https://open.greenhost.net/openappstack/single-sign-on/issues/28
+      # endpoint_end_session: https://sso.${domain}/oauth2/sessions/logout
+      no_sslverify: "0"
+      http_request_timeout: "15"
+      enable_logging: "1"
+      scope: email profile openid openappstack_roles offline_access
+      role_mapping_enabled: true
+      role_key: openappstack_roles
+    
+    database:
+      db:
+        user: wordpress
+        password: "${wordpress_mariadb_password}"
+      rootUser:
+        password: "${wordpress_mariadb_root_password}"
+      master:
+        persistence:
+          ## Enable MariaDB persistence using Persistent Volume Claims.
+          enabled: true
+          existingClaim: "wordpress-mariadb"
+        annotations:
+          # Let the backup system include nextcloud database data.
+          backup.velero.io/backup-volumes: "data"
+        resources:
+          limits:
+            cpu: 200m
+            memory: 512Mi
+          requests:
+            cpu: 100m
+            memory: 256Mi
+      replication:
+        enabled: false
+    
+    # It's advisable to set resource limits to prevent your K8s cluster from
+    # crashing
+    resources:
+      limits:
+        cpu: 500m
+        memory: 256Mi
+      requests:
+        cpu: 100m
+        memory: 128Mi
+    
+    ingress:
+      enabled: true
+      annotations:
+        kubernetes.io/tls-acme: "true"
+      path: /
+      hosts:
+        - "www.${domain}"
+        - "${domain}"
+      tls:
+        - hosts:
+            - "www.${domain}"
+            - "${domain}"
+          secretName: oas-wordpress
+    
\ No newline at end of file
diff --git a/flux2/cluster/base/core.yaml b/flux2/cluster/base/core.yaml
new file mode 100644
index 000000000..830a26644
--- /dev/null
+++ b/flux2/cluster/base/core.yaml
@@ -0,0 +1,23 @@
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: core
+  namespace: flux-system
+spec:
+  dependsOn:
+    - name: infrastructure
+  interval: 1m0s
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/core
+  prune: true
+  validation: client
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-single-sign-on-variables
+      - kind: Secret
+        name: oas-oauth-variables
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/base/infrastructure.yaml b/flux2/cluster/base/infrastructure.yaml
new file mode 100644
index 000000000..aa1dbd493
--- /dev/null
+++ b/flux2/cluster/base/infrastructure.yaml
@@ -0,0 +1,14 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: infrastructure
+  namespace: flux-system
+spec:
+  interval: 10m
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/infrastructure
+  prune: true
+  validation: client
diff --git a/flux2/cluster/base/monitoring.yaml b/flux2/cluster/base/monitoring.yaml
new file mode 100644
index 000000000..930ab2d2f
--- /dev/null
+++ b/flux2/cluster/base/monitoring.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: monitoring
+  namespace: flux-system
+spec:
+  interval: 45m
+  dependsOn:
+    - name: core
+    - name: infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/apps/monitoring
+  prune: true
+  validation: client
+  # healthChecks:
+  #   - apiVersion: helm.toolkit.fluxcd.io/v1beta1
+  #     kind: HelmRelease
+  #     name: podinfo
+  #     namespace: podinfo
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-kube-prometheus-stack-variables
+      - kind: Secret
+        name: oas-oauth-variables
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/optional/nextcloud/nextcloud.yaml b/flux2/cluster/optional/nextcloud/nextcloud.yaml
new file mode 100644
index 000000000..ed6a58caa
--- /dev/null
+++ b/flux2/cluster/optional/nextcloud/nextcloud.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: nextcloud
+  namespace: flux-system
+spec:
+  interval: 45m
+  dependsOn:
+    - name: core
+    - name: infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/apps/nextcloud
+  prune: true
+  validation: client
+  # healthChecks:
+  #   - apiVersion: helm.toolkit.fluxcd.io/v1beta1
+  #     kind: HelmRelease
+  #     name: podinfo
+  #     namespace: podinfo
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-nextcloud-variables
+      - kind: Secret
+        name: oas-oauth-variables
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/optional/rocketchat/rocketchat.yaml b/flux2/cluster/optional/rocketchat/rocketchat.yaml
new file mode 100644
index 000000000..080cbd554
--- /dev/null
+++ b/flux2/cluster/optional/rocketchat/rocketchat.yaml
@@ -0,0 +1,30 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: rocketchat
+  namespace: flux-system
+spec:
+  interval: 10m0s
+  dependsOn:
+    - name: core
+    - name: infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/apps/rocketchat
+  prune: true
+  validation: client
+  # healthChecks:
+  #   - apiVersion: helm.toolkit.fluxcd.io/v1beta1
+  #     kind: HelmRelease
+  #     name: podinfo
+  #     namespace: podinfo
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-rocketchat-variables
+      - kind: Secret
+        name: oas-oauth-variables
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/optional/velero/velero.yaml b/flux2/cluster/optional/velero/velero.yaml
new file mode 100644
index 000000000..c89ad010b
--- /dev/null
+++ b/flux2/cluster/optional/velero/velero.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: velero
+  namespace: flux-system
+spec:
+  interval: 45m
+  dependsOn:
+    - name: core
+    - name: infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/apps/velero
+  prune: true
+  validation: client
+  # healthChecks:
+  #   - apiVersion: helm.toolkit.fluxcd.io/v1beta1
+  #     kind: HelmRelease
+  #     name: podinfo
+  #     namespace: podinfo
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/optional/wordpress/wordpress.yaml b/flux2/cluster/optional/wordpress/wordpress.yaml
new file mode 100644
index 000000000..6fc4bc4df
--- /dev/null
+++ b/flux2/cluster/optional/wordpress/wordpress.yaml
@@ -0,0 +1,25 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: wordpress
+  namespace: flux-system
+spec:
+  interval: 45m
+  dependsOn:
+    - name: core
+    - name: infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/apps/wordpress
+  prune: true
+  validation: client
+  postBuild:
+    substituteFrom:
+      - kind: Secret
+        name: oas-wordpress-variables
+      - kind: Secret
+        name: oas-oauth-variables
+      - kind: Secret
+        name: oas-cluster-variables
diff --git a/flux2/cluster/test/all-optional.yaml b/flux2/cluster/test/all-optional.yaml
new file mode 100644
index 000000000..d24d824c5
--- /dev/null
+++ b/flux2/cluster/test/all-optional.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: all-optional
+  namespace: flux-system
+spec:
+  dependsOn:
+    - name: base
+  interval: 10m0s
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/cluster/optional
+  prune: true
+  validation: client
diff --git a/flux2/cluster/test/base.yaml b/flux2/cluster/test/base.yaml
new file mode 100644
index 000000000..e185ff1e9
--- /dev/null
+++ b/flux2/cluster/test/base.yaml
@@ -0,0 +1,14 @@
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: base
+  namespace: flux-system
+spec:
+  interval: 10m0s
+  sourceRef:
+    kind: GitRepository
+    name: openappstack
+  path: ./flux2/cluster/base
+  prune: true
+  validation: client
diff --git a/flux2/core/base/cluster-issuer/cluster-issuer.yaml b/flux2/core/base/cluster-issuer/cluster-issuer.yaml
new file mode 100644
index 000000000..a9de6a87b
--- /dev/null
+++ b/flux2/core/base/cluster-issuer/cluster-issuer.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: cert-manager.io/v1alpha2
+kind: ClusterIssuer
+metadata:
+  name: letsencrypt-issuer
+spec:
+  acme:
+    email: ${admin_email}
+    # overwrite this to "https://acme-staging-v02.api.letsencrypt.org/directory" for staging
+    server: ${acme_server}
+    privateKeySecretRef:
+      # Secret resource used to store the account's private key.
+      name: letsencrypt-account-key
+    # Enable the HTTP01 challenge mechanism for this Issuer
+    solvers:
+    - http01:
+        ingress:
+          class: nginx
diff --git a/flux2/core/base/cluster-issuer/kustomization.yaml b/flux2/core/base/cluster-issuer/kustomization.yaml
new file mode 100644
index 000000000..5b5d3a7f8
--- /dev/null
+++ b/flux2/core/base/cluster-issuer/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: cert-manager
+resources:
+  - cluster-issuer.yaml
\ No newline at end of file
diff --git a/flux2/core/base/metallb/kustomization.yaml b/flux2/core/base/metallb/kustomization.yaml
new file mode 100644
index 000000000..7d7b5c993
--- /dev/null
+++ b/flux2/core/base/metallb/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: kube-system
+resources:
+  - release.yaml
diff --git a/flux2/core/base/metallb/release.yaml b/flux2/core/base/metallb/release.yaml
new file mode 100644
index 000000000..8127c4873
--- /dev/null
+++ b/flux2/core/base/metallb/release.yaml
@@ -0,0 +1,27 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: metallb
+  namespace: oas
+spec:
+  releaseName: metallb
+  chart:
+    spec:
+      chart: metallb
+      version: 0.1.23
+      sourceRef:
+        kind: HelmRepository
+        name: bitnami
+        namespace: flux-system
+  interval: 40m
+  install:
+    timeout: 2m
+  values:
+    # https://artifacthub.io/packages/helm/bitnami/metallb#example-layer-2-configuration
+    configInline:
+      address-pools:
+        - name: default
+          protocol: layer2
+          addresses:
+            - "${ip_address}/32"
diff --git a/flux2/core/base/single-sign-on/kustomization.yaml b/flux2/core/base/single-sign-on/kustomization.yaml
new file mode 100644
index 000000000..20d46f714
--- /dev/null
+++ b/flux2/core/base/single-sign-on/kustomization.yaml
@@ -0,0 +1,7 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas
+resources:
+  - pvc.yaml
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/core/base/single-sign-on/pvc.yaml b/flux2/core/base/single-sign-on/pvc.yaml
new file mode 100644
index 000000000..a65723395
--- /dev/null
+++ b/flux2/core/base/single-sign-on/pvc.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: single-sign-on-userbackend
+spec:
+  accessModes:
+    - ReadWriteOnce
+  volumeMode: Filesystem
+  resources:
+    requests:
+      storage: 1Gi
+  storageClassName: local-path
\ No newline at end of file
diff --git a/flux2/core/base/single-sign-on/release.yaml b/flux2/core/base/single-sign-on/release.yaml
new file mode 100644
index 000000000..4215607f1
--- /dev/null
+++ b/flux2/core/base/single-sign-on/release.yaml
@@ -0,0 +1,149 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: single-sign-on
+spec:
+  releaseName: single-sign-on
+  dependsOn:
+    - name: nginx
+  chart:
+    spec:
+      chart: ./helmchart/single-sign-on/
+      # NOTE: Change the GitRepository yaml file if you want a different version
+      sourceRef:
+        kind: GitRepository
+        name: single-sign-on
+        namespace: flux-system
+  interval: 1h0m0s
+  install:
+    remediation:
+      retries: 3
+  values:
+    singleSignOnHost: &SSO_HOST "sso.${domain}"
+
+    userpanel:
+      applicationName: &USER_PANEL user-panel
+      ingress:
+        host: "admin.${domain}"
+
+    userbackend:
+      applications:
+        - name: *USER_PANEL
+          description: Administration interface to manage user accounts
+        - name: &NEXTCLOUD nextcloud
+          description: "Nextcloud Files offers an on-premise Universal File Access and sync platform with powerful collaboration capabilities and desktop, mobile and web interfaces."
+        - name: &WORDPRESS wordpress
+          description: "WordPress website hosting."
+        - name: &ROCKETCHAT rocketchat
+          description: "Communicate and collaborate using team chat and switch to video or audio calls with screen sharing for more efficient teamwork."
+        - name: &GRAFANA grafana
+          description: "Grafana allows you to query, visualize, alert on and understand metrics generated by OpenAppStack. It can be used to create explore and share dashboards."
+      username: "${userbackend_admin_username}"
+      password: "${userbackend_admin_password}"
+      email: "${userbackend_admin_email}"
+      postgres:
+        password: "${userbackend_postgres_password}"
+      persistence:
+        enabled: true
+        size: 1Gi
+        existingClaim: single-sign-on-userbackend
+      podAnnotations:
+        # Let the backup system include nextcloud database data.
+        backup.velero.io/backup-volumes: "database"
+
+    hydra:
+      hydra:
+        config:
+          urls:
+            self:
+              issuer: "https://sso.${domain}"
+            login: "https://sso.${domain}/login"
+            consent: "https://sso.${domain}/consent"
+          secrets:
+            system: "${hydra_system_secret}"
+          dsn: "memory"
+      ingress:
+        public:
+          enabled: true
+          annotations:
+            kubernetes.io/tls-acme: "true"
+          hosts:
+            - host: *SSO_HOST
+              paths: ["/"]
+          tls:
+            - hosts:
+              - *SSO_HOST
+              secretName: hydra-public.tls
+        admin:
+          enabled: false
+
+    oAuthClients:
+    - clientName: *USER_PANEL
+      clientSecret: "${userpanel_oauth_client_secret}"
+      redirectUri: "https://admin.${domain}/callback"
+      scopes: "openid profile email openappstack_roles"
+      clientUri: "https://admin.${domain}"
+      clientLogoUri: "https://admin.${domain}/favicon.ico"
+      tokenEndpointAuthMethod: "client_secret_basic"
+      responseTypes:
+        - "token"
+      grantTypes:
+        - "implicit"
+    - clientName: *NEXTCLOUD
+      clientSecret: "${nextcloud_oauth_client_secret}"
+      redirectUri: "https://files.${domain}/apps/sociallogin/custom_oidc/oas"
+      scopes: "openid profile email openappstack_roles"
+      clientUri: "https://files.${domain}"
+      clientLogoUri: "https://files.${domain}/core/img/favicon-touch.png"
+      tokenEndpointAuthMethod: "client_secret_post"
+      responseTypes:
+        - "code"
+        - "id_token"
+      grantTypes:
+        - "authorization_code"
+        - "refresh_token"
+        - "client_credentials"
+    - clientName: *WORDPRESS
+      clientSecret: "${wordpress_oauth_client_secret}"
+      redirectUri: "https://www.${domain}/wp-admin/admin-ajax.php?action=openid-connect-authorize"
+      scopes: "openid profile email openappstack_roles offline_access"
+      clientUri: "https://www.${domain}"
+      clientLogoUri: "https://www.${domain}/wp-admin/images/wordpress-logo.svg"
+      tokenEndpointAuthMethod: "client_secret_post"
+      responseTypes:
+        - "code"
+        - "id_token"
+      grantTypes:
+        - "authorization_code"
+        - "refresh_token"
+        - "client_credentials"
+        - "implicit"
+    - clientName: *ROCKETCHAT
+      clientSecret: "${rocketchat_oauth_client_secret}"
+      redirectUri: "https://chat.${domain}/_oauth/openappstack"
+      scopes: "openid profile email openappstack_roles"
+      clientUri: "https://chat.${domain}"
+      clientLogoUri: "https://chat.${domain}/images/logo/logo.svg"
+      tokenEndpointAuthMethod: "client_secret_post"
+      responseTypes:
+        - "code"
+        - "id_token"
+      grantTypes:
+        - "authorization_code"
+        - "refresh_token"
+        - "client_credentials"
+    - clientName: *GRAFANA
+      clientSecret: "${grafana_oauth_client_secret}"
+      redirectUri: "https://grafana.${domain}/login/generic_oauth"
+      scopes: "openid profile email openappstack_roles"
+      clientUri: "https://grafana.${domain}"
+      clientLogoUri: "https://grafana.${domain}/public/img/grafana_icon.svg"
+      tokenEndpointAuthMethod: "client_secret_post"
+      responseTypes:
+        - "code"
+        - "id_token"
+      grantTypes:
+        - "authorization_code"
+        - "refresh_token"
+        - "client_credentials"
diff --git a/flux2/infrastructure/cert-manager/kustomization.yaml b/flux2/infrastructure/cert-manager/kustomization.yaml
new file mode 100644
index 000000000..972010560
--- /dev/null
+++ b/flux2/infrastructure/cert-manager/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: cert-manager
+resources:
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/infrastructure/cert-manager/release.yaml b/flux2/infrastructure/cert-manager/release.yaml
new file mode 100644
index 000000000..ca524e844
--- /dev/null
+++ b/flux2/infrastructure/cert-manager/release.yaml
@@ -0,0 +1,47 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: cert-manager
+spec:
+  releaseName: cert-manager
+  chart:
+    spec:
+      chart: cert-manager
+      sourceRef:
+        kind: HelmRepository
+        name: jetstack
+        namespace: flux-system
+      version: 1.3.1
+  interval: 1h0m0s
+  install:
+    remediation:
+      retries: 3
+  values:
+    ingressShim:
+      defaultIssuerName: letsencrypt-issuer
+      defaultIssuerKind: ClusterIssuer
+    resources:
+      requests:
+        cpu: 100m
+        memory: 256Mi
+      limits:
+        cpu: 100m
+        memory: 512Mi
+    cainjector:
+      resources:
+        requests:
+          cpu: 100m
+          memory: 384Mi
+        limits:
+          cpu: 100m
+          memory: 768Mi
+    webhook:
+      resources:
+        requests:
+          cpu: 100m
+          memory: 40Mi
+        limits:
+          cpu: 100m
+          memory: 80Mi
+    installCRDs: true
diff --git a/flux2/infrastructure/local-path-provisioner/kustomization.yaml b/flux2/infrastructure/local-path-provisioner/kustomization.yaml
new file mode 100644
index 000000000..7d7b5c993
--- /dev/null
+++ b/flux2/infrastructure/local-path-provisioner/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: kube-system
+resources:
+  - release.yaml
diff --git a/flux2/infrastructure/local-path-provisioner/release.yaml b/flux2/infrastructure/local-path-provisioner/release.yaml
new file mode 100644
index 000000000..bd8afc1b7
--- /dev/null
+++ b/flux2/infrastructure/local-path-provisioner/release.yaml
@@ -0,0 +1,37 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: local-path-provisioner
+spec:
+  releaseName: local-path-provisioner
+  chart:
+    spec:
+      chart: ./deploy/chart
+      sourceRef:
+        kind: GitRepository
+        name: local-path-provisioner
+        namespace: flux-system
+  interval: 1h0m0s
+  install:
+    remediation:
+      retries: 3
+  values:
+    nodePathMap:
+      - node: DEFAULT_PATH_FOR_NON_LISTED_NODES
+        paths:
+          - "/var/lib/OpenAppStack/local-storage"
+    storageClass:
+      defaultClass: true
+    # We temporarily use our own build in order to use local volumes instead of
+    # hostPath.
+    image:
+      repository: "open.greenhost.net:4567/openappstack/openappstack/local-path-provisioner"
+      tag: "52f994f-amd64"
+    resources:
+      requests:
+        cpu: 200m
+        memory: 20Mi
+      limits:
+        cpu: 400m
+        memory: 40Mi
\ No newline at end of file
diff --git a/flux2/infrastructure/namespaces/cert-manager.yaml b/flux2/infrastructure/namespaces/cert-manager.yaml
new file mode 100644
index 000000000..c90416ff4
--- /dev/null
+++ b/flux2/infrastructure/namespaces/cert-manager.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: cert-manager
diff --git a/flux2/infrastructure/namespaces/kustomization.yaml b/flux2/infrastructure/namespaces/kustomization.yaml
new file mode 100644
index 000000000..1d9d771f6
--- /dev/null
+++ b/flux2/infrastructure/namespaces/kustomization.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+  - cert-manager.yaml
+  - oas.yaml
+  - oas-apps.yaml
+  - velero.yaml
diff --git a/flux2/infrastructure/namespaces/oas-apps.yaml b/flux2/infrastructure/namespaces/oas-apps.yaml
new file mode 100644
index 000000000..0ae83a7ac
--- /dev/null
+++ b/flux2/infrastructure/namespaces/oas-apps.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: oas-apps
\ No newline at end of file
diff --git a/flux2/infrastructure/namespaces/oas.yaml b/flux2/infrastructure/namespaces/oas.yaml
new file mode 100644
index 000000000..a960e21ab
--- /dev/null
+++ b/flux2/infrastructure/namespaces/oas.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: oas
diff --git a/flux2/infrastructure/namespaces/velero.yaml b/flux2/infrastructure/namespaces/velero.yaml
new file mode 100644
index 000000000..ba69c056c
--- /dev/null
+++ b/flux2/infrastructure/namespaces/velero.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: velero
\ No newline at end of file
diff --git a/flux2/infrastructure/nginx/kustomization.yaml b/flux2/infrastructure/nginx/kustomization.yaml
new file mode 100644
index 000000000..9045cb5d9
--- /dev/null
+++ b/flux2/infrastructure/nginx/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: oas
+resources:
+  - release.yaml
diff --git a/flux2/infrastructure/nginx/release.yaml b/flux2/infrastructure/nginx/release.yaml
new file mode 100644
index 000000000..1c2514019
--- /dev/null
+++ b/flux2/infrastructure/nginx/release.yaml
@@ -0,0 +1,42 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: nginx
+spec:
+  releaseName: ingress-nginx
+  chart:
+    spec:
+      chart: ingress-nginx
+      sourceRef:
+        kind: HelmRepository
+        name: ingress-nginx
+        namespace: flux-system
+      version: "3.31.0"
+  interval: 1h0m0s
+  install:
+    remediation:
+      retries: 3
+  values:
+    controller:
+      image:
+        # Disable image digest validation until flux supports it
+        # https://github.com/fluxcd/flux/issues/3189
+        digest: ''
+      service:
+        ## Set external traffic policy to: "Local" to preserve source IP on
+        ## providers supporting it
+        ## Ref: https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer
+        externalTrafficPolicy: Local
+      config:
+        # http://nginx.org/en/docs/http/ngx_http_access_module.html
+        # comma separated list of CIDRs, e.g. 10.0.0.0/24,172.10.0.1.
+        # By default we allow all access from everywhere
+        whitelist-source-range: '0.0.0.0/0'
+      resources:
+        limits:
+          cpu: 200m
+          memory: 1Gi
+        requests:
+          cpu: 100m
+          memory: 64Mi
diff --git a/flux2/infrastructure/secrets/kustomization.yaml b/flux2/infrastructure/secrets/kustomization.yaml
new file mode 100644
index 000000000..c6fd78466
--- /dev/null
+++ b/flux2/infrastructure/secrets/kustomization.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+# The secrets will be used by Flux in the postBuild Kustomization step and need
+# to be in the flux-system namespace
+namespace: flux-system
+resources:
+  - release.yaml
\ No newline at end of file
diff --git a/flux2/infrastructure/secrets/release.yaml b/flux2/infrastructure/secrets/release.yaml
new file mode 100644
index 000000000..a64f39be9
--- /dev/null
+++ b/flux2/infrastructure/secrets/release.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  name: oas-secrets
+spec:
+  releaseName: oas-secrets
+  chart:
+    spec:
+      chart: ./charts/oas-secrets/
+      # NOTE: Change the GitRepository yaml file if you want a different version
+      sourceRef:
+        kind: GitRepository
+        name: openappstack
+        namespace: flux-system
+  interval: 1h0m0s
diff --git a/flux2/infrastructure/sources/bitnami.yaml b/flux2/infrastructure/sources/bitnami.yaml
new file mode 100644
index 000000000..fc43978de
--- /dev/null
+++ b/flux2/infrastructure/sources/bitnami.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: bitnami
+spec:
+  interval: 5m
+  url: https://charts.bitnami.com/bitnami
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/grafana.yaml b/flux2/infrastructure/sources/grafana.yaml
new file mode 100644
index 000000000..a31873aba
--- /dev/null
+++ b/flux2/infrastructure/sources/grafana.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: grafana
+spec:
+  interval: 5m
+  url: https://grafana.github.io/helm-charts
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/helm-stable.yaml b/flux2/infrastructure/sources/helm-stable.yaml
new file mode 100644
index 000000000..937aa3139
--- /dev/null
+++ b/flux2/infrastructure/sources/helm-stable.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: helm-stable
+spec:
+  interval: 5m
+  url: https://charts.helm.sh/stable
diff --git a/flux2/infrastructure/sources/ingress-nginx.yaml b/flux2/infrastructure/sources/ingress-nginx.yaml
new file mode 100644
index 000000000..d58ea237d
--- /dev/null
+++ b/flux2/infrastructure/sources/ingress-nginx.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: ingress-nginx
+spec:
+  interval: 1h
+  url: https://kubernetes.github.io/ingress-nginx
diff --git a/flux2/infrastructure/sources/jetstack.yaml b/flux2/infrastructure/sources/jetstack.yaml
new file mode 100644
index 000000000..d926c1d4b
--- /dev/null
+++ b/flux2/infrastructure/sources/jetstack.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: jetstack
+spec:
+  interval: 1h
+  url: https://charts.jetstack.io
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/kustomization.yaml b/flux2/infrastructure/sources/kustomization.yaml
new file mode 100644
index 000000000..82c99049e
--- /dev/null
+++ b/flux2/infrastructure/sources/kustomization.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: flux-system
+resources:
+  - bitnami.yaml
+  - grafana.yaml
+  - helm-stable.yaml
+  - ingress-nginx.yaml
+  - jetstack.yaml
+  - local-path-provisioner.yaml
+  - nextcloud.yaml
+  - prometheus-community.yaml
+  - single-sign-on.yaml
+  - vmware-tanzu.yaml
+  - wordpress.yaml
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/local-path-provisioner.yaml b/flux2/infrastructure/sources/local-path-provisioner.yaml
new file mode 100644
index 000000000..791085cca
--- /dev/null
+++ b/flux2/infrastructure/sources/local-path-provisioner.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  name: local-path-provisioner
+spec:
+  # The interval at which to check the upstream for updates
+  interval: 1h
+  # The repository URL, can be a HTTP/S or SSH address
+  url: https://github.com/rancher/local-path-provisioner
+  # The Git reference to checkout and monitor for changes
+  # (defaults to master)
+  # For all available options, see:
+  # https://toolkit.fluxcd.io/components/source/api/#source.toolkit.fluxcd.io/v1beta1.GitRepositoryRef
+  ref:
+    tag: v0.0.14
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/nextcloud.yaml b/flux2/infrastructure/sources/nextcloud.yaml
new file mode 100644
index 000000000..eb3359133
--- /dev/null
+++ b/flux2/infrastructure/sources/nextcloud.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  name: nextcloud
+spec:
+  # The interval at which to check the upstream for updates
+  interval: 1h
+  # The repository URL, can be a HTTP/S or SSH address
+  url: https://open.greenhost.net/openappstack/nextcloud
+  # The Git reference to checkout and monitor for changes
+  # (defaults to master)
+  # For all available options, see:
+  # https://toolkit.fluxcd.io/components/source/api/#source.toolkit.fluxcd.io/v1beta1.GitRepositoryRef
+  ref:
+    tag: 0.2.7
diff --git a/flux2/infrastructure/sources/prometheus-community.yaml b/flux2/infrastructure/sources/prometheus-community.yaml
new file mode 100644
index 000000000..101e68de3
--- /dev/null
+++ b/flux2/infrastructure/sources/prometheus-community.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: prometheus-community
+spec:
+  interval: 5m
+  url: https://prometheus-community.github.io/helm-charts
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/single-sign-on.yaml b/flux2/infrastructure/sources/single-sign-on.yaml
new file mode 100644
index 000000000..c9f9ac398
--- /dev/null
+++ b/flux2/infrastructure/sources/single-sign-on.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  name: single-sign-on
+spec:
+  # The interval at which to check the upstream for updates
+  interval: 1h
+  # The repository URL, can be a HTTP/S or SSH address
+  url: https://open.greenhost.net/openappstack/single-sign-on
+  # The Git reference to checkout and monitor for changes
+  # (defaults to master)
+  # For all available options, see:
+  # https://toolkit.fluxcd.io/components/source/api/#source.toolkit.fluxcd.io/v1beta1.GitRepositoryRef
+  ref:
+    branch: master
diff --git a/flux2/infrastructure/sources/vmware-tanzu.yaml b/flux2/infrastructure/sources/vmware-tanzu.yaml
new file mode 100644
index 000000000..b3d1aeced
--- /dev/null
+++ b/flux2/infrastructure/sources/vmware-tanzu.yaml
@@ -0,0 +1,8 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  name: vmware-tanzu
+spec:
+  interval: 5m
+  url: https://vmware-tanzu.github.io/helm-charts
\ No newline at end of file
diff --git a/flux2/infrastructure/sources/wordpress.yaml b/flux2/infrastructure/sources/wordpress.yaml
new file mode 100644
index 000000000..3d103cbfa
--- /dev/null
+++ b/flux2/infrastructure/sources/wordpress.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  name: wordpress
+spec:
+  # The interval at which to check the upstream for updates
+  interval: 1h
+  # The repository URL, can be a HTTP/S or SSH address
+  url: https://open.greenhost.net/openappstack/wordpress-helm
+  # The Git reference to checkout and monitor for changes
+  # (defaults to master)
+  # For all available options, see:
+  # https://toolkit.fluxcd.io/components/source/api/#source.toolkit.fluxcd.io/v1beta1.GitRepositoryRef
+  ref:
+    # tag: 0.2.0
+    branch: 82-move-requirements-to-chart-use-uris-for-remote-repositories
diff --git a/install/ci-write-variable-files.sh b/install/ci-write-variable-files.sh
new file mode 100755
index 000000000..df4ff331a
--- /dev/null
+++ b/install/ci-write-variable-files.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+# Writes variables to files, then applies the kustomization that makes the
+# `oas-cluster-variables` secret that is needed by OAS installation
+
+ip_address=$1
+domain=$2
+
+echo "running with IP address: '$ip_address' and domain: '$domain'"
+
+cd "$( dirname "${BASH_SOURCE[0]}" )/installation-kustomization"
+
+
+echo "$domain" > domain
+# Needs to be a real email address (or at least not @example.com) for LE
+echo "info@openappstack.net" > admin_email
+
+# Outgoing mail: even though we disable it, we need values for them, because
+# Kustomize still wants to substitute them.
+echo "false" > outgoing_mail_enabled
+echo "admin@example.com" > outgoing_mail_from_address
+echo "admin" > outgoing_mail_from_prefix
+echo "example.com" > outgoing_mail_domain
+echo "example-password" > outgoing_mail_smtp_password
+
+# Example data for Greenhost SMTP login
+echo "smtp.greenhost.nl" > outgoing_mail_smtp_host
+echo "LOGIN" > outgoing_mail_smtp_authtype
+echo "587" > outgoing_mail_smtp_port
+echo "info@example.com" > outgoing_mail_smtp_user
+
+# ACME staging server address
+echo "https://acme-staging-v02.api.letsencrypt.org/directory" > acme_server
+# Used to let some programs accept insecure certificates
+echo "true" > acme_staging
+
+echo "$ip_address" > ip_address
+
+# (Example) backup data
+echo "oas.greenhost.net" > backup_s3_bucket
+echo "ci-prefix" > backup_s3_prefix
+echo "https://store.greenhost.net" > backup_s3_url
+echo "ceph" > backup_s3_region
+echo "example-access-key-id" > backup_s3_aws_access_key_id
+echo "example-secret-access-key" > backup_s3_aws_secret_access_key
+
+kubectl apply -k .
diff --git a/install/install-nextcloud.sh b/install/install-nextcloud.sh
new file mode 100755
index 000000000..c515e9839
--- /dev/null
+++ b/install/install-nextcloud.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# First, add some overrides for values that are only useful in CI
+kubectl apply -n oas-apps -f $( dirname "${BASH_SOURCE[0]}" )/nextcloud-values-override.yaml
+
+# This kustomization's only purpose is to add the kustomization that is in the
+# flxu2/cluster/optional/nextcloud folder. After this kustomization is applied
+# an `add-nextcloud` kustomization will be present on the cluster, as well as a
+# `nextcloud` kustomization that adds the actual app
+flux create kustomization add-nextcloud \
+  --source=GitRepository/openappstack \
+  --path="./flux2/cluster/optional/nextcloud" \
+  --prune=true \
+  --interval=10m
diff --git a/install/install-openappstack.sh b/install/install-openappstack.sh
new file mode 100755
index 000000000..5e0276a38
--- /dev/null
+++ b/install/install-openappstack.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+flux install \
+  --network-policy=false \
+  --watch-all-namespaces=true \
+  --namespace=flux-system
+
+flux create source git openappstack \
+  --url=https://open.greenhost.net/openappstack/openappstack \
+  --branch=try-flux-2 \
+  --interval=1m
+
+flux create kustomization openappstack \
+  --source=GitRepository/openappstack \
+  --path="./flux2/cluster/base" \
+  --prune=true \
+  --interval=10m
diff --git a/install/install-rocketchat.sh b/install/install-rocketchat.sh
new file mode 100755
index 000000000..576e21582
--- /dev/null
+++ b/install/install-rocketchat.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# This kustomization's only purpose is to add the kustomization that is in the
+# flxu2/cluster/optional/rocketchat folder. After this kustomization is applied
+# an `add-rocketchat` kustomization will be present on the cluster, as well as a
+# `rocketchat` kustomization that adds the actual app
+flux create kustomization add-rocketchat \
+  --source=GitRepository/openappstack \
+  --path="./flux2/cluster/optional/rocketchat" \
+  --prune=true \
+  --interval=10m
diff --git a/install/install-velero.sh b/install/install-velero.sh
new file mode 100755
index 000000000..7b849b978
--- /dev/null
+++ b/install/install-velero.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# This kustomization's only purpose is to add the kustomization that is in the
+# flxu2/cluster/optional/velero folder. After this kustomization is applied
+# an `add-velero` kustomization will be present on the cluster, as well as a
+# `velero` kustomization that adds the actual app
+flux create kustomization add-velero \
+  --source=GitRepository/openappstack \
+  --path="./flux2/cluster/optional/velero" \
+  --prune=true \
+  --interval=10m
diff --git a/install/install-wordpress.sh b/install/install-wordpress.sh
new file mode 100755
index 000000000..40d93607b
--- /dev/null
+++ b/install/install-wordpress.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# This kustomization's only purpose is to add the kustomization that is in the
+# flxu2/cluster/optional/wordpress folder. After this kustomization is applied
+# an `add-wordpress` kustomization will be present on the cluster, as well as a
+# `wordpress` kustomization that adds the actual app
+flux create kustomization add-wordpress \
+  --source=GitRepository/openappstack \
+  --path="./flux2/cluster/optional/wordpress" \
+  --prune=true \
+  --interval=10m
diff --git a/install/installation-kustomization/kustomization.yaml b/install/installation-kustomization/kustomization.yaml
new file mode 100644
index 000000000..a7c090839
--- /dev/null
+++ b/install/installation-kustomization/kustomization.yaml
@@ -0,0 +1,29 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+namespace: flux-system
+secretGenerator:
+  - name: oas-cluster-variables
+    files:
+      - domain
+      - admin_email
+      - outgoing_mail_enabled
+      - outgoing_mail_domain
+      - outgoing_mail_from_address
+      - outgoing_mail_from_prefix
+      - outgoing_mail_smtp_host
+      - outgoing_mail_smtp_authtype
+      - outgoing_mail_smtp_password
+      - outgoing_mail_smtp_port
+      - outgoing_mail_smtp_user
+      - acme_staging
+      - acme_server
+      - ip_address
+      - backup_s3_bucket
+      - backup_s3_prefix
+      - backup_s3_url
+      - backup_s3_region
+      - backup_s3_aws_access_key_id
+      - backup_s3_aws_secret_access_key
+generatorOptions:
+  disableNameSuffixHash: true
diff --git a/install/nextcloud-values-override.yaml b/install/nextcloud-values-override.yaml
new file mode 100644
index 000000000..238c6409e
--- /dev/null
+++ b/install/nextcloud-values-override.yaml
@@ -0,0 +1,10 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: nextcloud-values-override
+data:
+  values.yaml: |
+    onlyoffice:
+      unauthorizedStorage: true
+      httpsHstsEnabled: false
diff --git a/openappstack/__main__.py b/openappstack/__main__.py
index 2af9db534..e57329467 100755
--- a/openappstack/__main__.py
+++ b/openappstack/__main__.py
@@ -329,8 +329,6 @@ def create(clus, args):  # pylint: disable=too-many-branches
     # Set acme_staging to False so we use Let's Encrypt's live environment
     if args.acme_staging:
         clus.acme_staging = True
-    if args.local_flux:
-        clus.local_flux = True
     if args.create_droplet:
         clus.create_droplet(ssh_key_id=args.ssh_key_id, hostname=args.create_hostname)
         if args.verbose:
diff --git a/openappstack/cluster.py b/openappstack/cluster.py
index 02bf4b22d..4106a1e13 100644
--- a/openappstack/cluster.py
+++ b/openappstack/cluster.py
@@ -1,11 +1,13 @@
 """Contains code for managing the files related to an OpenAppStack cluster."""
 
+import base64
 import configparser
 import logging
 import os
 import sys
 import yaml
 import greenhost_cloud
+from kubernetes import client, config
 from openappstack import ansible
 
 CLUSTER_PATH = os.path.join(os.getcwd(), 'clusters')
@@ -31,7 +33,7 @@ DEFAULT_REGION = 'ams1'
 """Default disk size"""
 DEFAULT_DISK_SIZE_GB = 30
 """Default amount of memory"""
-DEFAULT_MEMORY_SIZE_MB = 8192
+DEFAULT_MEMORY_SIZE_MB = 12288
 """Default "image" (operating system): 19  =  Debian buster-x64 """
 DEFAULT_IMAGE = 19
 
@@ -54,9 +56,6 @@ class Cluster:
         self.domain = None
         # By default, use Let's Encrypt's live environment
         self.acme_staging = False
-        # By default, let auto-update listen to the main OpenAppStack flux repo
-        # for updates.
-        self.local_flux = False
         # Set this to False if the data needs to be (re)loaded from file
         self.data_loaded = False
         # Load data from inventory.yml and settings.yml
@@ -169,7 +168,6 @@ class Cluster:
         settings['ip_address'] = self.ip_address
         settings['domain'] = self.domain
         settings['admin_email'] = 'admin@{0}'.format(self.domain)
-        settings['flux']['local_flux'] = self.local_flux
         settings['cluster_dir'] = self.cluster_dir
 
         # Configure apps to handle invalid certs i.e. from
@@ -201,7 +199,6 @@ CLUSTER_DIR={cluster_dir}
 IP_ADDRESS={ip_address}
 HOSTNAME={hostname}
 FQDN={domain}
-LOCAL_FLUX={local_flux}
 KUBECONFIG={secret_dir}/kube_config_cluster.yml
 """
 
@@ -212,7 +209,6 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml
                 ip_address=self.ip_address,
                 hostname=self.hostname,
                 domain=self.domain,
-                local_flux=self.local_flux,
                 secret_dir=self.secret_dir
             ))
             log.info("Created %s", self.dotenv_file)
@@ -243,11 +239,6 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml
         return os.path.join(self.cluster_dir, '.cluster.env')
 
 
-    @property
-    def behave_file(self):
-        """Path to 'behave.ini' which is used for acceptance tests"""
-        return os.path.join(self.cluster_dir, 'behave.ini')
-
     @property
     def secret_dir(self):
         """Path where all the passwords for cluster admins are saved"""
@@ -266,22 +257,26 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml
                       'file! Remove the file if you want to run behave. '
                       'Program will exit now', config_path)
             sys.exit(2)
-        secret_directory = self.secret_dir
-        with open(os.path.join(
-                secret_directory, 'nextcloud_admin_password'), 'r') as stream:
-            nextcloud_admin_password = yaml.safe_load(stream)
 
-        with open(os.path.join(
-                secret_directory, 'rocketchat_admin_password'), 'r') as stream:
-            rocketchat_admin_password = yaml.safe_load(stream)
+        grafana_admin_password = self.get_password_from_kubernetes(
+            'kube-prometheus-stack-grafana',
+            'admin-password',
+            'oas')
 
-        with open(os.path.join(
-                secret_directory, 'wordpress_admin_password'), 'r') as stream:
-            wordpress_admin_password = yaml.safe_load(stream)
+        rocketchat_admin_password = self.get_password_from_kubernetes(
+            'oas-rocketchat-variables',
+            'rocketchat_admin_password',
+            'flux-system')
 
-        with open(os.path.join(
-                secret_directory, 'grafana_admin_password'), 'r') as stream:
-            grafana_admin_password = yaml.safe_load(stream)
+        nextcloud_admin_password = self.get_password_from_kubernetes(
+            'nc-nextcloud',
+            'nextcloud-password',
+            'oas-apps')
+
+        wordpress_admin_password = self.get_password_from_kubernetes(
+            'oas-wordpress-variables',
+            'wordpress_admin_password',
+            'flux-system')
 
         behave_config = configparser.ConfigParser()
         behave_config['behave'] = {}
@@ -320,6 +315,34 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml
         with open(config_path, 'w') as config_file:
             behave_config.write(config_file)
 
+    def get_password_from_kubernetes(self, secret, key, namespace):
+        """
+        Reads a password from the Kubernetes cluster. Always returns a string,
+        but returns "password not found" if no password was found.
+
+        :param string secret: The name of the secret in the cluster
+        :param string key: The key inside the secret that contains the base64
+            encoded password
+        :param string namespace: The namespace the secret is in
+        """
+        kubeconfig = os.path.join(self.secret_dir, 'kube_config_cluster.yml')
+        config.load_kube_config(config_file=kubeconfig)
+        api = client.CoreV1Api()
+        try:
+            secret_data = api.read_namespaced_secret(secret, namespace)
+        except client.exceptions.ApiException:
+            print(f"Secret {secret} not found in namespace '{namespace}'")
+            return "password not found"
+        try:
+            password = secret_data.data[key]
+        except KeyError:
+            print(f"Could not get password from secret '{secret}' in namespace"
+                   " '{namespace}' with key '{key}'")
+            return "password not found"
+
+        return base64.b64decode(password).decode('utf-8')
+
+
     def print_info(self, args):
         """Writes information about the cluster. Useful for debugging.
 
diff --git a/test/behave/features/environment.py b/test/behave/features/environment.py
index 8f696569f..2cdc9f865 100644
--- a/test/behave/features/environment.py
+++ b/test/behave/features/environment.py
@@ -5,7 +5,7 @@ def before_tag(context, tag):
     """Define steps run before each tag."""
 
     userdata = context.config.userdata
-    if tag == 'prometheus-stack':
+    if tag == 'kube-prometheus-stack':
         context.grafana = get_values(userdata, 'grafana')
 
     if tag == 'nextcloud':
diff --git a/test/behave/features/prometheus-stack.feature b/test/behave/features/kube-prometheus-stack.feature
similarity index 87%
rename from test/behave/features/prometheus-stack.feature
rename to test/behave/features/kube-prometheus-stack.feature
index 0be22f9ef..52cca2246 100644
--- a/test/behave/features/prometheus-stack.feature
+++ b/test/behave/features/kube-prometheus-stack.feature
@@ -1,4 +1,4 @@
-@prometheus-stack
+@kube-prometheus-stack
 Feature: Test grafana admin login
   As an OAS admin
   I want to be able to login to grafana as the user admin
@@ -20,7 +20,7 @@ Scenario: Login to grafana
   Then I wait on element "sidemenu.sidemenu" for 60000ms to be visible
   And I expect that the path is "/"
 
-Scenario: As an admin I want to look at the helm-operator logs
-  When I open the grafana explore helm-operator URL
+Scenario: As an admin I want to look at the helm-controller logs
+  When I open the grafana explore helm-controller URL
   Then I wait on element ".graph-panel" for 25000ms to be visible
   And I expect that element ".datapoints-warning" does not exist
diff --git a/test/behave/features/steps/steps.py b/test/behave/features/steps/steps.py
index b869f5882..b4d93bb6c 100644
--- a/test/behave/features/steps/steps.py
+++ b/test/behave/features/steps/steps.py
@@ -15,13 +15,13 @@ def step_impl(context):
     """Open wordpress URL."""
     context.behave_driver.get(context.wordpress['url'])
 
-@when(u'I open the grafana explore helm-operator URL')
-@given(u'I open the grafana explore helm-operator URL')
+@when(u'I open the grafana explore helm-controller URL')
+@given(u'I open the grafana explore helm-controller URL')
 def step_impl(context):
     """Open wordpress URL."""
-    helm_operator_url = str(context.grafana["url"]) + '/explore?orgId=1&left=["now-1h","now","Loki",{"expr":"{app=\\\"helm-operator\\\"}"}]'
-    print(helm_operator_url)
-    context.behave_driver.get(helm_operator_url)
+    helm_controller_url = str(context.grafana["url"]) + '/explore?orgId=1&left=["now-1h","now","Loki",{"expr":"{app=\\\"helm-controller\\\"}"}]'
+    print(helm_controller_url)
+    context.behave_driver.get(helm_controller_url)
 
 @when(u'I wait on element "{element}" to be clickable')
 @given(u'I wait on element "{element}" to be clickable')
diff --git a/test/pytest.ini b/test/pytest.ini
index 2c70f67bb..b9b8c12e4 100644
--- a/test/pytest.ini
+++ b/test/pytest.ini
@@ -6,6 +6,7 @@ markers =
     testinfra: Run testinfra tests (test OS/package versions etc)
     prometheus: Test prometheus
     helmreleases: Test deployed helmreleases installed by flux
+    kustomizations: Test that flux kustomizations are ready
     apps_running: Test if all the pods for the helmreleases are running
     dns: Check if cluster domain resolves at all nameservers
 
diff --git a/test/pytest/test_app_deployments.py b/test/pytest/test_app_deployments.py
index 0f94a38e4..81a085e95 100644
--- a/test/pytest/test_app_deployments.py
+++ b/test/pytest/test_app_deployments.py
@@ -11,12 +11,19 @@ from kubernetes import client, config
 from kubernetes.client.rest import ApiException
 import pytest
 
+EXPECTED_KUSTOMIZATIONS_BASE = [
+    'openappstack',
+    'monitoring',
+    'infrastructure',
+    'core',
+]
+
 EXPECTED_RELEASES = {
     'cert-manager': ['cert-manager'],
     'kube-system': ['local-path-provisioner'],
     'oas': [
         'ingress',
-        'prometheus-stack',
+        'kube-prometheus-stack',
         'loki',
         'promtail',
         'eventrouter',
@@ -45,7 +52,7 @@ EXPECTED_APP_LABELS = {
     'nextcloud': {
         'namespace': 'oas-apps',
         'label_selector': 'app.kubernetes.io/instance=nc'},
-    'prometheus-stack': {
+    'kube-prometheus-stack': {
         'namespace': 'oas',
         'label_selector': 'app in (grafana,prometheus)'},
     'rocketchat': {
@@ -60,18 +67,43 @@ EXPECTED_APP_LABELS = {
 }
 
 
+def get_kustomization_status(name, api, namespace="flux-system"):
+    """Returns status contition for kustomization `name` in `namespace`"""
+    print('Testing %s in namespace %s ...' % (name, namespace), end='')
+    try:
+        kustomization = api.get_namespaced_custom_object(
+            group="kustomize.toolkit.fluxcd.io",
+            version="v1beta1",
+            plural="kustomizations",
+            name=name,
+            namespace=namespace
+        )
+        ks_status = kustomization['status']['conditions'][0]['status']
+    except ApiException as ex:
+        if ex.status == 404:
+            ks_status = 'Not found'
+        else:
+            raise
+        print(f"**** NOT DEPLOYED, status: {ks_status} *****")
+    except KeyError:
+        ks_status = 'Key error'
+        print("Kustomization key error: ")
+        print(kustomization)
+    return ks_status
+
 def get_release_status(name, namespace, api):
     """Returns release status for release `name` in `namespace`"""
     print('Testing %s in namespace %s ...' % (name, namespace), end='')
     try:
         release = api.get_namespaced_custom_object(
-            group="helm.fluxcd.io",
-            version="v1",
+            group="helm.toolkit.fluxcd.io",
+            version="v2beta1",
             plural="helmreleases",
             name=name,
             namespace=namespace
         )
-        release_status = release['status']['releaseStatus']
+        print(release['status'])
+        release_status = release['status']['conditions'][0]['status']
         print(release_status)
     except ApiException as ex:
         if ex.status == 404:
@@ -80,9 +112,9 @@ def get_release_status(name, namespace, api):
             raise
         print("**** NOT DEPLOYED, status: %s *****" % release_status)
     except KeyError:
-        # Take a look at the 'phase' if 'releaseStatus' does not exist
-        release_status = release['status']['phase']
-        print(release_status)
+        release_status = 'Key error'
+        print("HelmRelease key error: ")
+        print(release)
     return release_status
 
 
@@ -113,6 +145,29 @@ def run_around_tests():
     config.load_kube_config(config_file=kubeconfig)
     yield
 
+@pytest.mark.app
+@pytest.mark.kustomizations
+def test_kustomizations(app):
+    """
+    Checks if all desired Kustomizations installed by weave flux are in
+    'deployed' state.
+    """
+    if app == 'base':
+        kustomizations = EXPECTED_KUSTOMIZATIONS_BASE
+    else:
+        kustomizations = [app]
+
+    custom_objects = client.CustomObjectsApi()
+
+    failed = 0
+    print('\n')
+    for kustomization in kustomizations:
+        ks_status = get_kustomization_status(kustomization, custom_objects)
+
+        if ks_status != 'True':
+            failed += 1
+
+    assert failed == 0, f"Error: {failed} kustomizations not 'ready'!"
 
 @pytest.mark.app
 @pytest.mark.helmreleases
@@ -135,7 +190,7 @@ def test_helmreleases(app):
             if app in (app_name, 'all'):
                 app_status = get_release_status(
                     app_name, namespace, custom_objects)
-                if app_status != 'deployed':
+                if app_status != 'True':
                     failed += 1
     assert failed == 0, "Error: {} apps not 'deployed'!".format(failed)
 
diff --git a/test/pytest/test_certs.py b/test/pytest/test_certs.py
index bd4d48c14..2b40c7678 100755
--- a/test/pytest/test_certs.py
+++ b/test/pytest/test_certs.py
@@ -105,7 +105,7 @@ def test_cert_validation(host, app): # pylint: disable=too-many-statements
 
     if app == 'all':
         apps = list(app_subdomains.keys())
-    elif app == 'prometheus-stack':
+    elif app == 'kube-prometheus-stack':
         apps = ['grafana', 'prometheus']
     else:
         assert app in app_subdomains, "Error: Unknown app: {}".format(app)
diff --git a/test/pytest/test_prometheus.py b/test/pytest/test_prometheus.py
index a89ce5b9b..14e64a61c 100755
--- a/test/pytest/test_prometheus.py
+++ b/test/pytest/test_prometheus.py
@@ -64,7 +64,7 @@ def test_prometheus_alerts(host):
 
     print("Starting prometheus test...")
 
-    url = 'http://prometheus-stack-kube-prom-prometheus.oas.svc.cluster.local:9090/api/v1/alerts'
+    url = 'http://kube-prometheus-stack-kube-prom-prometheus.oas.svc.cluster.local:9090/api/v1/alerts'
 
     alert_json = json.loads(host.check_output('curl ' + url))
     status = alert_json["status"]
-- 
GitLab