commit e2adb79e2df20b6989c19829277c62099348f03f Author: Root At Skic Date: Sun Nov 8 21:55:40 2026 +0200 Archify Sync GitOps template A Copier template for deploying Archify Sync with Argo CD: an app-of-apps, the server's Helm chart and its values, optional secrets through External Secrets and an optional static NFS volume for PostgreSQL. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd0a2b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +.env-* +~* +/tmp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f21105b --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# archify-vscode-ext-sync-gitops-tmpl + +A [Copier](https://copier.readthedocs.io) template for deploying [Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) with [Argo CD](https://argo-cd.readthedocs.io). It renders an environment repository that Argo CD syncs: an app-of-apps, the server's Helm chart (taken from the server repository) and its values. Keep your answers in the environment repository; never hand-edit what Copier renders there. + +```text +cluster/ +├── 0000-bootstrap/0100-app-of-apps.yml.jinja # root app 0000-archify-sync-app-of-apps, applied once +├── 0300-applications/ +│ ├── application.yml.jinja # the root app managing itself +│ ├── {{ '0100-archify-sync-secrets' if … }}/ # optional: secrets through External Secrets +│ ├── {{ '0150-archify-sync-storage' if … }}/ # optional: a static NFS volume for PostgreSQL +│ └── {{ '0200-archify-sync' if … }}/ # the chart from the server repo + values from here +└── 0500-resources/ # what those apps deploy +``` + +Answers: `argocd` (namespace, project, the environment repository and revision) and `archify_sync` (`is_enabled`, chart source, image, host, database, single sign-on, metrics, `external_secrets`). `copier-answers.example.yml` is a complete example. + +## Use it + +1. **Image.** Build the server image from the server repository's Dockerfile and push it to your registry (`image.repository`, `image.tag`). For a private registry, name an image pull Secret in `image.pull_secret`. +2. **Identity provider.** Create the client and groups described in the server's README (single sign-on section). +3. **Secrets.** Without External Secrets, create the server's Secret yourself; the bundled PostgreSQL and the server read it: + + ```bash + kubectl create namespace archify-sync + kubectl -n archify-sync create secret generic archify-sync-secrets \ + --from-literal=password="$PG_PASSWORD" \ + --from-literal=url="postgres://archify:$PG_PASSWORD@archify-sync-postgresql:5432/archify" \ + --from-literal=client-secret="$OIDC_CLIENT_SECRET" + ``` + + The password goes into a URL: letters and digits only. With [External Secrets](https://external-secrets.io) installed, set `external_secrets.store` and the names of the two entries instead, and `external_secrets.image_pull` for a private registry; the template then renders the ExternalSecrets. +4. **Render.** Into a new environment repository, from your answers: + + ```bash + copier copy --trust \ + --data-file my-answers.yml \ + https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync-gitops-tmpl.git \ + ../archify-sync-gitops + ``` + + Push it where Argo CD can read it (`argocd.repo_url`). +5. **Connect.** Give Argo CD access to the environment repository, then `kubectl apply -f cluster/0000-bootstrap/0100-app-of-apps.yml` once. After that the app-of-apps manages itself. + +Behind the ingress the server needs WebSocket upgrades and long read and send timeouts; the rendered values set them for ingress-nginx. + +## Tasks + +```bash +task validate # render off, on, and with External Secrets into tmp/; check YAML and paths; helm lint the values +task render # a new template version: copier update in the environment repo (ENV_DIR) +task render:answers # after editing the environment repo's cluster/copier-answers.yml: copier recopy +``` + +`copier update` needs this repository committed and tagged; the environment repository records the template's version. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..f1a824f --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,58 @@ +# https://taskfile.dev +version: "3" + +vars: + ENV_DIR: '{{.ENV_DIR | default "../archify-vscode-ext-sync-gitops"}}' + TMPL_URL: '{{.TMPL_URL | default "https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync-gitops-tmpl.git"}}' + CHART_DIR: '{{.CHART_DIR | default "../archify-vscode-ext-sync/charts/archify-sync"}}' + +tasks: + render:first: + desc: First render into the environment repository from the pushed template at a tag (-- REF=v0.1.0), with copier-answers.example.yml + requires: + vars: [REF] + cmds: + # From Gitea, not this folder: the answers file records where the template lives, so + # `copier update` in the environment repo finds it and its tags later. + - copier copy --trust --overwrite --defaults --vcs-ref "{{.REF}}" --data-file copier-answers.example.yml "{{.TMPL_URL}}" "{{.ENV_DIR}}" + + render: + desc: Re-render the environment repository from its own cluster/copier-answers.yml (copier update) + dir: "{{.ENV_DIR}}" + cmds: + - copier update --trust --defaults --skip-answered --answers-file cluster/copier-answers.yml + + render:answers: + desc: After editing the environment repository's cluster/copier-answers.yml - re-render everything from those answers (copier recopy) + dir: "{{.ENV_DIR}}" + cmds: + # copier update only carries template changes; changed answers need a recopy. + - copier recopy --trust --defaults --skip-answered --overwrite --answers-file cluster/copier-answers.yml + + validate: + desc: Render the working copy (HEAD plus uncommitted changes, not the latest tag) flag off and on into tmp/, check YAML, paths and leftovers, lint the chart with the values + cmds: + - rm -rf tmp && mkdir -p tmp + - copier copy --trust --overwrite --defaults --vcs-ref HEAD . tmp/off + - test -z "$(ls tmp/off/cluster/0300-applications | grep archify-sync)" + - copier copy --trust --overwrite --defaults --vcs-ref HEAD --data-file copier-answers.example.yml . tmp/on + - test "$(find tmp -name '*.jinja' | wc -l | tr -d ' ')" = 0 + - for f in $(find tmp -name '*.yml'); do yq -e 'true' "$f" >/dev/null || { echo "does not parse $f"; exit 1; }; done + - test ! -d tmp/on/cluster/0500-resources/0100-archify-sync-secrets + # With External Secrets and a private registry: ESO's own {{"{{ }}"}} must survive. + - yq '.archify_sync.external_secrets = {"store":"vault","postgres_password_key":"pg","oidc_client_secret_key":"oidc","image_pull":{"registry":"registry.example.com","username":"pull","password_key":"reg"}} | .archify_sync.image.pull_secret = "archify-sync-image-pull"' copier-answers.example.yml > tmp/eso.yml + - copier copy --trust --overwrite --defaults --vcs-ref HEAD --data-file tmp/eso.yml . tmp/eso + - test "$(find tmp/eso -name '*.jinja' | wc -l | tr -d ' ')" = 0 + - grep -q '{{"{{"}} .postgresPassword }}' tmp/eso/cluster/0500-resources/0100-archify-sync-secrets/archify-sync-secrets.yml + - grep -q '"password" .password' tmp/eso/cluster/0500-resources/0100-archify-sync-secrets/archify-sync-image-pull.yml + - | + for f in $(find tmp/on/cluster/0300-applications -name '*.yml'); do + for p in $(yq '(.spec.source.path, .spec.sources[].path) | select(. != null)' "$f"); do + case "$p" in cluster/*) [ -d "tmp/on/$p" ] || { echo "$f: missing $p"; exit 1; };; esac + done + for v in $(yq '.spec.sources[].helm.valueFiles[]' "$f" 2>/dev/null); do + [ -f "tmp/on/${v#\$values/}" ] || { echo "$f: missing $v"; exit 1; } + done + done + - helm lint "{{.CHART_DIR}}" -f tmp/on/cluster/0500-resources/0200-archify-sync/archify-sync/values.yml + - echo "valid" diff --git a/cluster/0000-bootstrap/0100-app-of-apps.yml.jinja b/cluster/0000-bootstrap/0100-app-of-apps.yml.jinja new file mode 100644 index 0000000..24ec704 --- /dev/null +++ b/cluster/0000-bootstrap/0100-app-of-apps.yml.jinja @@ -0,0 +1,34 @@ +{% macro render() %} +--- +# Applied once by hand (or by the connect step); after that the copy in +# 0300-applications keeps it in step with git. Named apart from other +# app-of-apps that share the Argo CD namespace. +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 0000-archify-sync-app-of-apps + namespace: {{ argocd.get('namespace', 'argocd') }} + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: {{ argocd.get('project', 'default') }} + source: + repoURL: {{ argocd.get('repo_url', 'https://git.example.com/you/archify-sync-gitops.git') }} + targetRevision: {{ argocd.get('target_revision', 'HEAD') }} + path: cluster/0300-applications + directory: + recurse: true + destination: + server: {{ argocd.get('destination_server', 'https://kubernetes.default.svc') }} + namespace: {{ argocd.get('namespace', 'argocd') }} + syncPolicy: + automated: + prune: true + selfHeal: true +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0300-applications/application.yml.jinja b/cluster/0300-applications/application.yml.jinja new file mode 100644 index 0000000..4cec012 --- /dev/null +++ b/cluster/0300-applications/application.yml.jinja @@ -0,0 +1,34 @@ +{% macro render() %} +--- +# The app-of-apps managing itself: the bootstrap copy is applied once, then this +# one (inside the recursed tree) keeps it in step with git. Named apart from +# other app-of-apps that share the Argo CD namespace. +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 0000-archify-sync-app-of-apps + namespace: {{ argocd.get('namespace', 'argocd') }} + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: {{ argocd.get('project', 'default') }} + source: + repoURL: {{ argocd.get('repo_url', 'https://git.example.com/you/archify-sync-gitops.git') }} + targetRevision: {{ argocd.get('target_revision', 'HEAD') }} + path: cluster/0300-applications + directory: + recurse: true + destination: + server: {{ argocd.get('destination_server', 'https://kubernetes.default.svc') }} + namespace: {{ argocd.get('namespace', 'argocd') }} + syncPolicy: + automated: + prune: true + selfHeal: true +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0300-applications/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja b/cluster/0300-applications/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja new file mode 100644 index 0000000..dbf6658 --- /dev/null +++ b/cluster/0300-applications/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja @@ -0,0 +1,37 @@ +{% macro render() %} +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 0100-archify-sync-secrets + namespace: {{ argocd.get('namespace', 'argocd') }} + finalizers: + - resources-finalizer.argocd.argoproj.io + annotations: + # Before the server: its pods need the Secret this app makes. + argocd.argoproj.io/sync-wave: "0" +spec: + project: {{ argocd.get('project', 'default') }} + # The server's secrets from your secret store through External Secrets. + source: + repoURL: {{ argocd.get('repo_url', 'https://git.example.com/you/archify-sync-gitops.git') }} + targetRevision: {{ argocd.get('target_revision', 'HEAD') }} + path: cluster/0500-resources/0100-archify-sync-secrets + directory: + recurse: true + destination: + server: {{ argocd.get('destination_server', 'https://kubernetes.default.svc') }} + namespace: {{ archify_sync.get('namespace', 'archify-sync') }} + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0300-applications/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja b/cluster/0300-applications/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja new file mode 100644 index 0000000..81671ef --- /dev/null +++ b/cluster/0300-applications/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja @@ -0,0 +1,37 @@ +{% macro render() %} +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 0150-archify-sync-storage + namespace: {{ argocd.get('namespace', 'argocd') }} + finalizers: + - resources-finalizer.argocd.argoproj.io + annotations: + # Before the server: its database needs the claim this app makes. + argocd.argoproj.io/sync-wave: "0" +spec: + project: {{ argocd.get('project', 'default') }} + # The database volume: a static NFS volume, an export owned by uid 70. + source: + repoURL: {{ argocd.get('repo_url', 'https://git.example.com/you/archify-sync-gitops.git') }} + targetRevision: {{ argocd.get('target_revision', 'HEAD') }} + path: cluster/0500-resources/0150-archify-sync-storage + directory: + recurse: true + destination: + server: {{ argocd.get('destination_server', 'https://kubernetes.default.svc') }} + namespace: {{ archify_sync.get('namespace', 'archify-sync') }} + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0300-applications/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync.yml.jinja b/cluster/0300-applications/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync.yml.jinja new file mode 100644 index 0000000..d4b74a8 --- /dev/null +++ b/cluster/0300-applications/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync.yml.jinja @@ -0,0 +1,44 @@ +{% macro render() %} +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 0200-archify-sync + namespace: {{ argocd.get('namespace', 'argocd') }} + finalizers: + - resources-finalizer.argocd.argoproj.io + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + project: {{ argocd.get('project', 'default') }} + # Archify Sync: the chart from the server repository, the values from this one. + # One pod by design (the live copy of each model is held by one server), so + # updates are Recreate; editors work offline for those seconds and catch up. + sources: + - repoURL: {{ archify_sync.get('chart_repo', 'https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync.git') }} + targetRevision: {{ archify_sync.get('chart_revision', 'main') }} + path: {{ archify_sync.get('chart_path', 'charts/archify-sync') }} + helm: + # The release name is part of the database URL in the ExternalSecret (0100). + releaseName: archify-sync + valueFiles: + - $values/cluster/0500-resources/0200-archify-sync/archify-sync/values.yml + - repoURL: {{ argocd.get('repo_url', 'https://git.example.com/you/archify-sync-gitops.git') }} + targetRevision: {{ argocd.get('target_revision', 'HEAD') }} + ref: values + destination: + server: {{ argocd.get('destination_server', 'https://kubernetes.default.svc') }} + namespace: {{ archify_sync.get('namespace', 'archify-sync') }} + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja b/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja new file mode 100644 index 0000000..2841e1c --- /dev/null +++ b/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/archify-sync-secrets.yml.jinja @@ -0,0 +1,36 @@ +{%- set es = archify_sync.get('external_secrets', {}) -%} +# Secret `archify-sync-secrets` from your secret store through External Secrets. +# ESO's own templating is escaped below so it reaches the cluster as written. +# +# The store needs two entries (named in external_secrets): +# postgres_password_key password of the bundled postgres role `archify`; +# letters and digits only (it goes into a URL) +# oidc_client_secret_key secret of the archify-sync client at the identity provider +# +# The URL names the chart's database Service, `-postgresql`; the release is +# `archify-sync` (0200-archify-sync). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: archify-sync-secrets + namespace: {{ archify_sync.get('namespace', 'archify-sync') }} +spec: + refreshInterval: 1h + secretStoreRef: + name: {{ es.get('store') }} + kind: {{ es.get('store_kind', 'ClusterSecretStore') }} + target: + name: archify-sync-secrets + template: + engineVersion: v2 + data: + password: "{% raw %}{{ .postgresPassword }}{% endraw %}" + url: "postgres://archify:{% raw %}{{ .postgresPassword }}{% endraw %}@archify-sync-postgresql:5432/archify" + client-secret: "{% raw %}{{ .oidcClientSecret }}{% endraw %}" + data: + - secretKey: postgresPassword + remoteRef: + key: {{ es.get('postgres_password_key', 'archify-sync-postgres-password') }} + - secretKey: oidcClientSecret + remoteRef: + key: {{ es.get('oidc_client_secret_key', 'archify-sync-oidc-client-secret') }} diff --git a/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/{{ archify_sync.get('image', {}).get('pull_secret', '') ~ '.yml' if archify_sync.get('external_secrets', {}).get('image_pull') and archify_sync.get('image', {}).get('pull_secret') }}.jinja b/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/{{ archify_sync.get('image', {}).get('pull_secret', '') ~ '.yml' if archify_sync.get('external_secrets', {}).get('image_pull') and archify_sync.get('image', {}).get('pull_secret') }}.jinja new file mode 100644 index 0000000..30486fb --- /dev/null +++ b/cluster/0500-resources/{{ '0100-archify-sync-secrets' if archify_sync.get('is_enabled', false) and archify_sync.get('external_secrets', {}).get('store') }}/{{ archify_sync.get('image', {}).get('pull_secret', '') ~ '.yml' if archify_sync.get('external_secrets', {}).get('image_pull') and archify_sync.get('image', {}).get('pull_secret') }}.jinja @@ -0,0 +1,27 @@ +{%- set pull = archify_sync.get('external_secrets', {}).get('image_pull', {}) -%} +{%- set es = archify_sync.get('external_secrets', {}) -%} +{%- set user = pull.get('username') | tojson -%} +# The image pull secret for a private registry: its password from your secret store. +# ESO's own templating is escaped below so it reaches the cluster as written. +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ archify_sync.image.pull_secret }} + namespace: {{ archify_sync.get('namespace', 'archify-sync') }} +spec: + refreshInterval: 1h + secretStoreRef: + name: {{ es.get('store') }} + kind: {{ es.get('store_kind', 'ClusterSecretStore') }} + target: + name: {{ archify_sync.image.pull_secret }} + creationPolicy: Owner + template: + engineVersion: v2 + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: '{{ '{{' }} dict "auths" (dict {{ pull.get('registry') | tojson }} (dict "username" {{ user }} "password" .password "auth" (printf "{{ pull.get('username') }}:%s" .password | b64enc))) | toJson {{ '}}' }}' + data: + - secretKey: password + remoteRef: + key: {{ pull.get('password_key') }} diff --git a/cluster/0500-resources/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja b/cluster/0500-resources/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja new file mode 100644 index 0000000..a14c2ba --- /dev/null +++ b/cluster/0500-resources/{{ '0150-archify-sync-storage' if archify_sync.get('is_enabled', false) and archify_sync.get('database', {}).get('nfs_path') }}/archify-sync-storage.yml.jinja @@ -0,0 +1,49 @@ +{% macro render() %} +{%- set database = archify_sync.get('database', {}) %} +{%- set namespace = archify_sync.get('namespace', 'archify-sync') %} +--- +# Static NFS volume: an export owned by postgres:17-alpine's uid 70 with mode 0700, +# shared with maproot. Shares that map every user to root are refused by PostgreSQL. +# Retain: deleting the app never deletes the data. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ namespace }}-database + labels: + app.kubernetes.io/part-of: archify-sync +spec: + capacity: + storage: {{ database.get('size', '8Gi') }} + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + nfs: + server: {{ database.get('nfs_server') }} + path: {{ database.get('nfs_path') }} + claimRef: + namespace: {{ namespace }} + name: archify-sync-database +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: archify-sync-database + namespace: {{ namespace }} + labels: + app.kubernetes.io/part-of: archify-sync +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + volumeName: {{ namespace }}-database + resources: + requests: + storage: {{ database.get('size', '8Gi') }} +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/cluster/0500-resources/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync/values.yml.jinja b/cluster/0500-resources/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync/values.yml.jinja new file mode 100644 index 0000000..e6e3ec2 --- /dev/null +++ b/cluster/0500-resources/{{ '0200-archify-sync' if archify_sync.get('is_enabled', false) }}/archify-sync/values.yml.jinja @@ -0,0 +1,72 @@ +{% macro render() %} +{%- set image = archify_sync.get('image', {}) %} +{%- set database = archify_sync.get('database', {}) %} +{%- set oidc = archify_sync.get('oidc', {}) %} +{%- set metrics = archify_sync.get('metrics', {}) %} +{%- set host = archify_sync.get('host', 'archify.example.com') %} +# Values for charts/archify-sync of the server repository. +image: + repository: {{ image.get('repository', 'registry.example.com/archify-sync') }} + tag: {{ image.get('tag', '0.1.0') | string | tojson }} + pullPolicy: IfNotPresent +{%- if image.get('pull_secret') %} +imagePullSecrets: + - name: {{ image.get('pull_secret') }} +{%- endif %} + +# TLS ends at your ingress or edge proxy; people reach the server here. +publicUrl: https://{{ host }} + +# The bundled PostgreSQL, its password and URL from Secret archify-sync-secrets +# (0100 with External Secrets, or made by hand). +postgresql: + enabled: true + existingSecret: archify-sync-secrets +{%- if database.get('nfs_path') %} + # The static NFS volume from 0150-archify-sync-storage. + existingClaim: archify-sync-database +{%- else %} +{%- if database.get('storage_class') %} + storageClass: {{ database.get('storage_class') }} +{%- endif %} + storage: {{ database.get('size', '8Gi') }} +{%- endif %} + +config: + name: Archify Sync + membersCanInvite: {{ archify_sync.get('members_can_invite', false) | tojson }} + logJson: true + +{%- if oidc.get('is_enabled', false) %} +oidc: + enabled: true + issuer: {{ oidc.get('issuer', 'https://sso.example.com/realms/home') }} + clientId: {{ oidc.get('client_id', 'archify-sync') }} + existingSecret: archify-sync-secrets + existingSecretKey: client-secret + label: {{ oidc.get('label', 'Keycloak') | tojson }} + allowedGroups: {{ oidc.get('allowed_groups', []) | tojson }} + adminGroups: {{ oidc.get('admin_groups', []) | tojson }} +{%- endif %} + +ingress: + enabled: true + className: {{ archify_sync.get('ingress_class', 'nginx') }} + host: {{ host }} + annotations: + # Edits travel over a WebSocket that stays open for hours. + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-body-size: 8m + +metrics: + enabled: {{ metrics.get('is_enabled', true) | tojson }} + serviceMonitor: + enabled: {{ metrics.get('service_monitor', false) | tojson }} +{% endmacro %} +{%- set result = render() -%} +{%- for line in result.split('\n') -%} +{% if line | trim | length != 0 -%} +{{ line }} +{% endif %} +{%- endfor %} diff --git a/copier-answers.example.yml b/copier-answers.example.yml new file mode 100644 index 0000000..7444c3f --- /dev/null +++ b/copier-answers.example.yml @@ -0,0 +1,57 @@ +# An example environment. Copy to your environment repository's +# cluster/copier-answers.yml on the first render (task render:first), then +# change values there and run `task render` (copier update). +argocd: + namespace: argocd + project: default + destination_server: https://kubernetes.default.svc + # The environment repository Argo CD reads (this template renders into it). + repo_url: https://git.example.com/you/archify-sync-gitops.git + target_revision: HEAD + +archify_sync: + is_enabled: true + namespace: archify-sync + # The Helm chart lives in the server repository. + chart_repo: https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync.git + chart_path: charts/archify-sync + chart_revision: main + image: + # Built from the server repository's Dockerfile and pushed to your registry. + repository: registry.example.com/archify-sync + tag: "0.1.4" + # For a private registry: the name of an image pull Secret in the namespace. + # pull_secret: archify-sync-image-pull + host: archify.example.com + ingress_class: nginx + database: + size: 8Gi + # The cluster's default storage class when unset. It must keep file owners: + # PostgreSQL refuses a data directory that maps every user to root. + # storage_class: standard + # Or a static NFS volume, an export owned by uid 70, mode 0700: + # nfs_server: 10.0.0.10 + # nfs_path: /exports/archify-sync-database + oidc: + is_enabled: true + issuer: https://sso.example.com/realms/home + client_id: archify-sync + label: Keycloak + allowed_groups: [archify-users] + admin_groups: [archify-admins] + members_can_invite: false + metrics: + is_enabled: true + service_monitor: false + # Without external_secrets, create Secret archify-sync-secrets yourself (see + # README). With External Secrets installed, name the store and its entries: + # external_secrets: + # store: vault + # store_kind: ClusterSecretStore + # postgres_password_key: archify-sync-postgres-password + # oidc_client_secret_key: archify-sync-oidc-client-secret + # # And the image pull secret for a private registry (needs image.pull_secret): + # image_pull: + # registry: registry.example.com + # username: archify-pull + # password_key: archify-sync-registry-password diff --git a/copier.yml b/copier.yml new file mode 100644 index 0000000..40e429c --- /dev/null +++ b/copier.yml @@ -0,0 +1,26 @@ +--- +# Copier template for the Argo CD deployment of Archify Sync. Renders into the +# environment repository (archify-vscode-ext-sync-gitops), which Argo CD syncs. +_templates_suffix: ".jinja" +_answers_file: cluster/copier-answers.yml +_exclude: + - "~*" + - ".git" + - ".env" + - ".env-*" + - "/tmp" + - "/copier.yml" + - "/copier-answers.example.yml" + - "/Taskfile.yml" + - "/README.md" + - "/.gitignore" + +argocd: + type: yaml + help: "Argo CD: namespace, project, the environment repository and its revision" + default: {} + +archify_sync: + type: yaml + help: "Archify Sync: is_enabled, image, host, database, single sign-on" + default: {} diff --git a/{{ _copier_conf.answers_file }}.jinja b/{{ _copier_conf.answers_file }}.jinja new file mode 100644 index 0000000..07ddf05 --- /dev/null +++ b/{{ _copier_conf.answers_file }}.jinja @@ -0,0 +1,2 @@ +# Changes here will be overwritten by Copier; run `task render` in the template repo after editing. +{{ _copier_answers|to_nice_yaml -}}