diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..e7847d8 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,24 @@ +name: Deploy + +on: + push: + branches: [ master, james/test ] + +permissions: read-all + +jobs: + deploy_to_dev3: + name: Deploy to dev 3 + uses: ./.github/workflows/helm_deploy.yaml + with: + environment: dev3 + secrets: inherit + #if: github.ref == 'refs/heads/main' + + deploy_to_prod1: + name: Deploy to prod 1 + uses: ./.github/workflows/helm_deploy.yaml + with: + environment: prod1 + secrets: inherit + if: github.ref == 'refs/heads/main' \ No newline at end of file diff --git a/.github/workflows/helm_deploy.yaml b/.github/workflows/helm_deploy.yaml new file mode 100644 index 0000000..6818f3e --- /dev/null +++ b/.github/workflows/helm_deploy.yaml @@ -0,0 +1,57 @@ +name: Deploy Helm Chart +permissions: read-all + +on: + workflow_call: + inputs: + environment: + required: true + type: string + +jobs: + helm_deploy: + name: Deploy helm chart + environment: ${{ inputs.environment }} + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install SSH key + uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2 + with: + key: ${{ secrets.SSH_PRIVATE_KEY }} + name: id_ed25519 # optional + known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }} + if_key_exists: fail # replace / ignore / fail; optional (defaults to fail) + + - name: Setup WireGuard + run: | + sudo apt install wireguard + echo "${{ secrets.WIREGUARD_PRIVATE_KEY }}" > privatekey + sudo ip link add dev wg1 type wireguard + sudo ip address add dev wg1 ${{ secrets.WIREGUARD_OVERLAY_NETWORK_IP }} peer ${{ secrets.SSH_TARGET_IP }} + sudo wg set wg1 listen-port 48123 private-key privatekey peer ${{ secrets.WIREGUARD_PEER_PUBLIC_KEY }} allowed-ips 0.0.0.0/0 endpoint ${{ secrets.WIREGUARD_ENDPOINT }} + sudo ip link set up dev wg1 + + - name: Install Helm Chart + run: | + scp ${{ secrets.SSH_USER }}@${{ secrets.SSH_TARGET_IP }}:~/.kube/config ./ + helm upgrade \ + wiki \ + bookstack-helm \ + --install \ + --kubeconfig ./config \ + --kube-apiserver https://${{ secrets.SSH_TARGET_IP }}:6443 \ + -n ${{ vars.APP_NAMESPACE }} \ + --create-namespace \ + -f ./bookstack-helm/values.yaml \ + --set bookstack.fqdn="${{ vars.FQDN }}" \ + --set bookstack.db.username="${{ secrets.DB_USERNAME }}" \ + --set bookstack.db.password="${{ secrets.DB_PASSWORD }}" \ + --set bookstack.db.root_password="${{ secrets.ROOT_PASSWORD }}" \ + --set bookstack.mail.username="${{ secrets.MAIL_USERNAME }}" \ + --set bookstack.mail.password="${{ secrets.MAIL_PASSWORD }}" \ + --set bookstack.aws.access_key_id="${{ secrets.ACCESS_KEY_ID }}" \ + --set bookstack.aws.secret_access_key="${{ secrets.SECRET_ACCESS_KEY }}" \ + --set bookstack.aws.backup_s3_url="${{ secrets.BACKUP_S3_URL }}" diff --git a/README.md b/README.md index 37bd45b..67878c9 100644 --- a/README.md +++ b/README.md @@ -1 +1,110 @@ -# Wiki Infra \ No newline at end of file +# Wiki Infra + +## Backups + +``` +docker exec 55fb8319dfd0 tar -chvf /config/wiki.tar /app/www/public/uploads/ /app/www/storage/uploads/ /app/www/public/img/ + +docker exec 00bf1e961d6b mysqldump -u bookstack --password=$THE_REAL_THING bookstackapp > wiki.sql + +tar --append -f wiki.tar wiki.sql + +gzip < wiki.tar > wiki.tgz + +# upload the tgz to s3... +``` + +## Restore + +1. Create `restore.yaml` + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: restorejob + namespace: wiki +spec: + template: + spec: + containers: + - name: restorejob + image: lscr.io/linuxserver/mariadb + command: + - /bin/bash + - /restore.sh + volumeMounts: + - name: backup-script + mountPath: /restore.sh + subPath: restore.sh + readOnly: true + - name: config-vol + mountPath: /config + - name: image-uploads-vol + mountPath: /app/www/public/img + env: + - name: RESTORE_S3_URL + value: "s3://.../backups/wiki.tgz" + - name: DB_HOST + valueFrom: + configMapKeyRef: + name: wikiconfig + key: DB_HOST + - name: DB_USER + valueFrom: + secretKeyRef: + name: wiki-secrets + key: db-username + - name: DB_DATABASE + valueFrom: + configMapKeyRef: + name: wikiconfig + key: DB_DATABASE + - name: DB_PASS + valueFrom: + secretKeyRef: + name: wiki-secrets + key: db-password + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: wiki-secrets + key: access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: wiki-secrets + key: secret-access-key + restartPolicy: OnFailure + volumes: + - name: backup-script + configMap: + name: backup-script + items: + - key: restore.sh + path: restore.sh + - name: config-vol + persistentVolumeClaim: + claimName: wikiconfig + - name: image-uploads-vol + persistentVolumeClaim: + claimName: wikiimages +``` + +2. Run the restore job + +``` +kubectl scale --replicas=0 deployment.apps/wiki-bookstack-helm-bookstack -n wiki +kubectl apply -f restore.yaml +# wait for it to complete +kubectl get all -n wiki +kubectl scale --replicas=1 deployment.apps/wiki-bookstack-helm-bookstack -n wiki +``` + +3. If the url is changing, you may need the following: + +``` +kubectl exec -n wiki -it pod/wiki-bookstack-helm-bookstack-695945475d-mrlhf bash +cd /app/www +php artisan bookstack:update-url https://wiki.mesh.nycmesh.net https://devwiki.mesh.nycmesh.net +``` diff --git a/bookstack-helm/templates/backup.yaml b/bookstack-helm/templates/backup.yaml new file mode 100644 index 0000000..0e6a641 --- /dev/null +++ b/bookstack-helm/templates/backup.yaml @@ -0,0 +1,67 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backupjob + namespace: {{ .Values.meshwiki_app_namespace }} +spec: + schedule: {{ .Values.bookstack.backup.cron_schedule | quote }} + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: backupjob + image: "{{ .Values.db_image.repository }}:{{ .Values.db_image.tag }}" + imagePullPolicy: {{ .Values.db_image.pullPolicy }} + command: + - /bin/bash + - /backup.sh + volumeMounts: + - name: backup-script + mountPath: /backup.sh + subPath: backup.sh + readOnly: true + env: + - name: DB_HOST + valueFrom: + configMapKeyRef: + name: wikiconfig + key: DB_HOST + - name: DB_DATABASE + valueFrom: + configMapKeyRef: + name: wikiconfig + key: DB_DATABASE + - name: DB_USER + valueFrom: + secretKeyRef: + name: wiki-secrets + key: db-username + - name: DB_PASS + valueFrom: + secretKeyRef: + name: wiki-secrets + key: db-password + - name: BACKUP_S3_URL + valueFrom: + secretKeyRef: + name: wiki-secrets + key: backup-s3-url + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: wiki-secrets + key: access-key-id + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: wiki-secrets + key: secret-access-key + volumes: + - name: backup-script + configMap: + name: backup-script + items: + - key: backup.sh + path: backup.sh diff --git a/bookstack-helm/templates/backupscript.yaml b/bookstack-helm/templates/backupscript.yaml new file mode 100644 index 0000000..be0044d --- /dev/null +++ b/bookstack-helm/templates/backupscript.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: backup-script + namespace: {{ .Values.meshwiki_app_namespace }} +data: + backup.sh: | + echo "Dumping db" + mysqldump -h "$DB_HOST" -u "$DB_USER" --password="$DB_PASS" "$DB_DATABASE" > wiki.sql + echo "Creating tarball" + backup_name="wiki_backup_$(date +%s).tar.gz" + tar -chzvf $backup_name wiki.sql /app/www/public/uploads/ /app/www/storage/uploads/ /app/www/public/img/ + echo "push to s3" + apk add aws-cli + aws s3 cp ./$backup_name "$BACKUP_S3_URL" + echo "done" + restore.sh: | + echo "Checking if restore is needed" + echo "Restore file is: $RESTORE_S3_URL" + if [ ! -z "$RESTORE_S3_URL" ]; then + echo "Restoring from $RESTORE_S3_URL" + apk add aws-cli + aws s3 cp "$RESTORE_S3_URL" /tmp/restore.tgz + echo "downloaded tarball" + tar -xzf /tmp/restore.tgz -C / + echo "Files restored" + mysql -h "$DB_HOST" -u "$DB_USER" --password="$DB_PASS" $DB_DATABASE < /wiki.sql + else + echo "Not restoring" + fi + echo "done" diff --git a/bookstack-helm/templates/bookstack.yaml b/bookstack-helm/templates/bookstack.yaml index 1f0b0f6..1c0f2e3 100644 --- a/bookstack-helm/templates/bookstack.yaml +++ b/bookstack-helm/templates/bookstack.yaml @@ -17,6 +17,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: + app: bookstack {{- include "bookstack-helm.labels" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} @@ -40,6 +41,16 @@ spec: configMapKeyRef: name: wikiconfig key: TZ + - name: PUID + valueFrom: + configMapKeyRef: + name: wikiconfig + key: PUID + - name: PGID + valueFrom: + configMapKeyRef: + name: wikiconfig + key: PGID - name: APP_URL valueFrom: configMapKeyRef: @@ -95,12 +106,12 @@ spec: configMapKeyRef: name: wikiconfig key: DB_DATABASE - - name: DB_USERNAME + - name: DB_USER valueFrom: secretKeyRef: name: wiki-secrets key: db-username - - name: DB_PASSWORD + - name: DB_PASS valueFrom: secretKeyRef: name: wiki-secrets diff --git a/bookstack-helm/templates/bookstack_service.yaml b/bookstack-helm/templates/bookstack_service.yaml new file mode 100644 index 0000000..05df610 --- /dev/null +++ b/bookstack-helm/templates/bookstack_service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "bookstack-helm.fullname" . }}-bookstack + namespace: {{ .Values.meshwiki_app_namespace }} + labels: + {{- include "bookstack-helm.labels" . | nindent 4 }} +spec: + type: NodePort + ports: + - port: {{ .Values.bookstack.port }} + targetPort: {{ .Values.bookstack.port }} + protocol: TCP + name: http + nodePort: 30875 + selector: + app: bookstack diff --git a/bookstack-helm/templates/configmap.yaml b/bookstack-helm/templates/configmap.yaml index 36b4db1..4ec2190 100644 --- a/bookstack-helm/templates/configmap.yaml +++ b/bookstack-helm/templates/configmap.yaml @@ -4,7 +4,9 @@ metadata: name: wikiconfig namespace: {{ .Values.meshwiki_app_namespace }} data: - APP_URL: {{ .Values.bookstack.app_url | quote }} + APP_URL: https://{{ .Values.bookstack.fqdn }} + PUID: {{ .Values.bookstack.uid | quote }} + PGID: {{ .Values.bookstack.gid | quote }} DB_HOST: {{ include "bookstack-helm.fullname" . }}-db.{{ .Values.meshwiki_app_namespace }}.svc.cluster.local DB_DATABASE: {{ .Values.bookstack.db.database_name | quote }} TZ: {{ .Values.timezone | quote }} diff --git a/bookstack-helm/templates/db.yaml b/bookstack-helm/templates/db.yaml index e8a7b96..1793250 100644 --- a/bookstack-helm/templates/db.yaml +++ b/bookstack-helm/templates/db.yaml @@ -17,7 +17,8 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: - {{- include "bookstack-helm.labels" . | nindent 8 }} + app: wiki-db + {{- include "bookstack-helm.labels" . | nindent 8 }}-db {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} @@ -31,8 +32,8 @@ spec: image: "{{ .Values.db_image.repository }}:{{ .Values.db_image.tag }}" imagePullPolicy: {{ .Values.db_image.pullPolicy }} ports: - - name: http - containerPort: {{ .Values.service.port }} + - name: db + containerPort: {{ .Values.bookstack.db.port }} protocol: TCP env: - name: TZ diff --git a/bookstack-helm/templates/db_service.yaml b/bookstack-helm/templates/db_service.yaml new file mode 100644 index 0000000..727cb04 --- /dev/null +++ b/bookstack-helm/templates/db_service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "bookstack-helm.fullname" . }}-db + namespace: {{ .Values.meshwiki_app_namespace }} + labels: + {{- include "bookstack-helm.labels" . | nindent 4 }} +spec: + ports: + - port: {{ .Values.bookstack.db.port }} + targetPort: {{ .Values.bookstack.db.port }} + protocol: TCP + name: db + selector: + app: wiki-db diff --git a/bookstack-helm/templates/ingress.yaml b/bookstack-helm/templates/ingress.yaml index 1e3b33b..86a664e 100644 --- a/bookstack-helm/templates/ingress.yaml +++ b/bookstack-helm/templates/ingress.yaml @@ -1,61 +1,19 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "bookstack-helm.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} kind: Ingress metadata: - name: {{ $fullName }} + name: {{ include "bookstack-helm.fullname" . }} + namespace: {{ .Values.meshwiki_app_namespace }} labels: {{- include "bookstack-helm.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} + - host: {{ .Values.bookstack.fqdn }} http: paths: - {{- range .paths }} - - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} + - path: "/" + pathType: ImplementationSpecific backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: - name: {{ $fullName }} + name: {{ include "bookstack-helm.fullname" . }}-bookstack port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} + number: {{ .Values.bookstack.port }} diff --git a/bookstack-helm/templates/secrets.yaml b/bookstack-helm/templates/secrets.yaml index 947fcf9..cb041d0 100644 --- a/bookstack-helm/templates/secrets.yaml +++ b/bookstack-helm/templates/secrets.yaml @@ -9,4 +9,7 @@ data: db-password: {{ .Values.bookstack.db.password | b64enc | quote }} db-root-password: {{ .Values.bookstack.db.root_password | b64enc | quote }} mail-username: {{ .Values.bookstack.mail.username | b64enc | quote }} - mail-password: {{ .Values.bookstack.mail.password | b64enc | quote }} \ No newline at end of file + mail-password: {{ .Values.bookstack.mail.password | b64enc | quote }} + access-key-id: {{ .Values.bookstack.aws.access_key_id | b64enc | quote }} + secret-access-key: {{ .Values.bookstack.aws.secret_access_key | b64enc | quote }} + backup-s3-url: {{ .Values.bookstack.aws.backup_s3_url | b64enc | quote }} \ No newline at end of file diff --git a/bookstack-helm/templates/service.yaml b/bookstack-helm/templates/service.yaml deleted file mode 100644 index 0626250..0000000 --- a/bookstack-helm/templates/service.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "bookstack-helm.fullname" . }}-bookstack - namespace: {{ .Values.meshwiki_app_namespace }} - labels: - {{- include "bookstack-helm.labels" . | nindent 4 }} -spec: - type: NodePort - ports: - - port: {{ .Values.bookstack.port }} - targetPort: {{ .Values.bookstack.port }} - protocol: TCP - name: http - nodePort: 30875 - selector: - {{- include "bookstack-helm.selectorLabels" . | nindent 4 }}-bookstack ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "bookstack-helm.fullname" . }}-db - namespace: {{ .Values.meshwiki_app_namespace }} - labels: - {{- include "bookstack-helm.labels" . | nindent 4 }} -spec: - ports: - - port: {{ .Values.bookstack.db.port }} - targetPort: {{ .Values.bookstack.db.port }} - protocol: TCP - name: db - selector: - {{- include "bookstack-helm.selectorLabels" . | nindent 4 }}-db \ No newline at end of file diff --git a/bookstack-helm/values.yaml b/bookstack-helm/values.yaml index 2715a99..a70e784 100644 --- a/bookstack-helm/values.yaml +++ b/bookstack-helm/values.yaml @@ -6,13 +6,14 @@ meshwiki_app_namespace: "wiki" timezone: "America/New_York" bookstack: - app_url: https://devwiki.mesh.nycmesh.net app_views_books: list allowed_iframe_sources: "https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com https://*.google.com" - port: 6875 + port: 80 + uid: 1000 + gid: 1000 db: database_name: bookstackapp - pvc_name: wiki_db + pvc_name: wikidb pvc_size: 5Gi port: 3306 mail: @@ -20,16 +21,18 @@ bookstack: port: 587 from: "nycmeshwiki@gmail.com" from_name: "NYC Mesh Wiki" - config_pvc_name: wiki_config + config_pvc_name: wikiconfig config_pvc_size: 5Gi - image_pvc_name: wiki_images + image_pvc_name: wikiimages image_pvc_size: 5Gi + backup: + cron_schedule: "33 3 * * *" image: repository: lscr.io/linuxserver/bookstack pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "latest" + tag: "v23.02.2-ls71" db_image: repository: lscr.io/linuxserver/mariadb @@ -69,22 +72,6 @@ service: type: ClusterIP port: 8080 -ingress: - enabled: false - className: "" - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little