diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index adca075..2cafe78 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,7 +47,7 @@ test-schema: stage: build_and_test script: - lein uberjar - - java -jar target/uberjar/c4k-nextcloud-standalone.jar valid-config.edn valid-auth.edn | kubeconform --kubernetes-version 1.19.0 --strict --skip Certificate - + - java -jar target/uberjar/c4k-nextcloud-standalone.jar valid-config.edn valid-auth.edn | kubeconform --kubernetes-version 1.19.0 --strict --skip "Certificate,CronJob" - artifacts: paths: - target/uberjar diff --git a/README.md b/README.md index 9af9e4c..eebf0d7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,42 @@ -# meissa-cloud +# convention 4 kubernetes: c4k-nextcloud +[![Clojars Project](https://img.shields.io/clojars/v/org.domaindrivenarchitecture/c4k-nextcloud.svg)](https://clojars.org/org.domaindrivenarchitecture/c4k-nextcloud) [![pipeline status](https://gitlab.com/domaindrivenarchitecture/c4k-nextcloud/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/c4k-nextcloud/-/commits/master) -# backup manuell triggern +[DeltaChat chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [team@social.meissa-gmbh.de team@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@team) | [Website & Blog](https://domaindrivenarchitecture.org) -# restore manuell triggern \ No newline at end of file +## Purpose + +c4k-nextcloud provides a k8s deployment for nextcloud containing: +* adjusted nextcloud docker image +* nextcloud +* ingress having a letsencrypt managed certificate +* postgres database + +The package aims to a low load sceanrio. + +## Status + +Stable - we use this setup on production. + +## Try out + +Click on the image to try out live in your browser: + +[![Try it out](doc/tryItOut.png "Try out yourself")](https://domaindrivenarchitecture.org/pages/dda-provision/c4k-nextcloud/) + +Your input will stay in your browser. No server interaction is required. + +You will also be able to try out on cli: +``` +target/graalvm/c4k-nextcloud src/test/resources/valid-config.edn src/test/resources/valid-auth.edn | kubeval - +target/graalvm/c4k-nextcloud src/test/resources/valid-config.edn src/test/resources/valid-auth.edn | kubectl apply -f - +``` + +## Documentation +* [Example Setup on Hetzner](doc/SetupOnHetzner.md) +* Backup and Restore + +## License + +Copyright © 2021 meissa GmbH +Licensed under the [Apache License, Version 2.0](LICENSE) (the "License") +Pls. find licenses of our subcomponents [here](doc/SUBCOMPONENT_LICENSE) \ No newline at end of file diff --git a/doc/SetupOnHetzner.md b/doc/SetupOnHetzner.md index 3117fa1..9872457 100644 --- a/doc/SetupOnHetzner.md +++ b/doc/SetupOnHetzner.md @@ -17,7 +17,7 @@ resource "aws_s3_bucket" "backup" { } } -resource "hcloud_server" "jira_09_2021" { +resource "hcloud_server" "cloud_09_2021" { name = "the name" image = "ubuntu-20.04" server_type = "cx31" @@ -31,14 +31,14 @@ resource "hcloud_server" "jira_09_2021" { resource "aws_route53_record" "v4_neu" { zone_id = the_dns_zone - name = "jira-neu" + name = "cloud-neu" type = "A" ttl = "300" - records = [hcloud_server.jira_09_2021.ipv4_address] + records = [hcloud_server.cloud_09_2021.ipv4_address] } output "ipv4" { - value = hcloud_server.jira_09_2021.ipv4_address + value = hcloud_server.cloud_09_2021.ipv4_address } ``` @@ -52,23 +52,23 @@ For k8s installation we use our [dda-k8s-crate](https://github.com/DomainDrivenA {:user :k8s :k8s {:external-ip "ip-from-above"} :cert-manager :letsencrypt-prod-issuer - :persistent-dirs ["jira", "postgres"] + :persistent-dirs ["cloud", "postgres"] } ``` -## kubectl apply c4k-jira +## kubectl apply c4k-nextcloud -The last step for applying the jira deployment is +The last step for applying the nextcloud deployment is ``` -c4k-jira config.edn auth.edn | kubectl apply -f - +c4k-nextcloud config.edn auth.edn | kubectl apply -f - ``` with the following config.edn: ``` {:fqdn "the-fqdn-from aws_route53_record.v4_neu" - :jira-data-volume-path "/var/jira" ;; Volume was configured at dda-k8s-crate, results in a PersistentVolume definition. + :nextcloud-data-volume-path "/var/cloud" ;; Volume was configured at dda-k8s-crate, results in a PersistentVolume definition. :postgres-data-volume-path "/var/postgres" ;; Volume was configured at dda-k8s-crate, results in a PersistentVolume definition. :restic-repository "s3:s3.amazonaws.com/your-bucket/your-folder"} ``` diff --git a/doc/tryItOut.png b/doc/tryItOut.png new file mode 100644 index 0000000..19ff34b Binary files /dev/null and b/doc/tryItOut.png differ diff --git a/infrastructure/docker-nextcloud/image/Dockerfile b/infrastructure/docker-nextcloud/image/Dockerfile index 8b3d9e9..61ac8ab 100644 --- a/infrastructure/docker-nextcloud/image/Dockerfile +++ b/infrastructure/docker-nextcloud/image/Dockerfile @@ -1,4 +1,4 @@ -FROM nextcloud:19 +FROM nextcloud:22 # Prepare Entrypoint Script ADD resources /tmp diff --git a/infrastructure/docker-nextcloud/image/resources/install.sh b/infrastructure/docker-nextcloud/image/resources/install.sh index b92fc18..2111956 100755 --- a/infrastructure/docker-nextcloud/image/resources/install.sh +++ b/infrastructure/docker-nextcloud/image/resources/install.sh @@ -1,6 +1,8 @@ #!/bin/bash set -Eeo pipefail +apt update && apt -qqy install postgresql-client > /dev/null + mkdir /var/data install -m 0700 /tmp/install-debug.sh /usr/local/bin/ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..9cda1d2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,18 @@ + + + + + + c4k-nextcloud + + + + + + +
+ + + + \ No newline at end of file diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 330e8e8..6bea133 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -4,7 +4,8 @@ "src/test/cljc" "src/test/cljs" "src/test/resources"] - :dependencies [[org.domaindrivenarchitecture/c4k-common-cljs "0.3.2-SNAPSHOT"]] + :dependencies [[org.domaindrivenarchitecture/c4k-common-cljs "0.4.3"] + [hickory "0.7.1"]] :builds {:frontend {:target :browser :modules {:main {:init-fn dda.c4k-nextcloud.browser/init}} :release {} diff --git a/src/main/cljs/dda/c4k_nextcloud/browser.cljs b/src/main/cljs/dda/c4k_nextcloud/browser.cljs index 94012b6..eee519c 100644 --- a/src/main/cljs/dda/c4k_nextcloud/browser.cljs +++ b/src/main/cljs/dda/c4k_nextcloud/browser.cljs @@ -6,6 +6,34 @@ [dda.c4k-common.browser :as br] [dda.c4k-common.postgres :as pgc])) +(defn generate-content + [] + (into [] (concat [(assoc (br/generate-needs-validation) :content + (into [] (concat (br/generate-input-field "fqdn" "Your fqdn:" "nextcloud-neu.prod.meissa-gmbh.de") + (br/generate-input-field "nextcloud-data-volume-path" "(Optional) Your nextcloud-data-volume-path:" "/var/nextcloud") + (br/generate-input-field "postgres-data-volume-path" "(Optional) Your postgres-data-volume-path:" "/var/postgres") + (br/generate-input-field "restic-repository" "(Optional) Your restic-repository:" "restic-repository") + (br/generate-input-field "issuer" "(Optional) Your issuer prod/staging:" "") + [(br/generate-br)] + (br/generate-text-area "auth" "Your auth.edn:" "{:postgres-db-user \"nextcloud\" + :postgres-db-password \"nextcloud-db-password\" + :nextcloud-admin-password \"nextcloud-admin-password\" + :nextcloud-admin-user \"nextcloud-admin-user\" + :aws-access-key-id \"aws-id\" + :aws-secret-access-key \"aws-secret\" + :restic-password \"restic-password\"}" + "5") + [(br/generate-br)] + (br/generate-button "generate-button" "Generate c4k yaml"))))] + (br/generate-output "c4k-nextcloud-output" "Your c4k deployment.yaml:" "25")))) + +(defn generate-content-div + [] + {:type :element + :tag :div + :content + (generate-content)}) + (defn config-from-document [] (let [nextcloud-data-volume-path (br/get-content-from-element "nextcloud-data-volume-path" :optional true) postgres-data-volume-path (br/get-content-from-element "postgres-data-volume-path" :optional true) @@ -32,7 +60,12 @@ (br/validate! "auth" core/auth? :deserializer edn/read-string) (br/set-validated!)) +(defn add-validate-listener [name] + (-> (br/get-element-by-id name) + (.addEventListener "blur" #(do (validate-all!))))) + (defn init [] + (br/append-hickory (generate-content-div)) (-> js/document (.getElementById "generate-button") (.addEventListener "click" @@ -41,22 +74,9 @@ (config-from-document) (br/get-content-from-element "auth" :deserializer edn/read-string)) (br/set-output!))))) - (-> (br/get-element-by-id "fqdn") - (.addEventListener "blur" - #(do (validate-all!)))) - (-> (br/get-element-by-id "nextcloud-data-volume-path") - (.addEventListener "blur" - #(do (validate-all!)))) - (-> (br/get-element-by-id "postgres-data-volume-path") - (.addEventListener "blur" - #(do (validate-all!)))) - (-> (br/get-element-by-id "restic-repository") - (.addEventListener "blur" - #(do (validate-all!)))) - (-> (br/get-element-by-id "issuer") - (.addEventListener "blur" - #(do (validate-all!)))) - (-> (br/get-element-by-id "auth") - (.addEventListener "blur" - #(do (validate-all!)))) - ) \ No newline at end of file + (add-validate-listener "fqdn") + (add-validate-listener "nextcloud-data-volume-path") + (add-validate-listener "postgres-data-volume-path") + (add-validate-listener "restic-repository") + (add-validate-listener "issuer") + (add-validate-listener "auth")) diff --git a/src/main/resources/backup/backup-restore.yaml b/src/main/resources/backup/backup-restore.yaml index c13e166..2c6aafb 100644 --- a/src/main/resources/backup/backup-restore.yaml +++ b/src/main/resources/backup/backup-restore.yaml @@ -12,12 +12,21 @@ spec: imagePullPolicy: IfNotPresent command: ["/entrypoint-start-and-wait.sh"] env: - - name: POSTGRES_USER_FILE - value: /var/run/secrets/cloud-secrets/postgres-user - - name: POSTGRES_DB_FILE - value: /var/run/secrets/cloud-secrets/postgres-db - - name: POSTGRES_PASSWORD_FILE - value: /var/run/secrets/cloud-secrets/postgres-password + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: postgres-secret + key: postgres-user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: postgres-password + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: postgres-config + key: postgres-db - name: POSTGRES_HOST value: "postgresql-service:5432" - name: POSTGRES_SERVICE diff --git a/src/main/resources/backup/cron.yaml b/src/main/resources/backup/cron.yaml index 2026cf5..3c54570 100644 --- a/src/main/resources/backup/cron.yaml +++ b/src/main/resources/backup/cron.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: cloud-backup diff --git a/src/main/resources/nextcloud/certificate.yaml b/src/main/resources/nextcloud/certificate.yaml index 054965b..6912f95 100644 --- a/src/main/resources/nextcloud/certificate.yaml +++ b/src/main/resources/nextcloud/certificate.yaml @@ -1,4 +1,4 @@ -apiVersion: cert-manager.io/v1alpha2 +apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: cloud-cert diff --git a/src/main/resources/nextcloud/deployment.yaml b/src/main/resources/nextcloud/deployment.yaml index f87b70a..b825eef 100644 --- a/src/main/resources/nextcloud/deployment.yaml +++ b/src/main/resources/nextcloud/deployment.yaml @@ -22,6 +22,14 @@ spec: imagePullPolicy: IfNotPresent ports: - containerPort: 80 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - PGPASSWORD=$POSTGRES_PASSWORD psql -h postgresql-service -U $POSTGRES_USER $POSTGRES_DB + initialDelaySeconds: 1 + periodSeconds: 5 env: - name: NEXTCLOUD_ADMIN_USER valueFrom: diff --git a/src/test/cljc/dda/c4k_nextcloud/backup_test.cljc b/src/test/cljc/dda/c4k_nextcloud/backup_test.cljc index 4aee6b2..227c4d6 100644 --- a/src/test/cljc/dda/c4k_nextcloud/backup_test.cljc +++ b/src/test/cljc/dda/c4k_nextcloud/backup_test.cljc @@ -10,7 +10,7 @@ :kind "Secret" :metadata {:name "backup-secret"} :type "Opaque" - :stringData + :data {:aws-access-key-id "YXdzLWlk", :aws-secret-access-key "YXdzLXNlY3JldA==", :restic-password "cmVzdGljLXB3"}} (cut/generate-secret {:aws-access-key-id "aws-id" :aws-secret-access-key "aws-secret" :restic-password "restic-pw"})))) @@ -25,7 +25,7 @@ (cut/generate-config {:restic-repository "s3:restic-repository"})))) (deftest should-generate-cron - (is (= {:apiVersion "batch/v1beta1" + (is (= {:apiVersion "batch/v1" :kind "CronJob" :metadata {:name "cloud-backup", :labels {:app.kubernetes.part-of "cloud"}} :spec @@ -38,7 +38,7 @@ {:spec {:containers [{:name "backup-app" - :image "domaindrivenarchitecture/meissa-cloud-backup" + :image "domaindrivenarchitecture/c4k-cloud-backup" :imagePullPolicy "IfNotPresent" :command ["/entrypoint.sh"] :env diff --git a/src/test/cljc/dda/c4k_nextcloud/nextcloud_test.cljc b/src/test/cljc/dda/c4k_nextcloud/nextcloud_test.cljc index 55ebae8..5494fa0 100644 --- a/src/test/cljc/dda/c4k_nextcloud/nextcloud_test.cljc +++ b/src/test/cljc/dda/c4k_nextcloud/nextcloud_test.cljc @@ -9,14 +9,14 @@ :kind "Secret" :metadata {:name "cloud-secret"} :type "Opaque" - :stringData + :data {:nextcloud-admin-user "Y2xvdWRhZG1pbg==" :nextcloud-admin-password "Y2xvdWRwYXNzd29yZA=="}} (cut/generate-secret {:nextcloud-admin-user "cloudadmin" :nextcloud-admin-password "cloudpassword"})))) (deftest should-generate-certificate - (is (= {:apiVersion "cert-manager.io/v1alpha2" + (is (= {:apiVersion "cert-manager.io/v1" :kind "Certificate" :metadata {:name "cloud-cert", :namespace "default"} :spec @@ -58,71 +58,46 @@ (deftest should-generate-persistent-volume (is (= {:kind "PersistentVolume" :apiVersion "v1" - :metadata {:name "cloud-pv-volume", :labels {:type "local" :app "cloud"}} - :spec - {:storageClassName "manual" - :accessModes ["ReadWriteOnce"] - :capacity {:storage "200Gi"} - :hostPath {:path "xx"}}} + :metadata {:name "cloud-pv-volume" + :labels {:type "local", :app.kubernetes.io/application "cloud"}} + :spec {:storageClassName "manual" + :accessModes ["ReadWriteOnce"] + :capacity {:storage "200Gi"} + :hostPath {:path "xx"}}} (cut/generate-persistent-volume {:nextcloud-data-volume-path "xx"})))) (deftest should-generate-deployment (is (= {:apiVersion "apps/v1" :kind "Deployment" - :metadata {:name "cloud"} + :metadata {:name "cloud-deployment"} :spec - {:selector {:matchLabels {:app "cloud"}} + {:selector {:matchLabels #:app.kubernetes.io{:name "cloud-pod", :application "cloud"}} :strategy {:type "Recreate"} :template - {:metadata {:labels {:app "cloud"}} + {:metadata {:labels {:app.kubernetes.io/name "cloud-pod", :app.kubernetes.io/application "cloud", :redeploy "v3"}} :spec {:containers - [{:image "domaindrivenarchitecture/meissa-cloud-app" + [{:image "domaindrivenarchitecture/c4k-cloud" :name "cloud-app" :imagePullPolicy "IfNotPresent" :ports [{:containerPort 80}] + :livenessProbe + {:exec + {:command + ["/bin/sh" + "-c" + "PGPASSWORD=$POSTGRES_PASSWORD psql -h postgresql-service -U $POSTGRES_USER $POSTGRES_DB"]} + :initialDelaySeconds 1 + :periodSeconds 5} :env - [{:name "NEXTCLOUD_ADMIN_USER_FILE" - :value - "/var/run/secrets/cloud-secrets/nextcloud-admin-user"} - {:name "NEXTCLOUD_ADMIN_PASSWORD_FILE" - :value - "/var/run/secrets/cloud-secrets/nextcloud-admin-password"} + [{:name "NEXTCLOUD_ADMIN_USER", :valueFrom {:secretKeyRef {:name "cloud-secret", :key "nextcloud-admin-user"}}} + {:name "NEXTCLOUD_ADMIN_PASSWORD" + :valueFrom {:secretKeyRef {:name "cloud-secret", :key "nextcloud-admin-password"}}} {:name "NEXTCLOUD_TRUSTED_DOMAINS", :value "xx"} - {:name "POSTGRES_USER_FILE" - :value - "/var/run/secrets/postgres-secret/postgres-user"} - {:name "POSTGRES_PASSWORD_FILE" - :value - "/var/run/secrets/postgres-secret/postgres-password"} - {:name "POSTGRES_DB_FILE" - :value - "/var/run/configs/postgres-config/postgres-db"} - {:name "POSTGRES_HOST" - :value "postgresql-service:5432"}] - :volumeMounts - [{:name "cloud-data-volume" - :mountPath "/var/www/html"} - {:name "cloud-secret-volume" - :mountPath "/var/run/secrets/cloud-secrets" - :readOnly true} - {:name "postgres-secret-volume" - :mountPath "/var/run/secrets/postgres-secret" - :readOnly true} - {:name "postgres-config-volume" - :mountPath "/var/run/configs/postgres-config" - :readOnly true}]}] - :volumes - [{:name "cloud-data-volume" - :persistentVolumeClaim {:claimName "cloud-pvc"}} - {:name "cloud-secret-volume" - :secret {:secretName "cloud-secret"}} - {:name "postgres-secret-volume" - :secret {:secretName "postgres-secret"}} - {:name "postgres-config-volume" - :configMap - {:name "postgres-config" - :items [{:key "postgres-db", :path "postgres-db"}]}} - {:name "backup-secret-volume" - :secret {:secretName "backup-secret"}}]}}}} + {:name "POSTGRES_USER", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-user"}}} + {:name "POSTGRES_PASSWORD", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-password"}}} + {:name "POSTGRES_DB", :valueFrom {:configMapKeyRef {:name "postgres-config", :key "postgres-db"}}} + {:name "POSTGRES_HOST", :value "postgresql-service:5432"}] + :volumeMounts [{:name "cloud-data-volume", :mountPath "/var/www/html"}]}] + :volumes [{:name "cloud-data-volume", :persistentVolumeClaim {:claimName "cloud-pvc"}}]}}}} (cut/generate-deployment {:fqdn "xx"}))))