installer: migrate apps to cuelang
diff --git a/core/installer/values-tmpl/appmanager.cue b/core/installer/values-tmpl/appmanager.cue
new file mode 100644
index 0000000..dbf60e7
--- /dev/null
+++ b/core/installer/values-tmpl/appmanager.cue
@@ -0,0 +1,49 @@
+import (
+	"encoding/base64"
+)
+
+input: {
+	repoAddr: string
+	sshPrivateKey: string
+}
+
+images: {
+	appmanager: {
+		repository: "giolekva"
+		name: "pcloud-installer"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	appmanager: {
+		chart: "charts/appmanager"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	appmanager: {
+		chart: charts.appmanager
+		values: {
+			repoAddr: input.repoAddr
+			sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
+			ingress: {
+				className: _ingressPrivate
+				domain: "apps.\(global.privateDomain)"
+				certificateIssuer: ""
+			}
+			clusterRoleName: "\(global.id)-appmanager"
+			image: {
+				repository: images.appmanager.fullName
+				tag: images.appmanager.tag
+				pullPolicy: images.appmanager.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/appmanager.jsonschema b/core/installer/values-tmpl/appmanager.jsonschema
deleted file mode 100644
index 8a011ca..0000000
--- a/core/installer/values-tmpl/appmanager.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "RepoAddr": { "type": "string", "default": "ssh://192.168.0.11/example" },
-	"SSHPrivateKey": { "type": "string", "default": "foo bar" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/appmanager.md b/core/installer/values-tmpl/appmanager.md
deleted file mode 100644
index 8fdc4ea..0000000
--- a/core/installer/values-tmpl/appmanager.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs PCloud App Manager
diff --git a/core/installer/values-tmpl/appmanager.yaml b/core/installer/values-tmpl/appmanager.yaml
deleted file mode 100644
index c630a76..0000000
--- a/core/installer/values-tmpl/appmanager.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: appmanager
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/appmanager
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    repoAddr: {{ .Values.RepoAddr }}
-    sshPrivateKey: {{ .Values.SSHPrivateKey | b64enc }}
-    ingress:
-      className: {{ .Global.Id }}-ingress-private
-      domain: apps.{{ .Global.PrivateDomain }}
-      certificateIssuer: ""
-    clusterRoleName: {{ .Global.Id }}-appmanager
diff --git a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.jsonschema b/core/installer/values-tmpl/cert-manager-webhook-gandi-role.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.md b/core/installer/values-tmpl/cert-manager-webhook-gandi-role.md
deleted file mode 100644
index e6f01a3..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs rbacs to let cert-manager create gandi resource
diff --git a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.yaml b/core/installer/values-tmpl/cert-manager-webhook-gandi-role.yaml
deleted file mode 100644
index ef0b383..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-gandi-role.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: cert-manager-webhook-gandi-role
-  namespace: {{ .Global.PCloudEnvName }}-cert-manager
-spec:
-  dependsOn:
-    - name: cert-manager
-      namespace: {{ .Global.PCloudEnvName }}-cert-manager
-  chart:
-    spec:
-      chart: charts/cert-manager-webhook-gandi-role
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  timeout: 20m0s
-  values:
-    certManager:
-      namespace: {{ .Global.PCloudEnvName }}-cert-manager
-      name: {{ .Global.PCloudEnvName }}-cert-manager
diff --git a/core/installer/values-tmpl/cert-manager-webhook-pcloud.jsonschema b/core/installer/values-tmpl/cert-manager-webhook-pcloud.jsonschema
deleted file mode 100644
index a6adce7..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-pcloud.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-	"APIGroupName": { "type": "string" },
-	"ResolverName": { "type": "string" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/cert-manager-webhook-pcloud.md b/core/installer/values-tmpl/cert-manager-webhook-pcloud.md
deleted file mode 100644
index 39df923..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-pcloud.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs cert-manager DNS01 resolver for pcloud internal DNS server
diff --git a/core/installer/values-tmpl/cert-manager-webhook-pcloud.yaml b/core/installer/values-tmpl/cert-manager-webhook-pcloud.yaml
deleted file mode 100644
index d75634b..0000000
--- a/core/installer/values-tmpl/cert-manager-webhook-pcloud.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: cert-manager-webhook-pcloud
-  namespace: {{ .Global.PCloudEnvName }}-cert-manager
-spec:
-  dependsOn:
-    - name: cert-manager
-      namespace: {{ .Global.PCloudEnvName }}-cert-manager # TODO(giolekva): derivative
-  chart:
-    spec:
-      chart: charts/cert-manager-webhook-pcloud
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  timeout: 20m0s
-  values:
-    fullnameOverride: {{ .Global.PCloudEnvName }}-cert-manager-webhook-pcloud
-    certManager:
-      namespace: {{ .Global.PCloudEnvName }}-cert-manager
-      name: {{ .Global.PCloudEnvName }}-cert-manager
-    image:
-      repository: giolekva/dns-challenge-solver
-      tag: latest
-      pullPolicy: Always
-    logLevel: 2
-    apiGroupName: dodo.cloud
-    resolverName: dns-resolver-pcloud
diff --git a/core/installer/values-tmpl/cert-manager.cue b/core/installer/values-tmpl/cert-manager.cue
new file mode 100644
index 0000000..dbecd50
--- /dev/null
+++ b/core/installer/values-tmpl/cert-manager.cue
@@ -0,0 +1,104 @@
+input: {}
+
+images: {
+	certManager: {
+		registry: "quay.io"
+		repository: "jetstack"
+		name: "cert-manager-controller"
+		tag: "v1.12.2"
+		pullPolicy: "IfNotPresent"
+	}
+	cainjector: {
+		registry: "quay.io"
+		repository: "jetstack"
+		name: "cert-manager-cainjector"
+		tag: "v1.12.2"
+		pullPolicy: "IfNotPresent"
+	}
+	webhook: {
+		registry: "quay.io"
+		repository: "jetstack"
+		name: "cert-manager-webhook"
+		tag: "v1.12.2"
+		pullPolicy: "IfNotPresent"
+	}
+	dnsChallengeSolver: {
+		repository: "giolekva"
+		name: "dns-challenge-solver"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	certManager: {
+		chart: "charts/cert-manager"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+	dnsChallengeSolver: {
+		chart: "charts/cert-manager-webhook-pcloud"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"cert-manager": {
+		chart: charts.certManager
+		dependsOnExternal: [{
+			name: "ingress-public"
+			namespace: _ingressPublic
+		}]
+		values: {
+			fullnameOverride: "\(global.pcloudEnvName)-cert-manager"
+			installCRDs: true
+			dns01RecursiveNameserversOnly: true
+			dns01RecursiveNameservers: "1.1.1.1:53,8.8.8.8:53"
+			image: {
+				repository: images.certManager.fullName
+				tag: images.certManager.tag
+				pullPolicy: images.certManager.pullPolicy
+			}
+			cainjector: {
+				image: {
+					repository: images.cainjector.fullName
+					tag: images.cainjector.tag
+					pullPolicy: images.cainjector.pullPolicy
+				}
+			}
+			webhook: {
+				image: {
+					repository: images.webhook.fullName
+					tag: images.webhook.tag
+					pullPolicy: images.webhook.pullPolicy
+				}
+			}
+		}
+	}
+	"cert-manager-webhook-pcloud": {
+		chart: charts.dnsChallengeSolver
+		dependsOn: [helm["cert-manager"]]
+		values: {
+			fullnameOverride: "\(global.pcloudEnvName)-cert-manager-webhook-pcloud"
+			certManager: {
+				name: "\(global.pcloudEnvName)-cert-manager"
+				namespace: "\(global.pcloudEnvName)-cert-manager"
+			}
+			image: {
+				repository: images.dnsChallengeSolver.fullName
+				tag: images.dnsChallengeSolver.tag
+				pullPolicy: images.dnsChallengeSolver.pullPolicy
+			}
+			logLevel: 2
+			apiGroupName: "dodo.cloud"
+			resolverName: "dns-resolver-pcloud"
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/cert-manager.jsonschema b/core/installer/values-tmpl/cert-manager.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/cert-manager.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/cert-manager.md b/core/installer/values-tmpl/cert-manager.md
deleted file mode 100644
index aba785a..0000000
--- a/core/installer/values-tmpl/cert-manager.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs cert-manager
diff --git a/core/installer/values-tmpl/cert-manager.yaml b/core/installer/values-tmpl/cert-manager.yaml
deleted file mode 100644
index 9f30311..0000000
--- a/core/installer/values-tmpl/cert-manager.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: cert-manager
-  namespace: {{ .Release.Namespace }}
-spec:
-  dependsOn:
-    - name: ingress-public
-      namespace: {{ .Global.PCloudEnvName }}-ingress-public # TODO(giolekva): derivative
-  chart:
-    spec:
-      chart: charts/cert-manager
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  timeout: 20m0s
-  values:
-    fullnameOverride: {{ .Global.PCloudEnvName }}-cert-manager
-    installCRDs: true
-    image:
-      tag: v1.11.1
-      pullPolicy: IfNotPresent
-    dns01RecursiveNameserversOnly: true
-    dns01RecursiveNameservers: "1.1.1.1:53,8.8.8.8:53"
diff --git a/core/installer/values-tmpl/certificate-issuer-private.cue b/core/installer/values-tmpl/certificate-issuer-private.cue
new file mode 100644
index 0000000..5c67a84
--- /dev/null
+++ b/core/installer/values-tmpl/certificate-issuer-private.cue
@@ -0,0 +1,42 @@
+input: {
+	apiConfigMap: {
+		name: string
+		namespace: string
+	}
+}
+
+images: {}
+
+charts: {
+	"certificate-issuer-private": {
+		chart: "charts/certificate-issuer-private"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	"certificate-issuer-private": {
+		chart: charts["certificate-issuer-private"]
+		dependsOnExternal: [{
+			name: "ingress-nginx"
+			namespace: "\(global.namespacePrefix)ingress-private"
+		}]
+		values: {
+			issuer: {
+				name: _issuerPrivate
+				server: "https://acme-v02.api.letsencrypt.org/directory"
+				// server: "https://acme-staging-v02.api.letsencrypt.org/directory"
+				domain: global.privateDomain
+				contactEmail: global.contactEmail
+			}
+			apiConfigMap: {
+				name: input.apiConfigMap.name
+				namespace: input.apiConfigMap.namespace
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/certificate-issuer-private.jsonschema b/core/installer/values-tmpl/certificate-issuer-private.jsonschema
deleted file mode 100644
index 27f907e..0000000
--- a/core/installer/values-tmpl/certificate-issuer-private.jsonschema
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "APIConfigMap": {
-	  "type": "object",
-	  "properties": {
-		"Name": { "type": "string" },
-		"Namespace": { "type": "string" }
-	  },
-	  "additionalProperties": false
-	}
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/certificate-issuer-private.md b/core/installer/values-tmpl/certificate-issuer-private.md
deleted file mode 100644
index 9ee84cc..0000000
--- a/core/installer/values-tmpl/certificate-issuer-private.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs certificate issuer for private domain
diff --git a/core/installer/values-tmpl/certificate-issuer-private.yaml b/core/installer/values-tmpl/certificate-issuer-private.yaml
deleted file mode 100644
index 8654be5..0000000
--- a/core/installer/values-tmpl/certificate-issuer-private.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: certificate-issuer-private
-  namespace: {{ .Global.Id }}-ingress-private
-spec:
-  dependsOn:
-  - name: ingress-private
-    namespace: {{ .Global.Id }}-ingress-private
-  chart:
-    spec:
-      chart: charts/certificate-issuer-private
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    issuer:
-      name: {{ .Global.Id }}-private
-      server: https://acme-v02.api.letsencrypt.org/directory
-      # server: https://acme-staging-v02.api.letsencrypt.org/directory
-      domain: {{ .Global.PrivateDomain }}
-      contactEmail: {{ .Global.ContactEmail }}
-      gandiAPIToken: {{ .Values.GandiAPIToken }}
-    apiConfigMap:
-      name: {{ .Values.APIConfigMap.Name }}
-      namespace: {{ .Values.APIConfigMap.Namespace }}
diff --git a/core/installer/values-tmpl/certificate-issuer-public.cue b/core/installer/values-tmpl/certificate-issuer-public.cue
new file mode 100644
index 0000000..8ad81f9
--- /dev/null
+++ b/core/installer/values-tmpl/certificate-issuer-public.cue
@@ -0,0 +1,34 @@
+input: {}
+
+images: {}
+
+charts: {
+	"certificate-issuer-public": {
+		chart: "charts/certificate-issuer-public"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	"certificate-issuer-public": {
+		chart: charts["certificate-issuer-public"]
+		dependsOnExternal: [{
+			name: "ingress-nginx"
+			namespace: "\(global.namespacePrefix)ingress-private"
+		}]
+		values: {
+			issuer: {
+				name: _issuerPublic
+				server: "https://acme-v02.api.letsencrypt.org/directory"
+				// server: "https://acme-staging-v02.api.letsencrypt.org/directory"
+				domain: global.domain
+				contactEmail: global.contactEmail
+				ingressClass: _ingressPublic
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/certificate-issuer-public.jsonschema b/core/installer/values-tmpl/certificate-issuer-public.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/certificate-issuer-public.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/certificate-issuer-public.md b/core/installer/values-tmpl/certificate-issuer-public.md
deleted file mode 100644
index 961c3c3..0000000
--- a/core/installer/values-tmpl/certificate-issuer-public.md
+++ /dev/null
@@ -1 +0,0 @@
-Install HTTP01 based certificate issuer for public domain
diff --git a/core/installer/values-tmpl/certificate-issuer-public.yaml b/core/installer/values-tmpl/certificate-issuer-public.yaml
deleted file mode 100644
index bcf0079..0000000
--- a/core/installer/values-tmpl/certificate-issuer-public.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: certificate-issuer-public
-  # TODO(giolekva): is there better namespace for this?
-  namespace: {{ .Global.Id }}-ingress-private
-spec:
-  dependsOn:
-  - name: ingress-private
-    namespace: {{ .Global.Id }}-ingress-private
-  chart:
-    spec:
-      chart: charts/certificate-issuer-public
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    issuer:
-      name: {{ .Global.Id }}-public
-      server: https://acme-v02.api.letsencrypt.org/directory
-      # server: https://acme-staging-v02.api.letsencrypt.org/directory
-      domain: {{ .Global.Domain }}
-      contactEmail: {{ .Global.ContactEmail }}
-      ingressClass: {{ .Global.PCloudEnvName }}-ingress-public
diff --git a/core/installer/values-tmpl/config-repo.cue b/core/installer/values-tmpl/config-repo.cue
new file mode 100644
index 0000000..3f0b432
--- /dev/null
+++ b/core/installer/values-tmpl/config-repo.cue
@@ -0,0 +1,47 @@
+input: {
+	privateKey: string
+	publicKey: string
+	adminKey: string
+}
+
+images: {
+	softserve: {
+		repository: "charmcli"
+		name: "soft-serve"
+		tag: "v0.7.1"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	softserve: {
+		chart: "charts/soft-serve"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	softserve: {
+		chart: charts.softserve
+		values: {
+			serviceType: "ClusterIP"
+			addressPool: ""
+			reservedIP: ""
+			adminKey: input.adminKey
+			privateKey: input.privateKey
+			publicKey: input.publicKey
+			ingress: {
+				enabled: false
+			}
+			image: {
+				repository: images.softserve.fullName
+				tag: images.softserve.tag
+				pullPolicy: images.softserve.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/core-auth-storage.yaml b/core/installer/values-tmpl/core-auth-storage.yaml
deleted file mode 100644
index 8ae3b71..0000000
--- a/core/installer/values-tmpl/core-auth-storage.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: core-auth-storage
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/postgresql
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    fullnameOverride: postgres
-    image:
-      repository: library/postgres  # arm64v8/postgres
-      tag: 15.3
-    service:
-      type: ClusterIP
-      port: 5432
-    primary:
-      initdb:
-        scripts:
-          init.sql: |
-            CREATE USER kratos WITH PASSWORD 'kratos';
-            CREATE USER hydra WITH PASSWORD 'hydra';
-            CREATE DATABASE kratos WITH OWNER = kratos;
-            CREATE DATABASE hydra WITH OWNER = hydra;
-      persistence:
-        size: 1Gi
-      securityContext:
-        enabled: true
-        fsGroup: 0
-      containerSecurityContext:
-        enabled: true
-        runAsUser: 0
-    volumePermissions:
-      securityContext:
-        runAsUser: 0
diff --git a/core/installer/values-tmpl/core-auth.cue b/core/installer/values-tmpl/core-auth.cue
new file mode 100644
index 0000000..38e6a47
--- /dev/null
+++ b/core/installer/values-tmpl/core-auth.cue
@@ -0,0 +1,480 @@
+input: {
+	subdomain: string
+}
+
+userSchema: ###"""
+{
+  "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "User",
+  "type": "object",
+  "properties": {
+	"traits": {
+	  "type": "object",
+	  "properties": {
+		"username": {
+		  "type": "string",
+		  "format": "username",
+		  "title": "Username",
+		  "minLength": 3,
+		  "ory.sh/kratos": {
+			"credentials": {
+			  "password": {
+				"identifier": true
+			  }
+			}
+		  }
+		}
+	  },
+	  "additionalProperties": false
+	}
+  }
+}
+"""###
+
+images: {
+	kratos: {
+		repository: "oryd"
+		name: "kratos"
+		tag: "v0.13.0"
+		pullPolicy: "IfNotPresent"
+	}
+	hydra: {
+		repository: "oryd"
+		name: "hydra"
+		tag: "v2.1.2"
+		pullPolicy: "IfNotPresent"
+	}
+	"hydra-maester": {
+		repository: "giolekva"
+		name: "ory-hydra-maester"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+	ui: {
+		repository: "giolekva"
+		name: "auth-ui"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+	postgres: {
+		repository: "library"
+		name: "postgres"
+		tag: "15.3"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	auth: {
+		chart: "charts/auth"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+	postgres: {
+		chart: "charts/postgresql"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	postgres: {
+		chart: charts.postgres
+		values: {
+			fullnameOverride: "postgres"
+			image: {
+				registry: images.postgres.registry
+				repository: images.postgres.imageName
+				tag: images.postgres.tag
+				pullPolicy: images.postgres.pullPolicy
+			}
+			service: {
+				type: "ClusterIP"
+				port: 5432
+			}
+			primary: {
+				initdb: {
+					scripts: {
+						"init.sql": """
+						CREATE USER kratos WITH PASSWORD 'kratos';
+						CREATE USER hydra WITH PASSWORD 'hydra';
+						CREATE DATABASE kratos WITH OWNER = kratos;
+						CREATE DATABASE hydra WITH OWNER = hydra;
+						"""
+					}
+				}
+				persistence: {
+					size: "1Gi"
+				}
+				securityContext: {
+					enabled: true
+					fsGroup: 0
+				}
+				containerSecurityContext: {
+					enabled: true
+					runAsUser: 0
+				}
+			}
+			volumePermissions: {
+				securityContext: {
+					runAsUser: 0
+				}
+			}
+		}
+	}
+	auth: {
+		chart: charts.auth
+		dependsOn: [postgres]
+		dependsOnExternal: [{
+			name: "ingress-nginx"
+			namespace: "\(global.namespacePrefix)ingress-private"
+		}]
+		values: {
+			kratos: {
+				fullnameOverride: "kratos"
+				image: {
+					repository: images.kratos.fullName
+					tag: images.kratos.tag
+					pullPolicy: images.kratos.pullPolicy
+				}
+				service: {
+					admin: {
+						enabled: true
+						type: "ClusterIP"
+						port: 80
+						name: "http"
+					}
+					public: {
+						enabled: true
+						type: "ClusterIP"
+						port: 80
+						name: "http"
+					}
+				}
+				ingress: {
+					admin: {
+						enabled: true
+						className: _ingressPrivate
+						hosts: [{
+							host: "kratos.\(global.privateDomain)"
+							paths: [{
+								path: "/"
+								pathType: "Prefix"
+							}]
+						}]
+						tls: [{
+							hosts: [
+								"kratos.\(global.privateDomain)"
+						]
+						}]
+					}
+					public: {
+						enabled: true
+						className: _ingressPublic
+						annotations: {
+							"acme.cert-manager.io/http01-edit-in-place": "true"
+							"cert-manager.io/cluster-issuer": _issuerPublic
+						}
+						hosts: [{
+							host: "accounts.\(global.domain)"
+							paths: [{
+								path: "/"
+								pathType: "Prefix"
+							}]
+						}]
+						tls: [{
+							hosts: ["accounts.\(global.domain)"]
+							secretName: "cert-accounts.\(global.domain)"
+						}]
+					}
+				}
+				secret: {
+					enabled: true
+				}
+				kratos: {
+					automigration: {
+						enabled: true
+					}
+					development: false
+					courier: {
+						enabled: false
+					}
+					config: {
+						version: "v0.7.1-alpha.1"
+						dsn: "postgres://kratos:kratos@postgres.\(global.namespacePrefix)core-auth.svc:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4"
+						serve: {
+							public: {
+								base_url: "https://accounts.\(global.domain)"
+								cors: {
+									enabled: true
+									debug: false
+									allow_credentials: true
+									allowed_origins: [
+										"https://\(global.domain)",
+										"https://*.\(global.domain)",
+								]
+								}
+							}
+							admin: {
+								base_url: "https://kratos.\(global.privateDomain)/"
+							}
+						}
+						selfservice: {
+							default_browser_return_url: "https://accounts-ui.\(global.domain)"
+							methods: {
+								password: {
+									enabled: true
+								}
+							}
+							flows: {
+								error: {
+									ui_url: "https://accounts-ui.\(global.domain)/error"
+								}
+								settings: {
+									ui_url: "https://accounts-ui.\(global.domain)/settings"
+									privileged_session_max_age: "15m"
+								}
+								recovery: {
+									enabled: false
+								}
+								verification: {
+									enabled: false
+								}
+								logout: {
+									after: {
+										default_browser_return_url: "https://accounts-ui.\(global.domain)/login"
+									}
+								}
+								login: {
+									ui_url: "https://accounts-ui.\(global.domain)/login"
+									lifespan: "10m"
+									after: {
+										password: {
+											default_browser_return_url: "https://accounts-ui.\(global.domain)/"
+										}
+									}
+								}
+								registration: {
+									lifespan: "10m"
+									ui_url: "https://accounts-ui.\(global.domain)/register"
+									after: {
+										password: {
+											hooks: [{
+												hook: "session"
+											}]
+											default_browser_return_url: "https://accounts-ui.\(global.domain)/"
+										}
+									}
+								}
+							}
+						}
+						log: {
+							level: "debug"
+							format: "text"
+							leak_sensitive_values: true
+						}
+						cookies: {
+							path: "/"
+							same_site: "None"
+							domain: global.domain
+						}
+						secrets: {
+							cookie: ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"]
+						}
+						hashers: {
+							argon2: {
+								parallelism: 1
+								memory: "128MB"
+								iterations: 2
+								salt_length: 16
+								key_length: 16
+								}
+						}
+						identity: {
+							schemas: [{
+								id: "user"
+								url: "file:///etc/config/identity.schema.json"
+							}]
+							default_schema_id: "user"
+						}
+						courier: {
+							smtp: {
+								connection_uri: "smtps://test-z1VmkYfYPjgdPRgPFgmeZ31esT9rUgS%40\(global.domain):iW%213Kk%5EPPLFrZa%24%21bbpTPN9Wv3b8mvwS6ZJvMLtce%23A2%2A4MotD@mx1.\(global.domain)"
+							}
+						}
+					}
+					identitySchemas: {
+                        "identity.schema.json": userSchema
+					}
+				}
+			}
+			hydra: {
+				fullnameOverride: "hydra"
+				image: {
+					repository: images.hydra.fullName
+					tag: images.hydra.tag
+					pullPolicy: images.hydra.pullPolicy
+				}
+				service: {
+					admin: {
+						enabled: true
+						type: "ClusterIP"
+						port: 80
+						name: "http"
+					}
+					public: {
+						enabled: true
+						type: "ClusterIP"
+						port: 80
+						name: "http"
+					}
+				}
+				ingress: {
+					admin: {
+						enabled: true
+						className: _ingressPrivate
+						hosts: [{
+							host: "hydra.\(global.privateDomain)"
+							paths: [{
+								path: "/"
+								pathType: "Prefix"
+							}]
+							   }]
+						tls: [{
+							hosts: ["hydra.\(global.privateDomain)"]
+						}]
+					}
+					public: {
+						enabled: true
+						className: _ingressPublic
+						annotations: {
+							"acme.cert-manager.io/http01-edit-in-place": "true"
+							"cert-manager.io/cluster-issuer": _issuerPublic
+						}
+						hosts: [{
+							host: "hydra.\(global.domain)"
+							paths: [{
+								path: "/"
+								pathType: "Prefix"
+							}]
+						}]
+						tls: [{
+							hosts: ["hydra.\(global.domain)"]
+							secretName: "cert-hydra.\(global.domain)"
+						}]
+					}
+				}
+				secret: {
+					enabled: true
+				}
+				maester: {
+					enabled: true
+				}
+				"hydra-maester": {
+					adminService: {
+						name: "hydra-admin"
+						port: 80
+					}
+					image: {
+						repository: images["hydra-maester"].fullName
+						tag: images["hydra-maester"].tag
+						pullPolicy: images["hydra-maester"].pullPolicy
+					}
+				}
+				hydra: {
+					automigration: {
+						enabled: true
+					}
+					config: {
+						version: "v1.10.6"
+						dsn: "postgres://hydra:hydra@postgres.\(global.namespacePrefix)core-auth.svc:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4"
+						serve: {
+							cookies: {
+								same_site_mode: "None"
+							}
+							public: {
+								cors: {
+									enabled: true
+									debug: false
+									allow_credentials: true
+									allowed_origins: [
+										"https://\(global.domain)",
+										"https://*.\(global.domain)"
+								]
+								}
+							}
+							admin: {
+								cors: {
+									allowed_origins: [
+										"https://hydra.\(global.privateDomain)"
+								]
+								}
+								tls: {
+									allow_termination_from: [
+										"0.0.0.0/0",
+										"10.42.0.0/16",
+										"10.43.0.0/16",
+								]
+								}
+							}
+							tls: {
+								allow_termination_from: [
+									"0.0.0.0/0",
+									"10.42.0.0/16",
+									"10.43.0.0/16",
+							]
+							}
+						}
+						urls: {
+							self: {
+								public: "https://hydra.\(global.domain)"
+								issuer: "https://hydra.\(global.domain)"
+							}
+							consent: "https://accounts-ui.\(global.domain)/consent"
+							login: "https://accounts-ui.\(global.domain)/login"
+							logout: "https://accounts-ui.\(global.domain)/logout"
+						}
+						secrets: {
+							system: ["youReallyNeedToChangeThis"]
+						}
+						oidc: {
+							subject_identifiers: {
+								supported_types: [
+									"pairwise",
+									"public",
+							]
+								pairwise: {
+									salt: "youReallyNeedToChangeThis"
+								}
+							}
+						}
+						log: {
+							level: "trace"
+							leak_sensitive_values: false
+						}
+					}
+				}
+			}
+			ui: {
+				certificateIssuer: _issuerPublic
+				ingressClassName: _ingressPublic
+				domain: global.domain
+				internalDomain: global.privateDomain
+				hydra: "hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
+				enableRegistration: false
+				image: {
+					repository: images.ui.fullName
+					tag: images.ui.tag
+					pullPolicy: images.ui.pullPolicy
+				}
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/core-auth.jsonschema b/core/installer/values-tmpl/core-auth.jsonschema
deleted file mode 100644
index a7ccc8d..0000000
--- a/core/installer/values-tmpl/core-auth.jsonschema
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Subdomain": { "type": "string" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/core-auth.md b/core/installer/values-tmpl/core-auth.md
deleted file mode 100644
index ed24bca..0000000
--- a/core/installer/values-tmpl/core-auth.md
+++ /dev/null
@@ -1 +0,0 @@
-OpenID Connect base Single Sign On solution
diff --git a/core/installer/values-tmpl/core-auth.yaml b/core/installer/values-tmpl/core-auth.yaml
deleted file mode 100644
index 26af983..0000000
--- a/core/installer/values-tmpl/core-auth.yaml
+++ /dev/null
@@ -1,299 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: core-auth
-  namespace: {{ .Release.Namespace }}
-spec:
-  dependsOn:
-  - name: core-auth-storage
-    namespace: {{ .Release.Namespace }}
-  - name: ingress-private # TODO(giolekva): is this needed?
-    namespace: {{ .Global.Id }}-ingress-private
-  chart:
-    spec:
-      chart: charts/auth
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    kratos:
-      fullnameOverride: kratos
-      image:
-        repository: oryd/kratos
-        tag: v0.13.0
-        pullPolicy: IfNotPresent
-      service:
-        admin:
-          enabled: true
-          type: ClusterIP
-          port: 80
-          name: http
-        public:
-          enabled: true
-          type: ClusterIP
-          port: 80
-          name: http
-      ingress:
-        admin:
-          enabled: true
-          className: {{ .Global.Id }}-ingress-private
-          hosts:
-          - host: kratos.p.{{ .Global.Domain }}
-            paths:
-            - path: /
-              pathType: Prefix
-          tls:
-          - hosts:
-            - kratos.p.{{ .Global.Domain }}
-        public:
-          enabled: true
-          className: {{ .Global.PCloudEnvName }}-ingress-public
-          annotations:
-            acme.cert-manager.io/http01-edit-in-place: "true"
-            cert-manager.io/cluster-issuer: {{ .Global.Id }}-public
-          hosts:
-          - host: accounts.{{ .Global.Domain }}
-            paths:
-            - path: /
-              pathType: Prefix
-          tls:
-          - hosts:
-            - accounts.{{ .Global.Domain }}
-            secretName: cert-accounts.{{ .Global.Domain }}
-      secret:
-        enabled: true
-      kratos:
-        automigration:
-          enabled: true
-        development: false
-        courier:
-          enabled: false
-        config:
-          version: v0.7.1-alpha.1
-          dsn: postgres://kratos:kratos@postgres.{{ .Global.Id }}-core-auth.svc:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
-          serve:
-            public:
-              base_url: https://accounts.{{ .Global.Domain }}
-              cors:
-                enabled: true
-                debug: false
-                allow_credentials: true
-                allowed_origins:
-                - https://{{ .Global.Domain }}
-                - https://*.{{ .Global.Domain }}
-            admin:
-              base_url: https://kratos.p.{{ .Global.Domain }}/
-          selfservice:
-            default_browser_return_url: https://accounts-ui.{{ .Global.Domain }}
-            # whitelisted_return_urls:
-            #   - https://accounts-ui.{{ .Global.Domain }}
-            methods:
-              password:
-                enabled: true
-            flows:
-              error:
-                ui_url: https://accounts-ui.{{ .Global.Domain }}/error
-              settings:
-                ui_url: https://accounts-ui.{{ .Global.Domain }}/settings
-                privileged_session_max_age: 15m
-              recovery:
-                enabled: false
-              verification:
-                enabled: false
-              logout:
-                after:
-                  default_browser_return_url: https://accounts-ui.{{ .Global.Domain }}/login
-              login:
-                ui_url: https://accounts-ui.{{ .Global.Domain }}/login
-                lifespan: 10m
-                after:
-                  password:
-                    default_browser_return_url: https://accounts-ui.{{ .Global.Domain }}/
-              registration:
-                lifespan: 10m
-                ui_url: https://accounts-ui.{{ .Global.Domain }}/register
-                after:
-                  password:
-                    hooks:
-                      -
-                        hook: session
-                    default_browser_return_url: https://accounts-ui.{{ .Global.Domain }}/
-          log:
-            level: debug
-            format: text
-            leak_sensitive_values: true
-          cookies:
-            path: /
-            same_site: None
-            domain: {{ .Global.Domain }}
-          secrets:
-            cookie:
-              - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
-            # cipher:
-            #   - 32-LONG-SECRET-NOT-SECURE-AT-ALL
-          # ciphers:
-          #   algorithm: xchacha20-poly1305
-          hashers:
-            argon2:
-              parallelism: 1
-              memory: 128MB
-              iterations: 2
-              salt_length: 16
-              key_length: 16
-          identity:
-            schemas:
-            - id: user
-              url: file:///etc/config/identity.schema.json
-            default_schema_id: user
-          courier:
-            smtp:
-              connection_uri: smtps://test-z1VmkYfYPjgdPRgPFgmeZ31esT9rUgS%40{{ .Global.Domain }}:iW%213Kk%5EPPLFrZa%24%21bbpTPN9Wv3b8mvwS6ZJvMLtce%23A2%2A4MotD@mx1.{{ .Global.Domain }}
-        identitySchemas:
-          "identity.schema.json": |
-            {
-              "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
-              "$schema": "http://json-schema.org/draft-07/schema#",
-              "title": "User",
-              "type": "object",
-              "properties": {
-                "traits": {
-                  "type": "object",
-                  "properties": {
-                    "username": {
-                      "type": "string",
-                      "format": "username",
-                      "title": "Username",
-                      "minLength": 3,
-                      "ory.sh/kratos": {
-                        "credentials": {
-                          "password": {
-                            "identifier": true
-                          }
-                        }
-                      }
-                    }
-                  },
-                  "additionalProperties": false
-                }
-              }
-            }
-    hydra:
-      fullnameOverride: hydra
-      image:
-        repository: oryd/hydra
-        tag: v2.1.2
-        pullPolicy: IfNotPresent
-        # repository: giolekva/ory-hydra
-        # tag: latest
-        # pullPolicy: Always
-      service:
-        admin:
-          enabled: true
-          type: ClusterIP
-          port: 80
-          name: http
-        public:
-          enabled: true
-          type: ClusterIP
-          port: 80
-          name: http
-      ingress:
-        admin:
-          enabled: true
-          className: {{ .Global.Id }}-ingress-private
-          hosts:
-          - host: hydra.p.{{ .Global.Domain }}
-            paths:
-            - path: /
-              pathType: Prefix
-          tls:
-          - hosts:
-            - hydra.p.{{ .Global.Domain }}
-        public:
-          enabled: true
-          className: {{ .Global.PCloudEnvName }}-ingress-public
-          annotations:
-            acme.cert-manager.io/http01-edit-in-place: "true"
-            cert-manager.io/cluster-issuer: {{ .Global.Id }}-public
-          hosts:
-          - host: hydra.{{ .Global.Domain }}
-            paths:
-            - path: /
-              pathType: Prefix
-          tls:
-          - hosts:
-            - hydra.{{ .Global.Domain }}
-            secretName: cert-hydra.{{ .Global.Domain }}
-      secret:
-        enabled: true
-      maester:
-        enabled: true
-      hydra-maester:
-        adminService:
-          name: hydra-admin
-          port: 80
-        image:
-          repository: giolekva/ory-hydra-maester
-          tag: latest
-          pullPolicy: IfNotPresent
-      hydra:
-        automigration:
-          enabled: true
-        config:
-          version: v1.10.6
-          dsn: postgres://hydra:hydra@postgres.{{ .Global.Id }}-core-auth.svc:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
-          serve:
-            cookies:
-              same_site_mode: None
-            public:
-              cors:
-                enabled: true
-                debug: false
-                allow_credentials: true
-                allowed_origins:
-                  - https://{{ .Global.Domain }}
-                  - https://*.{{ .Global.Domain }}
-            admin:
-              # host: localhost
-              cors:
-                allowed_origins:
-                  - https://hydra.p.{{ .Global.Domain }}
-              tls:
-                allow_termination_from:
-                  - 0.0.0.0/0
-                  - 10.42.0.0/16
-                  - 10.43.0.0/16
-            tls:
-              allow_termination_from:
-                - 0.0.0.0/0
-                - 10.42.0.0/16
-                - 10.43.0.0/16
-          urls:
-            self:
-              public: https://hydra.{{ .Global.Domain }}
-              issuer: https://hydra.{{ .Global.Domain }}
-            consent: https://accounts-ui.{{ .Global.Domain }}/consent
-            login: https://accounts-ui.{{ .Global.Domain }}/login
-            logout: https://accounts-ui.{{ .Global.Domain }}/logout
-          secrets:
-            system:
-              - youReallyNeedToChangeThis
-          oidc:
-            subject_identifiers:
-              supported_types:
-                - pairwise
-                - public
-              pairwise:
-                salt: youReallyNeedToChangeThis
-          log:
-            level: trace
-            leak_sensitive_values: false
-    ui:
-      certificateIssuer: {{ .Global.Id }}-public
-      ingressClassName: {{ .Global.PCloudEnvName }}-ingress-public
-      domain: {{ .Global.Domain }}
-      internalDomain: p.{{ .Global.Domain }}
-      hydra: hydra-admin.{{ .Global.Id }}-core-auth.svc.cluster.local
-      enableRegistration: false
diff --git a/core/installer/values-tmpl/coredns-keys.yaml b/core/installer/values-tmpl/coredns-keys.yaml
deleted file mode 100644
index a605deb..0000000
--- a/core/installer/values-tmpl/coredns-keys.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
-# apiVersion: v1
-# kind: PersistentVolumeClaim
-# metadata:
-#   name: keys
-#   namespace: dodo-core-coredns
-# spec:
-#   storageClassName: ""
-#   accessModes:
-#     - ReadWriteMany
-#   resources:
-#     requests:
-#       storage: 1Gi
-#   volumeName: keys
----
-apiVersion: v1
-kind: Pod
-metadata:
-  name: keys
-  namespace: dodo-core-coredns
-spec:
-  containers:
-  - name: keys
-    image: busybox:1.36.1
-    command: ["sleep", "infinity"]
-    volumeMounts:
-    - name: dodo
-      mountPath: /etc/dodo
-    - name: dodo-config
-      mountPath: /etc/dodo-config
-  volumes:
-  - name: dodo
-    persistentVolumeClaim:
-      claimName: keys
-  - name: dodo-config
-    configMap:
-      name: dodo-dns
-
diff --git a/core/installer/values-tmpl/coredns.yaml b/core/installer/values-tmpl/coredns.yaml
deleted file mode 100644
index cfd57e6..0000000
--- a/core/installer/values-tmpl/coredns.yaml
+++ /dev/null
@@ -1,85 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: coredns
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/coredns
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
-    image:
-      repository: coredns/coredns
-      tag: 1.11.1
-      pullPolicy: IfNotPresent
-    replicaCount: 1
-    resources:
-      limits:
-        cpu: 100m
-        memory: 128Mi
-      requests:
-        cpu: 100m
-        memory: 128Mi
-    rollingUpdate:
-      maxUnavailable: 1
-      maxSurge: 25%
-    terminationGracePeriodSeconds: 30
-    serviceType: "ClusterIP"
-    service:
-      name: coredns
-    serviceAccount:
-      create: false
-    rbac:
-      create: true
-      pspEnable: false
-    isClusterService: true
-    securityContext:
-      capabilities:
-        add:
-          - NET_BIND_SERVICE
-    servers:
-    - zones:
-      - zone: .
-      port: 53
-      plugins:
-      - name: log
-      - name: health
-        configBlock: |-
-          lameduck 5s
-      - name: ready
-    extraConfig:
-      import:
-        parameters: {{ .Values.Volume.MountPath }}/coredns.conf
-    extraVolumes:
-    - name: zone-configs
-      persistentVolumeClaim:
-        claimName: {{ .Values.Volume.ClaimName }}
-    extraVolumeMounts:
-    - name: zone-configs
-      mountPath: {{ .Values.Volume.MountPath}}
-    livenessProbe:
-      enabled: true
-      initialDelaySeconds: 60
-      periodSeconds: 10
-      timeoutSeconds: 5
-      failureThreshold: 5
-      successThreshold: 1
-    readinessProbe:
-      enabled: true
-      initialDelaySeconds: 30
-      periodSeconds: 10
-      timeoutSeconds: 5
-      failureThreshold: 5
-      successThreshold: 1
-    zoneFiles: []
-    hpa:
-      enabled: false
-    autoscaler:
-      enabled: false
-    deployment:
-      enabled: true
diff --git a/core/installer/values-tmpl/csi-driver-smb.cue b/core/installer/values-tmpl/csi-driver-smb.cue
new file mode 100644
index 0000000..ef0f530
--- /dev/null
+++ b/core/installer/values-tmpl/csi-driver-smb.cue
@@ -0,0 +1,67 @@
+input: {}
+
+_baseImage: {
+	registry: "registry.k8s.io"
+	repository: "sig-storage"
+	pullPolicy: "IfNotPresent"
+}
+
+images: {
+	smb: _baseImage & {
+		name: "smbplugin"
+		tag: "v1.11.0"
+	}
+	csiProvisioner: _baseImage & {
+		name: "csi-provisioner"
+		tag: "v3.5.0"
+	}
+	livenessProbe: _baseImage & {
+		name: "livenessprobe"
+		tag: "v2.10.0"
+	}
+	nodeDriverRegistrar: _baseImage & {
+		name: "csi-node-driver-registrar"
+		tag: "v2.8.0"
+	}
+}
+
+charts: {
+	csiDriverSMB: {
+		chart: "charts/csi-driver-smb"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"csi-driver-smb": {
+		chart: charts.csiDriverSMB
+		values: {
+			image: {
+				smb: {
+					repository: images.smb.fullName
+					tag: images.smb.tag
+					pullPolicy: images.smb.pullPolicy
+				}
+				csiProvisioner: {
+					repository: images.csiProvisioner.fullName
+					tag: images.csiProvisioner.tag
+					pullPolicy: images.csiProvisioner.pullPolicy
+				}
+				livenessProbe: {
+					repository: images.livenessProbe.fullName
+					tag: images.livenessProbe.tag
+					pullPolicy: images.livenessProbe.pullPolicy
+				}
+				nodeDriverRegistrar: {
+					repository: images.nodeDriverRegistrar.fullName
+					tag: images.nodeDriverRegistrar.tag
+					pullPolicy: images.nodeDriverRegistrar.pullPolicy
+				}
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/csi-driver-smb.jsonschema b/core/installer/values-tmpl/csi-driver-smb.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/csi-driver-smb.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/csi-driver-smb.md b/core/installer/values-tmpl/csi-driver-smb.md
deleted file mode 100644
index 171a000..0000000
--- a/core/installer/values-tmpl/csi-driver-smb.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs iCSI SMB driver
diff --git a/core/installer/values-tmpl/csi-driver-smb.yaml b/core/installer/values-tmpl/csi-driver-smb.yaml
deleted file mode 100644
index 9297d0f..0000000
--- a/core/installer/values-tmpl/csi-driver-smb.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: csi-driver-smb
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/csi-driver-smb
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  timeout: 20m0s
-  values:
diff --git a/core/installer/values-tmpl/dns-challenge-resolver.yaml b/core/installer/values-tmpl/dns-challenge-resolver.yaml
deleted file mode 100644
index ddabaa8..0000000
--- a/core/installer/values-tmpl/dns-challenge-resolver.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-certManager:
-  namespace: dodo-cert-manager
-  serviceAccountName: dodo-cert-manager
diff --git a/core/installer/values-tmpl/dns-zone-controller.jsonschema b/core/installer/values-tmpl/dns-zone-controller.jsonschema
deleted file mode 100644
index 4c67ac3..0000000
--- a/core/installer/values-tmpl/dns-zone-controller.jsonschema
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Volume": {
-      "type": "object",
-	  "properties": {
-		"ClaimName": { "type": "string" },
-		"MountPath": { "type": "string" }
-	  },
-	  "additionalProperties": false
-	},
-	"APIConfigMapName": { "type": "string" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/dns-zone-controller.md b/core/installer/values-tmpl/dns-zone-controller.md
deleted file mode 100644
index a6abe91..0000000
--- a/core/installer/values-tmpl/dns-zone-controller.md
+++ /dev/null
@@ -1 +0,0 @@
-Sets up DNS zone controller to automatically generate zone files of registered domains.
diff --git a/core/installer/values-tmpl/dns-zone-controller.yaml b/core/installer/values-tmpl/dns-zone-controller.yaml
deleted file mode 100644
index 51fe1e3..0000000
--- a/core/installer/values-tmpl/dns-zone-controller.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: dns-zone-controller
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/dns-ns-controller
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
-    image:
-      repository: giolekva/dns-ns-controller
-      tag: latest
-      pullPolicy: Always
-    installCRDs: true
-    volume:
-      claimName: {{ .Values.Volume.ClaimName }}
-      mountPath: {{ .Values.Volume.MountPath }}
-    apiConfigMapName: {{ .Values.APIConfigMapName }}
diff --git a/core/installer/values-tmpl/dns-zone-manager.cue b/core/installer/values-tmpl/dns-zone-manager.cue
new file mode 100644
index 0000000..4d977d0
--- /dev/null
+++ b/core/installer/values-tmpl/dns-zone-manager.cue
@@ -0,0 +1,175 @@
+input: {
+	apiConfigMapName: string
+	volume: {
+		size: string
+		claimName: string
+		mountPath: string
+	}
+}
+
+images: {
+	dnsZoneController: {
+		repository: "giolekva"
+		name: "dns-ns-controller"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+	kubeRBACProxy: {
+		registry: "gcr.io"
+		repository: "kubebuilder"
+		name: "kube-rbac-proxy"
+		tag: "v0.13.0"
+		pullPolicy: "IfNotPresent"
+	}
+	coredns: {
+		repository: "coredns"
+		name: "coredns"
+		tag: "1.11.1"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	volume: {
+		chart: "charts/volumes"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+	dnsZoneController: {
+		chart: "charts/dns-ns-controller"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+	coredns: {
+		chart: "charts/coredns"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+_volumeName: "zone-configs"
+
+helm: {
+	volume: {
+		chart: charts.volume
+		values: {
+			name: input.volume.claimName
+			size: input.volume.size
+			accessMode: "ReadWriteMany"
+		}
+	}
+	"dns-zone-controller": {
+		chart: charts.dnsZoneController
+		values: {
+			installCRDs: true
+			apiConfigMapName: input.apiConfigMapName
+			volume: {
+				claimName: input.volume.claimName
+				mountPath: input.volume.mountPath
+			}
+			image: {
+				repository: images.dnsZoneController.fullName
+				tag: images.dnsZoneController.tag
+				pullPolicy: images.dnsZoneController.pullPolicy
+			}
+			kubeRBACProxy: {
+				image: {
+					repository: images.kubeRBACProxy.fullName
+					tag: images.kubeRBACProxy.tag
+					pullPolicy: images.kubeRBACProxy.pullPolicy
+				}
+			}
+		}
+	}
+	coredns: {
+		chart: charts.coredns
+		values: {
+			image: {
+				repository: images.coredns.fullName
+				tag: images.coredns.tag
+				pullPolicy: images.coredns.pullPolicy
+			}
+			replicaCount: 1
+			resources: {
+				limits: {
+					cpu: "100m"
+					memory: "128Mi"
+				}
+				requests: {
+					cpu: "100m"
+					memory: "128Mi"
+				}
+			}
+			rollingUpdate: {
+				maxUnavailable: 1
+				maxSurge: "25%"
+			}
+			terminationGracePeriodSeconds: 30
+			serviceType: "ClusterIP"
+			service: name: "coredns"
+			serviceAccount: create: false
+			rbac: {
+				create: true
+				pspEnable: false
+			}
+			isClusterService: true
+			securityContext: capabilities: add: ["NET_BIND_SERVICE"]
+			servers: [{
+				zones: [{
+					zone: "."
+				}]
+				port: 53
+				plugins: [
+					{
+						name: "log"
+					},
+					{
+						name: "health"
+						configBlock: "lameduck 5s"
+					},
+					{
+						name: "ready"
+					}
+			]
+			}]
+			extraConfig: import: parameters: "\(input.volume.mountPath)/coredns.conf"
+			extraVolumes: [{
+				name: _volumeName
+				persistentVolumeClaim: claimName: input.volume.claimName
+			}]
+			extraVolumeMounts: [{
+				name: _volumeName
+				mountPath: input.volume.mountPath
+			}]
+			livenessProbe: {
+				enabled: true
+				initialDelaySeconds: 60
+				periodSeconds: 10
+				timeoutSeconds: 5
+				failureThreshold: 5
+				successThreshold: 1
+			}
+			readinessProbe: {
+				enabled: true
+				initialDelaySeconds: 30
+				periodSeconds: 10
+				timeoutSeconds: 5
+				failureThreshold: 5
+				successThreshold: 1
+			}
+			zoneFiles: []
+			hpa: enabled: false
+			autoscaler: enabled: false
+			deployment: enabled: true
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/dns-zone-storage.yaml b/core/installer/values-tmpl/dns-zone-storage.yaml
deleted file mode 100644
index 72b7848..0000000
--- a/core/installer/values-tmpl/dns-zone-storage.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: dns-zone-storage
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/volumes
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 10m0s
-  values:
-    name: {{ .Values.Volume.ClaimName }}
-    size: {{ .Values.Volume.Size }}
-    accessMode: ReadWriteMany
diff --git a/core/installer/values-tmpl/env-manager.cue b/core/installer/values-tmpl/env-manager.cue
new file mode 100644
index 0000000..e34c546
--- /dev/null
+++ b/core/installer/values-tmpl/env-manager.cue
@@ -0,0 +1,48 @@
+import (
+	"encoding/base64"
+)
+
+input: {
+	repoIP: string
+	repoPort: number
+	repoName: string
+	sshPrivateKey: string
+}
+
+images: {
+	envManager: {
+		repository: "giolekva"
+		name: "pcloud-installer"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	envManager: {
+		chart: "charts/env-manager"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"env-manager": {
+		chart: charts.envManager
+		values: {
+			repoIP: input.repoIP
+			repoPort: input.repoPort
+			repoName: input.repoName
+			sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
+			clusterRoleName: "\(global.pcloudEnvName)-env-manager"
+			image: {
+				repository: images.envManager.fullName
+				tag: images.envManager.tag
+				pullPolicy: images.envManager.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/env-manager.jsonschema b/core/installer/values-tmpl/env-manager.jsonschema
deleted file mode 100644
index aa2f01d..0000000
--- a/core/installer/values-tmpl/env-manager.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "RepoIP": { "type": "string", "default": "192.168.0.11" },
-	"SSHPrivateKey": { "type": "string", "default": "foo bar" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/env-manager.md b/core/installer/values-tmpl/env-manager.md
deleted file mode 100644
index ec69eba..0000000
--- a/core/installer/values-tmpl/env-manager.md
+++ /dev/null
@@ -1 +0,0 @@
-PCloud environment manager
diff --git a/core/installer/values-tmpl/env-manager.yaml b/core/installer/values-tmpl/env-manager.yaml
deleted file mode 100644
index 7271dff..0000000
--- a/core/installer/values-tmpl/env-manager.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: env-manager
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/env-manager
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
-    repoIP: {{ .Values.RepoIP }}
-    repoPort: {{ .Values.RepoPort }}
-    repoName: {{ .Values.RepoName }}
-    sshPrivateKey: {{ .Values.SSHPrivateKey | b64enc }}
-    clusterRoleName: {{ .Global.PCloudEnvName }}-env-manager
diff --git a/core/installer/values-tmpl/fluxcd-reconciler.cue b/core/installer/values-tmpl/fluxcd-reconciler.cue
new file mode 100644
index 0000000..8c90648
--- /dev/null
+++ b/core/installer/values-tmpl/fluxcd-reconciler.cue
@@ -0,0 +1,34 @@
+input: {}
+
+images: {
+	fluxcdReconciler: {
+		repository: "giolekva"
+		name: "fluxcd-reconciler"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	fluxcdReconciler: {
+		chart: "charts/fluxcd-reconciler"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"fluxcd-reconciler": {
+		chart: charts.fluxcdReconciler
+		values: {
+			image: {
+				repository: images.fluxcdReconciler.fullName
+				tag: images.fluxcdReconciler.tag
+				pullPolicy: images.fluxcdReconciler.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/fluxcd-reconciler.jsonschema b/core/installer/values-tmpl/fluxcd-reconciler.jsonschema
deleted file mode 100644
index 4a7c07d..0000000
--- a/core/installer/values-tmpl/fluxcd-reconciler.jsonschema
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "type": "object",
-  "properties": {},
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/fluxcd-reconciler.md b/core/installer/values-tmpl/fluxcd-reconciler.md
deleted file mode 100644
index f31c256..0000000
--- a/core/installer/values-tmpl/fluxcd-reconciler.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs gateway to Fluxcd API
diff --git a/core/installer/values-tmpl/fluxcd-reconciler.yaml b/core/installer/values-tmpl/fluxcd-reconciler.yaml
deleted file mode 100644
index 51fd8d1..0000000
--- a/core/installer/values-tmpl/fluxcd-reconciler.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: fluxcd-reconciler
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/fluxcd-reconciler
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
diff --git a/core/installer/values-tmpl/harbor.yaml b/core/installer/values-tmpl/harbor.yaml
new file mode 100644
index 0000000..8fe0180
--- /dev/null
+++ b/core/installer/values-tmpl/harbor.yaml
@@ -0,0 +1,918 @@
+expose:
+  # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer"
+  # and fill the information in the corresponding section
+  type: ingress
+  tls:
+    # Enable TLS or not.
+    # Delete the "ssl-redirect" annotations in "expose.ingress.annotations" when TLS is disabled and "expose.type" is "ingress"
+    # Note: if the "expose.type" is "ingress" and TLS is disabled,
+    # the port must be included in the command when pulling/pushing images.
+    # Refer to https://github.com/goharbor/harbor/issues/5291 for details.
+    enabled: true
+    # The source of the tls certificate. Set as "auto", "secret"
+    # or "none" and fill the information in the corresponding section
+    # 1) auto: generate the tls certificate automatically
+    # 2) secret: read the tls certificate from the specified secret.
+    # The tls certificate can be generated manually or by cert manager
+    # 3) none: configure no tls certificate for the ingress. If the default
+    # tls certificate is configured in the ingress controller, choose this option
+    certSource: auto
+    auto:
+      # The common name used to generate the certificate, it's necessary
+      # when the type isn't "ingress"
+      commonName: ""
+    secret:
+      # The name of secret which contains keys named:
+      # "tls.crt" - the certificate
+      # "tls.key" - the private key
+      secretName: ""
+  ingress:
+    hosts:
+      core: harbor.t46.lekva.me
+    # set to the type of ingress controller if it has specific requirements.
+    # leave as `default` for most ingress controllers.
+    # set to `gce` if using the GCE ingress controller
+    # set to `ncp` if using the NCP (NSX-T Container Plugin) ingress controller
+    # set to `alb` if using the ALB ingress controller
+    # set to `f5-bigip` if using the F5 BIG-IP ingress controller
+    controller: default
+    ## Allow .Capabilities.KubeVersion.Version to be overridden while creating ingress
+    kubeVersionOverride: ""
+    className: dodo-ingress-public
+    annotations:
+      # note different ingress controllers may require a different ssl-redirect annotation
+      # for Envoy, use ingress.kubernetes.io/force-ssl-redirect: "true" and remove the nginx lines below
+      ingress.kubernetes.io/ssl-redirect: "true"
+      ingress.kubernetes.io/proxy-body-size: "0"
+      nginx.ingress.kubernetes.io/ssl-redirect: "true"
+      nginx.ingress.kubernetes.io/proxy-body-size: "0"
+      acme.cert-manager.io/http01-edit-in-place: "true"
+      cert-manager.io/cluster-issuer: vhrb-public
+    harbor:
+      # harbor ingress-specific annotations
+      annotations: {}
+      # harbor ingress-specific labels
+      labels: {}
+  clusterIP:
+    # The name of ClusterIP service
+    name: harbor
+    # The ip address of the ClusterIP service (leave empty for acquiring dynamic ip)
+    staticClusterIP: ""
+    # Annotations on the ClusterIP service
+    annotations: {}
+    ports:
+      # The service port Harbor listens on when serving HTTP
+      httpPort: 80
+      # The service port Harbor listens on when serving HTTPS
+      httpsPort: 443
+  nodePort:
+    # The name of NodePort service
+    name: harbor
+    ports:
+      http:
+        # The service port Harbor listens on when serving HTTP
+        port: 80
+        # The node port Harbor listens on when serving HTTP
+        nodePort: 30002
+      https:
+        # The service port Harbor listens on when serving HTTPS
+        port: 443
+        # The node port Harbor listens on when serving HTTPS
+        nodePort: 30003
+  loadBalancer:
+    # The name of LoadBalancer service
+    name: harbor
+    # Set the IP if the LoadBalancer supports assigning IP
+    IP: ""
+    ports:
+      # The service port Harbor listens on when serving HTTP
+      httpPort: 80
+      # The service port Harbor listens on when serving HTTPS
+      httpsPort: 443
+    annotations: {}
+    sourceRanges: []
+
+# The external URL for Harbor core service. It is used to
+# 1) populate the docker/helm commands showed on portal
+# 2) populate the token service URL returned to docker client
+#
+# Format: protocol://domain[:port]. Usually:
+# 1) if "expose.type" is "ingress", the "domain" should be
+# the value of "expose.ingress.hosts.core"
+# 2) if "expose.type" is "clusterIP", the "domain" should be
+# the value of "expose.clusterIP.name"
+# 3) if "expose.type" is "nodePort", the "domain" should be
+# the IP address of k8s node
+#
+# If Harbor is deployed behind the proxy, set it as the URL of proxy
+externalURL: https://harbor.t46.lekva.me
+
+# The internal TLS used for harbor components secure communicating. In order to enable https
+# in each component tls cert files need to provided in advance.
+internalTLS:
+  # If internal TLS enabled
+  enabled: false
+  # enable strong ssl ciphers (default: false)
+  strong_ssl_ciphers: false
+  # There are three ways to provide tls
+  # 1) "auto" will generate cert automatically
+  # 2) "manual" need provide cert file manually in following value
+  # 3) "secret" internal certificates from secret
+  certSource: "auto"
+  # The content of trust ca, only available when `certSource` is "manual"
+  trustCa: ""
+  # core related cert configuration
+  core:
+    # secret name for core's tls certs
+    secretName: ""
+    # Content of core's TLS cert file, only available when `certSource` is "manual"
+    crt: ""
+    # Content of core's TLS key file, only available when `certSource` is "manual"
+    key: ""
+  # jobservice related cert configuration
+  jobservice:
+    # secret name for jobservice's tls certs
+    secretName: ""
+    # Content of jobservice's TLS key file, only available when `certSource` is "manual"
+    crt: ""
+    # Content of jobservice's TLS key file, only available when `certSource` is "manual"
+    key: ""
+  # registry related cert configuration
+  registry:
+    # secret name for registry's tls certs
+    secretName: ""
+    # Content of registry's TLS key file, only available when `certSource` is "manual"
+    crt: ""
+    # Content of registry's TLS key file, only available when `certSource` is "manual"
+    key: ""
+  # portal related cert configuration
+  portal:
+    # secret name for portal's tls certs
+    secretName: ""
+    # Content of portal's TLS key file, only available when `certSource` is "manual"
+    crt: ""
+    # Content of portal's TLS key file, only available when `certSource` is "manual"
+    key: ""
+  # trivy related cert configuration
+  trivy:
+    # secret name for trivy's tls certs
+    secretName: ""
+    # Content of trivy's TLS key file, only available when `certSource` is "manual"
+    crt: ""
+    # Content of trivy's TLS key file, only available when `certSource` is "manual"
+    key: ""
+
+ipFamily:
+  # ipv6Enabled set to true if ipv6 is enabled in cluster, currently it affected the nginx related component
+  ipv6:
+    enabled: false
+  # ipv4Enabled set to true if ipv4 is enabled in cluster, currently it affected the nginx related component
+  ipv4:
+    enabled: true
+
+# The persistence is enabled by default and a default StorageClass
+# is needed in the k8s cluster to provision volumes dynamically.
+# Specify another StorageClass in the "storageClass" or set "existingClaim"
+# if you already have existing persistent volumes to use
+#
+# For storing images and charts, you can also use "azure", "gcs", "s3",
+# "swift" or "oss". Set it in the "imageChartStorage" section
+persistence:
+  enabled: true
+  # Setting it to "keep" to avoid removing PVCs during a helm delete
+  # operation. Leaving it empty will delete PVCs after the chart deleted
+  # (this does not apply for PVCs that are created for internal database
+  # and redis components, i.e. they are never deleted automatically)
+  resourcePolicy: "keep"
+  persistentVolumeClaim:
+    registry:
+      # Use the existing PVC which must be created manually before bound,
+      # and specify the "subPath" if the PVC is shared with other components
+      existingClaim: ""
+      # Specify the "storageClass" used to provision the volume. Or the default
+      # StorageClass will be used (the default).
+      # Set it to "-" to disable dynamic provisioning
+      storageClass: ""
+      subPath: ""
+      accessMode: ReadWriteOnce
+      size: 5Gi
+      annotations: {}
+    jobservice:
+      jobLog:
+        existingClaim: ""
+        storageClass: ""
+        subPath: ""
+        accessMode: ReadWriteOnce
+        size: 1Gi
+        annotations: {}
+    # If external database is used, the following settings for database will
+    # be ignored
+    database:
+      existingClaim: ""
+      storageClass: ""
+      subPath: ""
+      accessMode: ReadWriteOnce
+      size: 1Gi
+      annotations: {}
+    # If external Redis is used, the following settings for Redis will
+    # be ignored
+    redis:
+      existingClaim: ""
+      storageClass: ""
+      subPath: ""
+      accessMode: ReadWriteOnce
+      size: 1Gi
+      annotations: {}
+    trivy:
+      existingClaim: ""
+      storageClass: ""
+      subPath: ""
+      accessMode: ReadWriteOnce
+      size: 5Gi
+      annotations: {}
+  # Define which storage backend is used for registry to store
+  # images and charts. Refer to
+  # https://github.com/distribution/distribution/blob/main/docs/configuration.md#storage
+  # for the detail.
+  imageChartStorage:
+    # Specify whether to disable `redirect` for images and chart storage, for
+    # backends which not supported it (such as using minio for `s3` storage type), please disable
+    # it. To disable redirects, simply set `disableredirect` to `true` instead.
+    # Refer to
+    # https://github.com/distribution/distribution/blob/main/docs/configuration.md#redirect
+    # for the detail.
+    disableredirect: false
+    # Specify the "caBundleSecretName" if the storage service uses a self-signed certificate.
+    # The secret must contain keys named "ca.crt" which will be injected into the trust store
+    # of registry's containers.
+    # caBundleSecretName:
+
+    # Specify the type of storage: "filesystem", "azure", "gcs", "s3", "swift",
+    # "oss" and fill the information needed in the corresponding section. The type
+    # must be "filesystem" if you want to use persistent volumes for registry
+    type: filesystem
+    filesystem:
+      rootdirectory: /storage
+      #maxthreads: 100
+
+imagePullPolicy: IfNotPresent
+
+# Use this set to assign a list of default pullSecrets
+imagePullSecrets:
+#  - name: docker-registry-secret
+#  - name: internal-registry-secret
+
+# The update strategy for deployments with persistent volumes(jobservice, registry): "RollingUpdate" or "Recreate"
+# Set it as "Recreate" when "RWM" for volumes isn't supported
+updateStrategy:
+  type: RollingUpdate
+
+# debug, info, warning, error or fatal
+logLevel: info
+
+# The initial password of Harbor admin. Change it from portal after launching Harbor
+# or give an existing secret for it
+# key in secret is given via (default to HARBOR_ADMIN_PASSWORD)
+# existingSecretAdminPassword:
+existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD
+harborAdminPassword: "Harbor12345"
+
+# The name of the secret which contains key named "ca.crt". Setting this enables the
+# download link on portal to download the CA certificate when the certificate isn't
+# generated automatically
+caSecretName: ""
+
+# The secret key used for encryption. Must be a string of 16 chars.
+secretKey: "not-a-secure-key"
+# If using existingSecretSecretKey, the key must be secretKey
+existingSecretSecretKey: ""
+
+# The proxy settings for updating trivy vulnerabilities from the Internet and replicating
+# artifacts from/to the registries that cannot be reached directly
+proxy:
+  httpProxy:
+  httpsProxy:
+  noProxy: 127.0.0.1,localhost,.local,.internal
+  components:
+    - core
+    - jobservice
+    - trivy
+
+# Run the migration job via helm hook
+enableMigrateHelmHook: false
+
+# The custom ca bundle secret, the secret must contain key named "ca.crt"
+# which will be injected into the trust store for core, jobservice, registry, trivy components
+# caBundleSecretName: ""
+
+## UAA Authentication Options
+# If you're using UAA for authentication behind a self-signed
+# certificate you will need to provide the CA Cert.
+# Set uaaSecretName below to provide a pre-created secret that
+# contains a base64 encoded CA Certificate named `ca.crt`.
+# uaaSecretName:
+
+# If service exposed via "ingress", the Nginx will not be used
+nginx:
+  image:
+    repository: goharbor/nginx-photon
+    tag: v2.10.0
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  replicas: 1
+  revisionHistoryLimit: 10
+  # resources:
+  #  requests:
+  #    memory: 256Mi
+  #    cpu: 100m
+  extraEnvVars: []
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  ## The priority class to run the pod as
+  priorityClassName:
+
+portal:
+  image:
+    repository: goharbor/harbor-portal
+    tag: v2.10.0
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  replicas: 1
+  revisionHistoryLimit: 10
+  # resources:
+  #  requests:
+  #    memory: 256Mi
+  #    cpu: 100m
+  extraEnvVars: []
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  ## Additional service annotations
+  serviceAnnotations: {}
+  ## The priority class to run the pod as
+  priorityClassName:
+
+core:
+  image:
+    repository: goharbor/harbor-core
+    tag: v2.10.0
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  replicas: 1
+  revisionHistoryLimit: 10
+  ## Startup probe values
+  startupProbe:
+    enabled: true
+    initialDelaySeconds: 10
+  # resources:
+  #  requests:
+  #    memory: 256Mi
+  #    cpu: 100m
+  extraEnvVars: []
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  ## Additional service annotations
+  serviceAnnotations: {}
+  ## User settings configuration json string
+  configureUserSettings:
+  # The provider for updating project quota(usage), there are 2 options, redis or db.
+  # By default it is implemented by db but you can configure it to redis which
+  # can improve the performance of high concurrent pushing to the same project,
+  # and reduce the database connections spike and occupies.
+  # Using redis will bring up some delay for quota usage updation for display, so only
+  # suggest switch provider to redis if you were ran into the db connections spike around
+  # the scenario of high concurrent pushing to same project, no improvment for other scenes.
+  quotaUpdateProvider: db # Or redis
+  # Secret is used when core server communicates with other components.
+  # If a secret key is not specified, Helm will generate one. Alternatively set existingSecret to use an existing secret
+  # Must be a string of 16 chars.
+  secret: ""
+  # Fill in the name of a kubernetes secret if you want to use your own
+  # If using existingSecret, the key must be secret
+  existingSecret: ""
+  # Fill the name of a kubernetes secret if you want to use your own
+  # TLS certificate and private key for token encryption/decryption.
+  # The secret must contain keys named:
+  # "tls.key" - the private key
+  # "tls.crt" - the certificate
+  secretName: ""
+  # If not specifying a preexisting secret, a secret can be created from tokenKey and tokenCert and used instead.
+  # If none of secretName, tokenKey, and tokenCert are specified, an ephemeral key and certificate will be autogenerated.
+  # tokenKey and tokenCert must BOTH be set or BOTH unset.
+  # The tokenKey value is formatted as a multiline string containing a PEM-encoded RSA key, indented one more than tokenKey on the following line.
+  tokenKey: |
+  # If tokenKey is set, the value of tokenCert must be set as a PEM-encoded certificate signed by tokenKey, and supplied as a multiline string, indented one more than tokenCert on the following line.
+  tokenCert: |
+  # The XSRF key. Will be generated automatically if it isn't specified
+  xsrfKey: ""
+  # If using existingSecret, the key is defined by core.existingXsrfSecretKey
+  existingXsrfSecret: ""
+  # If using existingSecret, the key
+  existingXsrfSecretKey: CSRF_KEY
+  ## The priority class to run the pod as
+  priorityClassName:
+  # The time duration for async update artifact pull_time and repository
+  # pull_count, the unit is second. Will be 10 seconds if it isn't set.
+  # eg. artifactPullAsyncFlushDuration: 10
+  artifactPullAsyncFlushDuration:
+  gdpr:
+    deleteUser: false
+
+jobservice:
+  image:
+    repository: goharbor/harbor-jobservice
+    tag: v2.10.0
+  replicas: 1
+  revisionHistoryLimit: 10
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  maxJobWorkers: 10
+  # The logger for jobs: "file", "database" or "stdout"
+  jobLoggers:
+    - file
+    # - database
+    # - stdout
+  # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
+  loggerSweeperDuration: 14 #days
+  notification:
+    webhook_job_max_retry: 3
+    webhook_job_http_client_timeout: 3 # in seconds
+  reaper:
+    # the max time to wait for a task to finish, if unfinished after max_update_hours, the task will be mark as error, but the task will continue to run, default value is 24
+    max_update_hours: 24
+    # the max time for execution in running state without new task created
+    max_dangling_hours: 168
+
+  # resources:
+  #   requests:
+  #     memory: 256Mi
+  #     cpu: 100m
+  extraEnvVars: []
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints:
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  # Secret is used when job service communicates with other components.
+  # If a secret key is not specified, Helm will generate one.
+  # Must be a string of 16 chars.
+  secret: ""
+  # Use an existing secret resource
+  existingSecret: ""
+  # Key within the existing secret for the job service secret
+  existingSecretKey: JOBSERVICE_SECRET
+  ## The priority class to run the pod as
+  priorityClassName:
+
+registry:
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  registry:
+    image:
+      repository: goharbor/registry-photon
+      tag: v2.10.0
+    # resources:
+    #  requests:
+    #    memory: 256Mi
+    #    cpu: 100m
+    extraEnvVars: []
+  controller:
+    image:
+      repository: goharbor/harbor-registryctl
+      tag: dev
+
+    # resources:
+    #  requests:
+    #    memory: 256Mi
+    #    cpu: 100m
+    extraEnvVars: []
+  replicas: 1
+  revisionHistoryLimit: 10
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  ## The priority class to run the pod as
+  priorityClassName:
+  # Secret is used to secure the upload state from client
+  # and registry storage backend.
+  # See: https://github.com/distribution/distribution/blob/main/docs/configuration.md#http
+  # If a secret key is not specified, Helm will generate one.
+  # Must be a string of 16 chars.
+  secret: ""
+  # Use an existing secret resource
+  existingSecret: ""
+  # Key within the existing secret for the registry service secret
+  existingSecretKey: REGISTRY_HTTP_SECRET
+  # If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL.
+  relativeurls: false
+  credentials:
+    username: "harbor_registry_user"
+    password: "harbor_registry_password"
+    # If using existingSecret, the key must be REGISTRY_PASSWD and REGISTRY_HTPASSWD
+    existingSecret: ""
+    # Login and password in htpasswd string format. Excludes `registry.credentials.username`  and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt.
+    # htpasswdString: $apr1$XLefHzeG$Xl4.s00sMSCCcMyJljSZb0 # example string
+    htpasswdString: ""
+  middleware:
+    enabled: false
+    type: cloudFront
+    cloudFront:
+      baseurl: example.cloudfront.net
+      keypairid: KEYPAIRID
+      duration: 3000s
+      ipfilteredby: none
+      # The secret key that should be present is CLOUDFRONT_KEY_DATA, which should be the encoded private key
+      # that allows access to CloudFront
+      privateKeySecret: "my-secret"
+  # enable purge _upload directories
+  upload_purging:
+    enabled: true
+    # remove files in _upload directories which exist for a period of time, default is one week.
+    age: 168h
+    # the interval of the purge operations
+    interval: 24h
+    dryrun: false
+
+trivy:
+  # enabled the flag to enable Trivy scanner
+  enabled: true
+  image:
+    # repository the repository for Trivy adapter image
+    repository: goharbor/trivy-adapter-photon
+    # tag the tag for Trivy adapter image
+    tag: dev
+  # set the service account to be used, default if left empty
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  # replicas the number of Pod replicas
+  replicas: 1
+  # debugMode the flag to enable Trivy debug mode with more verbose scanning log
+  debugMode: false
+  # vulnType a comma-separated list of vulnerability types. Possible values are `os` and `library`.
+  vulnType: "os,library"
+  # severity a comma-separated list of severities to be checked
+  severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
+  # ignoreUnfixed the flag to display only fixed vulnerabilities
+  ignoreUnfixed: false
+  # insecure the flag to skip verifying registry certificate
+  insecure: false
+  # gitHubToken the GitHub access token to download Trivy DB
+  #
+  # Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases.
+  # It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached
+  # in the local file system (`/home/scanner/.cache/trivy/db/trivy.db`). In addition, the database contains the update
+  # timestamp so Trivy can detect whether it should download a newer version from the Internet or use the cached one.
+  # Currently, the database is updated every 12 hours and published as a new release to GitHub.
+  #
+  # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
+  # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
+  # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
+  # https://developer.github.com/v3/#rate-limiting
+  #
+  # You can create a GitHub token by following the instructions in
+  # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
+  gitHubToken: ""
+  # skipUpdate the flag to disable Trivy DB downloads from GitHub
+  #
+  # You might want to set the value of this flag to `true` in test or CI/CD environments to avoid GitHub rate limiting issues.
+  # If the value is set to `true` you have to manually download the `trivy.db` file and mount it in the
+  # `/home/scanner/.cache/trivy/db/trivy.db` path.
+  skipUpdate: false
+  # The offlineScan option prevents Trivy from sending API requests to identify dependencies.
+  #
+  # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it.
+  # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't
+  # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode.
+  # It would work if all the dependencies are in local.
+  # This option doesn’t affect DB download. You need to specify skipUpdate as well as offlineScan in an air-gapped environment.
+  offlineScan: false
+  # Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`.
+  securityCheck: "vuln"
+  # The duration to wait for scan completion
+  timeout: 5m0s
+  resources:
+    requests:
+      cpu: 200m
+      memory: 512Mi
+    limits:
+      cpu: 1
+      memory: 1Gi
+  extraEnvVars: []
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  ## The priority class to run the pod as
+  priorityClassName:
+
+database:
+  # if external database is used, set "type" to "external"
+  # and fill the connection information in "external" section
+  type: internal
+  internal:
+    # set the service account to be used, default if left empty
+    serviceAccountName: ""
+    # mount the service account token
+    automountServiceAccountToken: false
+    image:
+      repository: goharbor/harbor-db
+      tag: v2.10.0
+    # The initial superuser password for internal database
+    password: "changeit"
+    # The size limit for Shared memory, pgSQL use it for shared_buffer
+    # More details see:
+    # https://github.com/goharbor/harbor/issues/15034
+    shmSizeLimit: 512Mi
+    # resources:
+    #  requests:
+    #    memory: 256Mi
+    #    cpu: 100m
+    # The timeout used in livenessProbe; 1 to 5 seconds
+    livenessProbe:
+      timeoutSeconds: 1
+    # The timeout used in readinessProbe; 1 to 5 seconds
+    readinessProbe:
+      timeoutSeconds: 1
+    extraEnvVars: []
+    nodeSelector: {}
+    tolerations: []
+    affinity: {}
+    ## The priority class to run the pod as
+    priorityClassName:
+    initContainer:
+      migrator: {}
+      # resources:
+      #  requests:
+      #    memory: 128Mi
+      #    cpu: 100m
+      permissions: {}
+      # resources:
+      #  requests:
+      #    memory: 128Mi
+      #    cpu: 100m
+  external:
+    host: "192.168.0.1"
+    port: "5432"
+    username: "user"
+    password: "password"
+    coreDatabase: "registry"
+    # if using existing secret, the key must be "password"
+    existingSecret: ""
+    # "disable" - No SSL
+    # "require" - Always SSL (skip verification)
+    # "verify-ca" - Always SSL (verify that the certificate presented by the
+    # server was signed by a trusted CA)
+    # "verify-full" - Always SSL (verify that the certification presented by the
+    # server was signed by a trusted CA and the server host name matches the one
+    # in the certificate)
+    sslmode: "disable"
+  # The maximum number of connections in the idle connection pool per pod (core+exporter).
+  # If it <=0, no idle connections are retained.
+  maxIdleConns: 100
+  # The maximum number of open connections to the database per pod (core+exporter).
+  # If it <= 0, then there is no limit on the number of open connections.
+  # Note: the default number of connections is 1024 for postgre of harbor.
+  maxOpenConns: 900
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+
+redis:
+  # if external Redis is used, set "type" to "external"
+  # and fill the connection information in "external" section
+  type: internal
+  internal:
+    # set the service account to be used, default if left empty
+    serviceAccountName: ""
+    # mount the service account token
+    automountServiceAccountToken: false
+    image:
+      repository: goharbor/redis-photon
+      tag: v2.10.0
+    # resources:
+    #  requests:
+    #    memory: 256Mi
+    #    cpu: 100m
+    extraEnvVars: []
+    nodeSelector: {}
+    tolerations: []
+    affinity: {}
+    ## The priority class to run the pod as
+    priorityClassName:
+    # # jobserviceDatabaseIndex defaults to "1"
+    # # registryDatabaseIndex defaults to "2"
+    # # trivyAdapterIndex defaults to "5"
+    # # harborDatabaseIndex defaults to "0", but it can be configured to "6", this config is optional
+    # # cacheLayerDatabaseIndex defaults to "0", but it can be configured to "7", this config is optional
+    jobserviceDatabaseIndex: "1"
+    registryDatabaseIndex: "2"
+    trivyAdapterIndex: "5"
+    # harborDatabaseIndex: "6"
+    # cacheLayerDatabaseIndex: "7"
+  external:
+    # support redis, redis+sentinel
+    # addr for redis: <host_redis>:<port_redis>
+    # addr for redis+sentinel: <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
+    addr: "192.168.0.2:6379"
+    # The name of the set of Redis instances to monitor, it must be set to support redis+sentinel
+    sentinelMasterSet: ""
+    # The "coreDatabaseIndex" must be "0" as the library Harbor
+    # used doesn't support configuring it
+    # harborDatabaseIndex defaults to "0", but it can be configured to "6", this config is optional
+    # cacheLayerDatabaseIndex defaults to "0", but it can be configured to "7", this config is optional
+    coreDatabaseIndex: "0"
+    jobserviceDatabaseIndex: "1"
+    registryDatabaseIndex: "2"
+    trivyAdapterIndex: "5"
+    # harborDatabaseIndex: "6"
+    # cacheLayerDatabaseIndex: "7"
+    # username field can be an empty string, and it will be authenticated against the default user
+    username: ""
+    password: ""
+    # If using existingSecret, the key must be REDIS_PASSWORD
+    existingSecret: ""
+  ## Additional deployment annotations
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+
+exporter:
+  replicas: 1
+  revisionHistoryLimit: 10
+  # resources:
+  #  requests:
+  #    memory: 256Mi
+  #    cpu: 100m
+  extraEnvVars: []
+  podAnnotations: {}
+  ## Additional deployment labels
+  podLabels: {}
+  serviceAccountName: ""
+  # mount the service account token
+  automountServiceAccountToken: false
+  image:
+    repository: goharbor/harbor-exporter
+    tag: v2.10.0
+  nodeSelector: {}
+  tolerations: []
+  affinity: {}
+  # Spread Pods across failure-domains like regions, availability zones or nodes
+  topologySpreadConstraints: []
+  # - maxSkew: 1
+  #   topologyKey: topology.kubernetes.io/zone
+  #   nodeTaintsPolicy: Honor
+  #   whenUnsatisfiable: DoNotSchedule
+  cacheDuration: 23
+  cacheCleanInterval: 14400
+  ## The priority class to run the pod as
+  priorityClassName:
+
+metrics:
+  enabled: false
+  core:
+    path: /metrics
+    port: 8001
+  registry:
+    path: /metrics
+    port: 8001
+  jobservice:
+    path: /metrics
+    port: 8001
+  exporter:
+    path: /metrics
+    port: 8001
+  ## Create prometheus serviceMonitor to scrape harbor metrics.
+  ## This requires the monitoring.coreos.com/v1 CRD. Please see
+  ## https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md
+  ##
+  serviceMonitor:
+    enabled: false
+    additionalLabels: {}
+    # Scrape interval. If not set, the Prometheus default scrape interval is used.
+    interval: ""
+    # Metric relabel configs to apply to samples before ingestion.
+    metricRelabelings:
+      []
+      # - action: keep
+      #   regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
+      #   sourceLabels: [__name__]
+    # Relabel configs to apply to samples before ingestion.
+    relabelings:
+      []
+      # - sourceLabels: [__meta_kubernetes_pod_node_name]
+      #   separator: ;
+      #   regex: ^(.*)$
+      #   targetLabel: nodename
+      #   replacement: $1
+      #   action: replace
+
+trace:
+  enabled: false
+  # trace provider: jaeger or otel
+  # jaeger should be 1.26+
+  provider: jaeger
+  # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth
+  sample_rate: 1
+  # namespace used to differentiate different harbor services
+  # namespace:
+  # attributes is a key value dict contains user defined attributes used to initialize trace provider
+  # attributes:
+  #   application: harbor
+  jaeger:
+    # jaeger supports two modes:
+    #   collector mode(uncomment endpoint and uncomment username, password if needed)
+    #   agent mode(uncomment agent_host and agent_port)
+    endpoint: http://hostname:14268/api/traces
+    # username:
+    # password:
+    # agent_host: hostname
+    # export trace data by jaeger.thrift in compact mode
+    # agent_port: 6831
+  otel:
+    endpoint: hostname:4318
+    url_path: /v1/traces
+    compression: false
+    insecure: true
+    # timeout is in seconds
+    timeout: 10
+
+# cache layer configurations
+# if this feature enabled, harbor will cache the resource
+# `project/project_metadata/repository/artifact/manifest` in the redis
+# which help to improve the performance of high concurrent pulling manifest.
+cache:
+  # default is not enabled.
+  enabled: false
+  # default keep cache for one day.
+  expireHours: 24
diff --git a/core/installer/values-tmpl/headscale-controller.cue b/core/installer/values-tmpl/headscale-controller.cue
new file mode 100644
index 0000000..8a9756b
--- /dev/null
+++ b/core/installer/values-tmpl/headscale-controller.cue
@@ -0,0 +1,49 @@
+input: {}
+
+images: {
+	headscaleController: {
+		repository: "giolekva"
+		name: "headscale-controller"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+	kubeRBACProxy: {
+		registry: "gcr.io"
+		repository: "kubebuilder"
+		name: "kube-rbac-proxy"
+		tag: "v0.13.0"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	headscaleController: {
+		chart: "charts/headscale-controller"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"headscale-controller": {
+		chart: charts.headscaleController
+		values: {
+			installCRDs: true
+			image: {
+				repository: images.headscaleController.fullName
+				tag: images.headscaleController.tag
+				pullPolicy: images.headscaleController.pullPolicy
+			}
+			kubeRBACProxy: {
+				image: {
+					repository: images.kubeRBACProxy.fullName
+					tag: images.kubeRBACProxy.tag
+					pullPolicy: images.kubeRBACProxy.pullPolicy
+				}
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/headscale-controller.jsonschema b/core/installer/values-tmpl/headscale-controller.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/headscale-controller.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/headscale-controller.md b/core/installer/values-tmpl/headscale-controller.md
deleted file mode 100644
index 99d2190..0000000
--- a/core/installer/values-tmpl/headscale-controller.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs headscale controller
diff --git a/core/installer/values-tmpl/headscale-controller.yaml b/core/installer/values-tmpl/headscale-controller.yaml
deleted file mode 100644
index f799d9b..0000000
--- a/core/installer/values-tmpl/headscale-controller.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: headscale-controller
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/headscale-controller
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
diff --git a/core/installer/values-tmpl/headscale-user.cue b/core/installer/values-tmpl/headscale-user.cue
new file mode 100644
index 0000000..c591d12
--- /dev/null
+++ b/core/installer/values-tmpl/headscale-user.cue
@@ -0,0 +1,33 @@
+input: {
+	username: string
+	preAuthKey: {
+		enabled: bool | *false
+	}
+}
+
+images: {}
+
+charts: {
+	headscaleUser: {
+		chart: "charts/headscale-user"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	"headscale-user": {
+		chart: charts.headscaleUser
+		values: {
+			username: input.username
+			headscaleApiAddress: "http://headscale-api.\(global.namespacePrefix)app-headscale.svc.cluster.local"
+			preAuthKey: {
+				enabled: input.preAuthKey.enabled
+				secretName: "\(input.username)-headscale-preauthkey"
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/headscale-user.jsonschema b/core/installer/values-tmpl/headscale-user.jsonschema
deleted file mode 100644
index e4884a6..0000000
--- a/core/installer/values-tmpl/headscale-user.jsonschema
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Username": { "type": "string" },
-	"PreAuthKey": {
-	  "type": "object",
-	  "properties": {
-	    "Enabled": { "type": "boolean" }
-	  },
-	  "additionalProperties": false
-	}
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/headscale-user.md b/core/installer/values-tmpl/headscale-user.md
deleted file mode 100644
index 7246082..0000000
--- a/core/installer/values-tmpl/headscale-user.md
+++ /dev/null
@@ -1 +0,0 @@
-Creates Headscale user resource
diff --git a/core/installer/values-tmpl/headscale-user.yaml b/core/installer/values-tmpl/headscale-user.yaml
deleted file mode 100644
index 0eca8d0..0000000
--- a/core/installer/values-tmpl/headscale-user.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: user-{{ .Values.Username }}
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/headscale-user
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    username: {{ .Values.Username }}
-    headscaleApiAddress: http://headscale-api.{{ .Global.Id }}-app-headscale.svc.cluster.local
-    preAuthKey:
-      enabled: {{ .Values.PreAuthKey.Enabled }}
-      secretName: {{ .Values.Username }}-headscale-preauthkey
diff --git a/core/installer/values-tmpl/headscale.cue b/core/installer/values-tmpl/headscale.cue
new file mode 100644
index 0000000..21dbb75
--- /dev/null
+++ b/core/installer/values-tmpl/headscale.cue
@@ -0,0 +1,68 @@
+input: {
+	subdomain: string
+}
+
+images: {
+	headscale: {
+		repository: "headscale"
+		name: "headscale"
+		tag: "0.22.3"
+		pullPolicy: "IfNotPresent"
+	}
+	api: {
+		repository: "giolekva"
+		name: "headscale-api"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	headscale: {
+		chart: "charts/headscale"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	headscale: {
+		chart: charts.headscale
+		dependsOnExternal: [{
+			name: "auth"
+			namespace: "\(global.namespacePrefix)core-auth"
+		}]
+		values: {
+			image: {
+				repository: images.headscale.fullName
+				tag: images.headscale.tag
+				pullPolicy: images.headscale.pullPolicy
+			}
+			storage: size: "5Gi"
+			ingressClassName: _ingressPublic
+			certificateIssuer: _issuerPublic
+			domain: "\(input.subdomain).\(global.domain)"
+			publicBaseDomain: global.domain
+			oauth2: {
+				hydraAdmin: "http://hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
+				hydraPublic: "https://hydra.\(global.domain)"
+				clientId: "headscale"
+				secretName: "oauth2-client-headscale"
+			}
+			ipAddressPool: "\(global.id)-headscale"
+			api: {
+				port: 8585
+				rootDomain: global.domain
+				image: {
+					repository: images.api.fullName
+					tag: images.api.tag
+					pullPolicy: images.api.pullPolicy
+				}
+			}
+			ui: enabled: false
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/headscale.jsonschema b/core/installer/values-tmpl/headscale.jsonschema
deleted file mode 100644
index a7ccc8d..0000000
--- a/core/installer/values-tmpl/headscale.jsonschema
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Subdomain": { "type": "string" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/headscale.md b/core/installer/values-tmpl/headscale.md
deleted file mode 100644
index 1dec9d9..0000000
--- a/core/installer/values-tmpl/headscale.md
+++ /dev/null
@@ -1 +0,0 @@
-Will install headscale VPN service on https://{{ .Values.Subdomain }}.{{ .Global.Domain }}
diff --git a/core/installer/values-tmpl/headscale.yaml b/core/installer/values-tmpl/headscale.yaml
deleted file mode 100644
index 4eeeacd..0000000
--- a/core/installer/values-tmpl/headscale.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: headscale
-  namespace: {{ .Release.Namespace }}
-spec:
-  dependsOn:
-    - name: core-auth
-      namespace: {{ .Global.NamespacePrefix }}core-auth
-  chart:
-    spec:
-      chart: charts/headscale
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    image:
-      repository: headscale/headscale
-      tag: 0.22.3
-      pullPolicy: IfNotPresent
-    storage:
-      size: 5Gi
-    ingressClassName: {{ .Global.PCloudEnvName }}-ingress-public
-    certificateIssuer: {{ .Global.Id }}-public
-    domain: {{ .Values.Subdomain }}.{{ .Global.Domain }}
-    publicBaseDomain: {{ .Global.Domain }}
-    oauth2:
-      hydraAdmin: http://hydra-admin.{{ .Global.NamespacePrefix }}core-auth.svc.cluster.local
-      hydraPublic: https://hydra.{{ .Global.Domain }}
-      clientId: headscale
-      secretName: oauth2-client-headscale
-    ipAddressPool: {{ .Global.Id }}-headscale
-    api:
-      port: 8585
-      rootDomain: {{ .Global.Domain }}
-      image:
-        repository: giolekva/headscale-api
-        tag: latest
-        pullPolicy: Always
-    ui:
-      enabled: false
diff --git a/core/installer/values-tmpl/ingress-private.yaml b/core/installer/values-tmpl/ingress-private.yaml
deleted file mode 100644
index fb15cd4..0000000
--- a/core/installer/values-tmpl/ingress-private.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: ingress-private
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/ingress-nginx
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    fullnameOverride: {{ .Global.Id }}-nginx-private
-    controller:
-      service:
-        enabled: true
-        type: LoadBalancer
-        annotations:
-          metallb.universe.tf/address-pool: {{ .Global.Id }}-ingress-private
-      ingressClassByName: true
-      ingressClassResource:
-        name: {{ .Global.Id }}-ingress-private
-        enabled: true
-        default: false
-        controllerValue: k8s.io/{{ .Global.Id }}-ingress-private
-      extraArgs:
-        default-ssl-certificate: "{{ .Global.Id }}-ingress-private/cert-wildcard.p.{{ .Global.Domain }}"
-      admissionWebhooks:
-        enabled: false
diff --git a/core/installer/values-tmpl/ingress-public.cue b/core/installer/values-tmpl/ingress-public.cue
new file mode 100644
index 0000000..1717762
--- /dev/null
+++ b/core/installer/values-tmpl/ingress-public.cue
@@ -0,0 +1,57 @@
+input: {}
+
+images: {
+	ingressNginx: {
+		registry: "registry.k8s.io"
+		repository: "ingress-nginx"
+		name: "controller"
+		tag: "v1.8.0"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	ingressNginx: {
+		chart: "charts/ingress-nginx"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"ingress-public": {
+		chart: charts.ingressNginx
+		values: {
+			fullnameOverride: _ingressPublic
+			controller: {
+				kind: "DaemonSet"
+				hostNetwork: true
+				hostPort: enabled: true
+				service: enabled: false
+				ingressClassByName: true
+				ingressClassResource: {
+					name: _ingressPublic
+					enabled: true
+					default: false
+					controllerValue: "k8s.io/\(_ingressPublic)"
+				}
+				config: "proxy-body-size": "100M" // TODO(giolekva): configurable
+				image: {
+					registry: images.ingressNginx.registry
+					image: images.ingressNginx.imageName
+					tag: images.ingressNginx.tag
+					pullPolicy: images.ingressNginx.pullPolicy
+				}
+			}
+			tcp: {
+				"53": "\(global.pcloudEnvName)-dns-zone-manager/coredns:53"
+			}
+			udp: {
+				"53": "\(global.pcloudEnvName)-dns-zone-manager/coredns:53"
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/ingress-public.jsonschema b/core/installer/values-tmpl/ingress-public.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/ingress-public.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/ingress-public.md b/core/installer/values-tmpl/ingress-public.md
deleted file mode 100644
index 227c2d4..0000000
--- a/core/installer/values-tmpl/ingress-public.md
+++ /dev/null
@@ -1 +0,0 @@
-Sets up ingress for publicly accessible services
diff --git a/core/installer/values-tmpl/ingress-public.yaml b/core/installer/values-tmpl/ingress-public.yaml
deleted file mode 100644
index 25379d4..0000000
--- a/core/installer/values-tmpl/ingress-public.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: ingress-public
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/ingress-nginx
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
-    fullnameOverride: {{ .Global.PCloudEnvName }}-ingress-public
-    controller:
-      kind: DaemonSet
-      hostNetwork: true
-      hostPort:
-        enabled: true
-      service:
-        enabled: false
-      ingressClassByName: true
-      ingressClassResource:
-        name: {{ .Global.PCloudEnvName }}-ingress-public
-        enabled: true
-        default: false
-        controllerValue: k8s.io/{{ .Global.PCloudEnvName }}-ingress-public
-      config:
-        proxy-body-size: 100M # TODO(giolekva): configurable
-    tcp:
-      "53": "{{ .Global.PCloudEnvName }}-dns-zone-manager/coredns:53"
-    udp:
-      "53": "{{ .Global.PCloudEnvName }}-dns-zone-manager/coredns:53"
diff --git a/core/installer/values-tmpl/jellyfin.cue b/core/installer/values-tmpl/jellyfin.cue
new file mode 100644
index 0000000..3beff46
--- /dev/null
+++ b/core/installer/values-tmpl/jellyfin.cue
@@ -0,0 +1,46 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "jellyfin application will be installed on \(input.network.name) network and be accessible to any user on https://\(_domain)"
+
+images: {
+	jellyfin: {
+		repository: "jellyfin"
+		name: "jellyfin"
+		tag: "10.8.10"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	jellyfin: {
+		chart: "charts/jellyfin"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	jellyfin: {
+		chart: charts.jellyfin
+		values: {
+			pcloudInstanceId: global.id
+			ingress: {
+				className: input.network.ingressClass
+				domain: _domain
+			}
+			image: {
+				repository: images.jellyfin.fullName
+				tag: images.jellyfin.tag
+				pullPolicy: images.jellyfin.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/jellyfin.jsonschema b/core/installer/values-tmpl/jellyfin.jsonschema
deleted file mode 100644
index ec6a2c5..0000000
--- a/core/installer/values-tmpl/jellyfin.jsonschema
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Values": {
-      "type": "object",
-      "properties": {
-        "NamespacePrefix": { "type": "string" },
-        "Id": { "type": "string" },
-        "Domain": { "type": "string" }
-      },
-      "additionalProperties": false
-    }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/jellyfin.yaml b/core/installer/values-tmpl/jellyfin.yaml
deleted file mode 100644
index d486072..0000000
--- a/core/installer/values-tmpl/jellyfin.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: jellyfin
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/jellyfin
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    pcloudInstanceId: {{ .Global.Id }}
-    ingress:
-      className: {{ .Global.Id }}-ingress-private
-      domain: {{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
diff --git a/core/installer/values-tmpl/matrix-storage.yaml b/core/installer/values-tmpl/matrix-storage.yaml
deleted file mode 100644
index 9b9c56c..0000000
--- a/core/installer/values-tmpl/matrix-storage.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: matrix-storage
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/postgresql
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    fullnameOverride: postgres
-    image:
-      repository: library/postgres # arm64v8/postgres
-      tag: 15.3 # 13.4
-    service:
-      type: ClusterIP
-      port: 5432
-    primary:
-      initdb:
-        scripts:
-          init.sql: |
-            CREATE USER matrix WITH PASSWORD 'matrix';
-            CREATE DATABASE matrix WITH OWNER = matrix ENCODING = UTF8 LOCALE = 'C' TEMPLATE = template0;
-      persistence:
-        size: 10Gi
-      securityContext:
-        enabled: true
-        fsGroup: 0
-      containerSecurityContext:
-        enabled: true
-        runAsUser: 0
-    volumePermissions:
-      securityContext:
-        runAsUser: 0
diff --git a/core/installer/values-tmpl/matrix.cue b/core/installer/values-tmpl/matrix.cue
new file mode 100644
index 0000000..606c7f8
--- /dev/null
+++ b/core/installer/values-tmpl/matrix.cue
@@ -0,0 +1,120 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "matrix application will be installed on \(input.network.name) network and be accessible to any user on https://\(_domain)"
+
+images: {
+	matrix: {
+		repository: "matrixdotorg"
+		name: "synapse"
+		tag: "latest"
+		pullPolicy: "IfNotPresent"
+	}
+	postgres: {
+		repository: "library"
+		name: "postgres"
+		tag: "15.3"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	matrix: {
+		chart: "charts/matrix"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+	postgres: {
+		chart: "charts/postgresql"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	matrix: {
+		dependsOn: [
+			postgres
+	    ]
+		chart: charts.matrix
+		values: {
+			domain: global.domain
+			subdomain: input.subdomain
+			oauth2: {
+				hydraAdmin: "http://hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
+				hydraPublic: "https://hydra.\(global.domain)"
+				secretName: "oauth2-client"
+			}
+			postgresql: {
+				host: "postgres"
+				port: 5432
+				database: "matrix"
+				user: "matrix"
+				password: "matrix"
+			}
+			certificateIssuer: _issuerPublic
+			ingressClassName: _ingressPublic
+			configMerge: {
+				configName: "config-to-merge"
+				fileName: "to-merge.yaml"
+			}
+			image: {
+				repository: images.matrix.fullName
+				tag: images.matrix.tag
+				pullPolicy: images.matrix.pullPolicy
+			}
+		}
+	}
+	postgres: {
+		chart: charts.postgres
+		values: {
+			fullnameOverride: "postgres"
+			image: {
+				registry: images.postgres.registry
+				repository: images.postgres.imageName
+				tag: images.postgres.tag
+				pullPolicy: images.postgres.pullPolicy
+			}
+			service: {
+				type: "ClusterIP"
+				port: 5432
+			}
+			primary: {
+				initdb: {
+					scripts: {
+						"init.sql": """
+						CREATE USER matrix WITH PASSWORD 'matrix';
+						CREATE DATABASE matrix WITH OWNER = matrix ENCODING = UTF8 LOCALE = 'C' TEMPLATE = template0;
+						"""
+					}
+				}
+				persistence: {
+					size: "10Gi"
+				}
+				securityContext: {
+					enabled: true
+					fsGroup: 0
+				}
+				containerSecurityContext: {
+					enabled: true
+					runAsUser: 0
+				}
+			}
+			volumePermissions: {
+				securityContext: {
+					runAsUser: 0
+				}
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/matrix.jsonschema b/core/installer/values-tmpl/matrix.jsonschema
deleted file mode 100644
index 89f64ce..0000000
--- a/core/installer/values-tmpl/matrix.jsonschema
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-	"Subdomain": { "type": "string", "default": "matrix" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/matrix.yaml b/core/installer/values-tmpl/matrix.yaml
deleted file mode 100644
index 579eecd..0000000
--- a/core/installer/values-tmpl/matrix.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: matrix
-  namespace: {{ .Release.Namespace }}
-spec:
-  dependsOn:
-  - name: matrix-storage
-    namespace: {{ .Release.Namespace }}
-  chart:
-    spec:
-      chart: charts/matrix
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    domain: {{ .Global.Domain }}
-    subdomain: {{ .Values.Subdomain }}
-    oauth2:
-      hydraAdmin: http://hydra-admin.{{ .Global.NamespacePrefix }}core-auth.svc.cluster.local
-      hydraPublic: https://hydra.{{ .Global.Domain }}
-      secretName: oauth2-client
-    postgresql:
-      host: postgres
-      port: 5432
-      database: matrix
-      user: matrix
-      password: matrix
-    certificateIssuer: {{ .Global.Id }}-public
-    ingressClassName: {{ .Global.PCloudEnvName }}-ingress-public
-    configMerge:
-      configName: config-to-merge
-      fileName: to-merge.yaml
-    image:
-      repository: matrixdotorg/synapse
-      tag: v1.98.0
-      pullPolicy: IfNotPresent
diff --git a/core/installer/values-tmpl/metallb-ipaddresspool.cue b/core/installer/values-tmpl/metallb-ipaddresspool.cue
new file mode 100644
index 0000000..ba7dee9
--- /dev/null
+++ b/core/installer/values-tmpl/metallb-ipaddresspool.cue
@@ -0,0 +1,33 @@
+input: {
+	name: string
+	from: string
+	to: string
+	autoAssign: bool | *false
+	namespace: string
+}
+
+images: {}
+
+charts: {
+	metallbIPAddressPool: {
+		chart: "charts/metallb-ipaddresspool"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName // TODO(gio): id ?
+		}
+	}
+}
+
+helm: {
+	"metallb-ipaddresspool-\(input.name)": {
+		chart: charts.metallbIPAddressPool
+		values: {
+			name: input.name
+			from: input.from
+			to: input.to
+			autoAssign: input.autoAssign
+			namespace: input.namespace
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/metallb-ipaddresspool.jsonschema b/core/installer/values-tmpl/metallb-ipaddresspool.jsonschema
deleted file mode 100644
index d76700b..0000000
--- a/core/installer/values-tmpl/metallb-ipaddresspool.jsonschema
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Name": { "type": "string" },
-	"From": { "type": "string" },
-    "To": { "type": "string" },
-	"AutoAssign": { "type": "boolean" },
-	"Namespace": { "type": "string" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/metallb-ipaddresspool.md b/core/installer/values-tmpl/metallb-ipaddresspool.md
deleted file mode 100644
index e0ba8e6..0000000
--- a/core/installer/values-tmpl/metallb-ipaddresspool.md
+++ /dev/null
@@ -1 +0,0 @@
-Metallb IPAddressPool
diff --git a/core/installer/values-tmpl/metallb-ipaddresspool.yaml b/core/installer/values-tmpl/metallb-ipaddresspool.yaml
deleted file mode 100644
index 87b55f4..0000000
--- a/core/installer/values-tmpl/metallb-ipaddresspool.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: metallb-ipaddresspool-{{ .Values.Name }}
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/metallb-ipaddresspool
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 10m0s
-  values:
-    name: {{ .Values.Name }}
-    from: {{ .Values.From }}
-    to: {{ .Values.To }}
-    autoAssign: {{ .Values.AutoAssign }}
-    namespace: {{ .Values.Namespace }}
diff --git a/core/installer/values-tmpl/penpot.cue b/core/installer/values-tmpl/penpot.cue
new file mode 100644
index 0000000..75c3b46
--- /dev/null
+++ b/core/installer/values-tmpl/penpot.cue
@@ -0,0 +1,177 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "penpot application will be installed on \(input.network.name) network and be accessible to any user on https://\(_domain)"
+
+images: {
+	postgres: {
+		repository: "library"
+		name: "postgres"
+		tag: "15.3"
+		pullPolicy: "IfNotPresent"
+	}
+	backend: {
+		repository: "penpotapp"
+		name: "backend"
+		tag: "1.16.0-beta"
+		pullPolicy: "IfNotPresent"
+	}
+	frontend: {
+		repository: "penpotapp"
+		name: "frontend"
+		tag: "1.16.0-beta"
+		pullPolicy: "IfNotPresent"
+	}
+	exporter: {
+		repository: "penpotapp"
+		name: "exporter"
+		tag: "1.16.0-beta"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	postgres: {
+		chart: "charts/postgresql"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+	oauth2Client: {
+		chart: "charts/oauth2-client"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+	penpot: {
+		chart: "charts/penpot"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+_oauth2SecretName: "oauth2-credentials"
+
+helm: {
+	"oauth2-client": {
+		chart: charts.oauth2Client
+		values: {
+			name: "penpot"
+			secretName: _oauth2SecretName
+			grantTypes: ["authorization_code"]
+			responseTypes: ["code"]
+			scope: "openid profile email"
+			redirectUris: ["https://\(_domain)/api/auth/oauth/oidc/callback"]
+			hydraAdmin: "http://hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
+			tokenEndpointAuthMethod: "client_secret_post"
+		}
+	}
+	postgres: {
+		chart: charts.postgres
+		values: {
+			fullnameOverride: "postgres"
+			image: {
+				registry: images.postgres.registry
+				repository: images.postgres.imageName
+				tag: images.postgres.tag
+				pullPolicy: images.postgres.pullPolicy
+			}
+			auth: {
+				username: "penpot"
+				password: "penpot"
+				database: "penpot"
+			}
+		}
+	}
+	penpot: {
+		chart: charts.penpot
+		values: {
+			"global": {
+				postgresqlEnabled: false
+				redisEnabled: true // TODO(gio): provide redis from outside
+			}
+			fullnameOverride: "penpot"
+			backend: {
+				image: {
+					repository: images.backend.fullName
+					tag: images.backend.tag
+					imagePullPolicy: images.backend.pullPolicy
+				}
+			}
+			frontend: {
+				image: {
+					repository: images.frontend.fullName
+					tag: images.frontend.tag
+					imagePullPolicy: images.frontend.pullPolicy
+				}
+				ingress: {
+					enabled: true
+					className: input.network.ingressClass
+					if input.network.certificateIssuer != "" {
+						annotations: {
+							"acme.cert-manager.io/http01-edit-in-place": "true"
+							"cert-manager.io/cluster-issuer": input.network.certificateIssuer
+						}
+					}
+					hosts: [_domain]
+					tls: [{
+						hosts: [_domain]
+						secretName: "cert-\(_domain)"
+					}]
+				}
+			}
+			persistence: enabled: true
+			config: {
+				publicURI: _domain
+				flags: "enable-login-with-oidc enable-registration enable-insecure-register disable-demo-users disable-demo-warning" // TODO(gio): remove enable-insecure-register?
+				postgresql: {
+					host: "postgres.\(release.namespace).svc.cluster.local"
+					database: "penpot"
+					username: "penpot"
+					password: "penpot"
+				}
+				redis: host: "penpot-redis-headless.\(release.namespace).svc.cluster.local"
+				providers: {
+					oidc: {
+						enabled: true
+						baseURI: "https://hydra.\(global.domain)"
+						clientID: ""
+						clientSecret: ""
+						authURI: ""
+						tokenURI: ""
+						userURI: ""
+						roles: ""
+						rolesAttribute: ""
+						scopes: ""
+						nameAttribute: "name"
+						emailAttribute: "email"
+					}
+					existingSecret: _oauth2SecretName
+					secretKeys: {
+						oidcClientIDKey: "client_id"
+						oidcClientSecretKey: "client_secret"
+					}
+				}
+			}
+			exporter: {
+				image: {
+					repository: images.exporter.fullName
+					tag: images.exporter.tag
+					imagePullPolicy: images.exporter.pullPolicy
+				}
+			}
+			redis: image: tag: "7.0.8-debian-11-r16"
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/penpot.jsonschema b/core/installer/values-tmpl/penpot.jsonschema
deleted file mode 100644
index 0824944..0000000
--- a/core/installer/values-tmpl/penpot.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Network": { "type": "string", "default": "Public", "role": "network" },
-    "Subdomain": { "type": "string", "default": "penpot" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/penpot.md b/core/installer/values-tmpl/penpot.md
deleted file mode 100644
index d750a90..0000000
--- a/core/installer/values-tmpl/penpot.md
+++ /dev/null
@@ -1 +0,0 @@
-Penpot application will be installed on {{ .Values.Network.Name }} network and be accessible to any user on https://{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
diff --git a/core/installer/values-tmpl/penpot.yaml b/core/installer/values-tmpl/penpot.yaml
deleted file mode 100644
index ae67f39..0000000
--- a/core/installer/values-tmpl/penpot.yaml
+++ /dev/null
@@ -1,96 +0,0 @@
-apiVersion: hydra.ory.sh/v1alpha1
-kind: OAuth2Client
-metadata:
-  name: penpot
-  namespace: {{ .Release.Namespace }}
-spec:
-  grantTypes:
-  - authorization_code
-  responseTypes:
-  - code
-  scope: "openid profile email"
-  secretName: oauth2-credentials # TODO(gio): config
-  redirectUris:
-  - https://{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}/api/auth/oauth/oidc/callback # TODO
-  hydraAdmin:
-    endpoint: /admin/clients
-    forwardedProto: https
-    port: 80
-    url: http://hydra-admin.esrt-core-auth.svc.cluster.local
-  tokenEndpointAuthMethod: client_secret_post
----
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: penpot
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/penpot
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    global:
-      postgresqlEnabled: true
-      redisEnabled: true
-    fullnameOverride: penpot
-    frontend:
-      ingress:
-        enabled: true
-        className: {{ .Values.Network.IngressClass }}
-        {{ if .Values.Network.CertificateIssuer }}
-        annotations:
-          acme.cert-manager.io/http01-edit-in-place: "true"
-          cert-manager.io/cluster-issuer: "{{ .Values.Network.CertificateIssuer }}"
-        {{ end }}
-        hosts:
-        - "{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}"
-        tls:
-        - hosts:
-          - "{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}"
-          secretName: cert-{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
-    persistence:
-      enabled: true
-    config:
-      publicURI: https://{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
-      # flags: "enable-registration enable-login"
-      flags: "enable-login-with-oidc enable-registration enable-insecure-register disable-demo-users disable-demo-warning" # TODO(gio): remove enable-insecure-register?
-      postgresql:
-        host: penpot-postgresql.{{ .Release.Namespace }}.svc.cluster.local
-        database: penpot
-        username: penpot
-        password: penpot
-      redis:
-        host: penpot-redis-headless.{{ .Release.Namespace }}.svc.cluster.local
-      providers:
-        oidc:
-          enabled: true
-          baseURI: https://hydra.{{ .Global.Domain }}
-          clientID: ""
-          clientSecret: ""
-          authURI: ""
-          tokenURI: ""
-          userURI: ""
-          roles: ""
-          rolesAttribute: ""
-          scopes: ""
-          nameAttribute: "name"
-          emailAttribute: "email"
-        existingSecret: oauth2-credentials
-        secretKeys:
-          oidcClientIDKey: client_id
-          oidcClientSecretKey: client_secret
-    redis:
-      image:
-        tag: 7.0.8-debian-11-r16
-    postgresql:
-      image:
-        tag: 15.3.0-debian-11-r16
-      auth:
-        username: penpot
-        password: penpot
-        database: penpot
diff --git a/core/installer/values-tmpl/pihole.cue b/core/installer/values-tmpl/pihole.cue
new file mode 100644
index 0000000..de4529c
--- /dev/null
+++ b/core/installer/values-tmpl/pihole.cue
@@ -0,0 +1,90 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "Installs pihole at https://\(_domain)"
+
+images: {
+	pihole: {
+		repository: "pihole"
+		name: "pihole"
+		tag: "v5.8.1"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	pihole: {
+		chart: "charts/pihole"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	pihole: {
+		chart: charts.pihole
+		values: {
+			domain: _domain
+			pihole: {
+				fullnameOverride: "pihole"
+				persistentVolumeClaim: { // TODO(gio): create volume separately as a dependency
+					enabled: true
+					size: "5Gi"
+				}
+				admin: {
+					enabled: false
+				}
+				ingress: {
+					enabled: false
+				}
+				serviceDhcp: {
+					enabled: false
+				}
+				serviceDns: {
+					type: "ClusterIP"
+				}
+				serviceWeb: {
+					type: "ClusterIP"
+					http: {
+						enabled: true
+					}
+					https: {
+						enabled: false
+					}
+				}
+				virtualHost: _domain
+				resources: {
+					requests: {
+						cpu: "250m"
+						memory: "100M"
+					}
+					limits: {
+						cpu: "500m"
+						memory: "250M"
+					}
+				}
+				image: {
+					repository: images.pihole.fullName
+					tag: images.pihole.tag
+					pullPolicy: images.pihole.pullPolicy
+				}
+			}
+			oauth2: {
+				secretName: "oauth2-secret"
+				configName: "oauth2-proxy"
+				hydraAdmin: "http://hydra-admin.\(global.namespacePrefix)core-auth.svc"
+			}
+			hydraPublic: "https://hydra.\(global.domain)"
+			profileUrl: "https://accounts-ui.\(global.domain)"
+			ingressClassName: input.network.ingressClass
+			certificateIssuer: input.network.certificateIssuer
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/pihole.jsonschema b/core/installer/values-tmpl/pihole.jsonschema
deleted file mode 100644
index 942b269..0000000
--- a/core/installer/values-tmpl/pihole.jsonschema
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Subdomain": { "type": "string", "default": "pihole" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/pihole.md b/core/installer/values-tmpl/pihole.md
deleted file mode 100644
index c8c690d..0000000
--- a/core/installer/values-tmpl/pihole.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs pihole at https://{{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
diff --git a/core/installer/values-tmpl/pihole.yaml b/core/installer/values-tmpl/pihole.yaml
deleted file mode 100644
index 1fe405d..0000000
--- a/core/installer/values-tmpl/pihole.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: pihole
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/pihole
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    domain: {{ .Values.Subdomain}}.{{ .Global.PrivateDomain }}
-    pihole:
-      fullnameOverride: pihole
-      image:
-        repository: "pihole/pihole"
-        tag: v5.8.1
-      persistentVolumeClaim:
-        enabled: true
-        size: 5Gi
-      admin:
-        enabled: false
-      ingress:
-        enabled: false
-      serviceDhcp:
-        enabled: false
-      serviceDns:
-        type: ClusterIP
-      serviceWeb:
-        type: ClusterIP
-        http:
-          enabled: true
-        https:
-          enabled: false
-      virtualHost: {{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
-      resources:
-        requests:
-          cpu: "250m"
-          memory: "100M"
-        limits:
-          cpu: "500m"
-          memory: "250M"
-    oauth2:
-      secretName: oauth2-secret
-      configName: oauth2-proxy
-      hydraAdmin: http://hydra-admin.{{ .Global.NamespacePrefix }}core-auth.svc
-    hydraPublic: https://hydra.{{ .Global.Domain }}
-    profileUrl: https://accounts-ui.{{ .Global.Domain }}
-    ingressClassName: {{ .Global.Id }}-ingress-private
diff --git a/core/installer/values-tmpl/private-network.cue b/core/installer/values-tmpl/private-network.cue
new file mode 100644
index 0000000..d888f27
--- /dev/null
+++ b/core/installer/values-tmpl/private-network.cue
@@ -0,0 +1,95 @@
+input: {
+	privateNetwork: {
+		hostname: string
+		username: string
+		ipSubnet: string // TODO(gio): use cidr type
+	}
+}
+
+images: {
+	"ingress-nginx": {
+		registry: "registry.k8s.io"
+		repository: "ingress-nginx"
+		name: "controller"
+		tag: "v1.8.0"
+		pullPolicy: "IfNotPresent"
+	}
+	"tailscale-proxy": {
+		repository: "tailscale"
+		name: "tailscale"
+		tag: "v1.42.0"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	"ingress-nginx": {
+		chart: "charts/ingress-nginx"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+	"tailscale-proxy": {
+		chart: "charts/tailscale-proxy"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"ingress-nginx": {
+		chart: charts["ingress-nginx"]
+		values: {
+			fullnameOverride: "\(global.id)-nginx-private"
+			controller: {
+				service: {
+					enabled: true
+					type: "LoadBalancer"
+					annotations: {
+						"metallb.universe.tf/address-pool": _ingressPrivate
+					}
+				}
+				ingressClassByName: true
+				ingressClassResource: {
+					name: _ingressPrivate
+					enabled: true
+					default: false
+					controllerValue: "k8s.io/\(_ingressPrivate)"
+				}
+				extraArgs: {
+					"default-ssl-certificate": "\(_ingressPrivate)/cert-wildcard.\(global.privateDomain)"
+				}
+				admissionWebhooks: {
+					enabled: false
+				}
+				image: {
+					registry: images["ingress-nginx"].registry
+					image: images["ingress-nginx"].imageName
+					tag: images["ingress-nginx"].tag
+					pullPolicy: images["ingress-nginx"].pullPolicy
+				}
+			}
+		}
+	}
+	"tailscale-proxy": {
+		chart: charts["tailscale-proxy"]
+		values: {
+			hostname: input.privateNetwork.hostname
+			apiServer: "http://headscale-api.\(global.namespacePrefix)app-headscale.svc.cluster.local"
+			loginServer: "https://headscale.\(global.domain)" // TODO(gio): take headscale subdomain from configuration
+			ipSubnet: input.privateNetwork.ipSubnet
+			username: input.privateNetwork.username // TODO(gio): maybe install headscale-user chart separately?
+			preAuthKeySecret: "headscale-preauth-key"
+			image: {
+				repository: images["tailscale-proxy"].fullName
+				tag: images["tailscale-proxy"].tag
+				pullPolicy: images["tailscale-proxy"].pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/private-network.jsonschema b/core/installer/values-tmpl/private-network.jsonschema
deleted file mode 100644
index 7f40a40..0000000
--- a/core/installer/values-tmpl/private-network.jsonschema
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-	"PrivateNetwork": {
-	  "type": "object",
-	  "properties": {
-		"Hostname": { "type": "string", "default": "10.1.0.1" },
-		"Username": { "type": "string", "default": "example" },
-		"IPSubnet": { "type": "string", "default": "10.1.0.1" }
-	  }
-	}
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/private-network.md b/core/installer/values-tmpl/private-network.md
deleted file mode 100644
index 35a878f..0000000
--- a/core/installer/values-tmpl/private-network.md
+++ /dev/null
@@ -1 +0,0 @@
-{{ .Global.PrivateDomain }}
diff --git a/core/installer/values-tmpl/qbittorrent.cue b/core/installer/values-tmpl/qbittorrent.cue
new file mode 100644
index 0000000..b5dbf62
--- /dev/null
+++ b/core/installer/values-tmpl/qbittorrent.cue
@@ -0,0 +1,50 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "qbittorrent application will be installed on \(input.network.name) network and be accessible to any user on https://\(_domain)"
+
+images: {
+	qbittorrent: {
+		registry: "lscr.io"
+		repository: "linuxserver"
+		name: "qbittorrent"
+		tag: "4.5.3"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	qbittorrent: {
+		chart: "charts/qbittorrent"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	qbittorrent: {
+		chart: charts.qbittorrent
+		values: {
+			pcloudInstanceId: global.id
+			ingress: {
+				className: input.network.ingressClass
+				domain: _domain
+			}
+			webui: port: 8080
+			bittorrent: port: 6881
+			storage: size: "100Gi"
+			image: {
+				repository: images.qbittorrent.fullName
+				tag: images.qbittorrent.tag
+				pullPolicy: images.qbittorrent.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/qbittorrent.jsonschema b/core/installer/values-tmpl/qbittorrent.jsonschema
deleted file mode 100644
index ec6a2c5..0000000
--- a/core/installer/values-tmpl/qbittorrent.jsonschema
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Values": {
-      "type": "object",
-      "properties": {
-        "NamespacePrefix": { "type": "string" },
-        "Id": { "type": "string" },
-        "Domain": { "type": "string" }
-      },
-      "additionalProperties": false
-    }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/qbittorrent.md b/core/installer/values-tmpl/qbittorrent.md
deleted file mode 100644
index 7abc4d9..0000000
--- a/core/installer/values-tmpl/qbittorrent.md
+++ /dev/null
@@ -1 +0,0 @@
-https://{{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
diff --git a/core/installer/values-tmpl/qbittorrent.yaml b/core/installer/values-tmpl/qbittorrent.yaml
deleted file mode 100644
index 0597595..0000000
--- a/core/installer/values-tmpl/qbittorrent.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: qbittorrent
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/qbittorrent
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    pcloudInstanceId: {{ .Global.Id }}
-    image:
-      repository: lscr.io/linuxserver/qbittorrent
-      tag: 4.5.3
-      pullPolicy: IfNotPresent
-    ingress:
-      className: {{ .Global.Id }}-ingress-private
-      domain: {{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
-    webui:
-      port: 8080
-    bittorrent:
-      port: 6881
-    storage:
-      size: 1Ti
diff --git a/core/installer/values-tmpl/resource-renderer-controller.cue b/core/installer/values-tmpl/resource-renderer-controller.cue
new file mode 100644
index 0000000..b65a0ea
--- /dev/null
+++ b/core/installer/values-tmpl/resource-renderer-controller.cue
@@ -0,0 +1,49 @@
+input: {}
+
+images: {
+	resourceRenderer: {
+		repository: "giolekva"
+		name: "resource-renderer-controller"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+	kubeRBACProxy: {
+		registry: "gcr.io"
+		repository: "kubebuilder"
+		name: "kube-rbac-proxy"
+		tag: "v0.13.0"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	resourceRenderer: {
+		chart: "charts/resource-renderer-controller"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.pcloudEnvName
+		}
+	}
+}
+
+helm: {
+	"resource-renderer": {
+		chart: charts.resourceRenderer
+		values: {
+			installCRDs: true
+			image: {
+				repository: images.resourceRenderer.fullName
+				tag: images.resourceRenderer.tag
+				pullPolicy: images.resourceRenderer.pullPolicy
+			}
+			kubeRBACProxy: {
+				image: {
+					repository: images.kubeRBACProxy.fullName
+					tag: images.kubeRBACProxy.tag
+					pullPolicy: images.kubeRBACProxy.pullPolicy
+				}
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/resource-renderer-controller.jsonschema b/core/installer/values-tmpl/resource-renderer-controller.jsonschema
deleted file mode 100644
index f42d895..0000000
--- a/core/installer/values-tmpl/resource-renderer-controller.jsonschema
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/resource-renderer-controller.md b/core/installer/values-tmpl/resource-renderer-controller.md
deleted file mode 100644
index 81231e0..0000000
--- a/core/installer/values-tmpl/resource-renderer-controller.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs resource-renderer controller
diff --git a/core/installer/values-tmpl/resource-renderer-controller.yaml b/core/installer/values-tmpl/resource-renderer-controller.yaml
deleted file mode 100644
index 7ffa9d0..0000000
--- a/core/installer/values-tmpl/resource-renderer-controller.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: rr-controller
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/resource-renderer-controller
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.PCloudEnvName }}
-  interval: 1m0s
-  values:
diff --git a/core/installer/values-tmpl/rpuppy.cue b/core/installer/values-tmpl/rpuppy.cue
index b9d21bc..59f559b 100644
--- a/core/installer/values-tmpl/rpuppy.cue
+++ b/core/installer/values-tmpl/rpuppy.cue
@@ -1,10 +1,8 @@
-#Input: {
+input: {
 	network: #Network
 	subdomain: string
 }
 
-input: #Input
-
 _domain: "\(input.subdomain).\(input.network.domain)"
 
 readme: "rpuppy application will be installed on \(input.network.name) network and be accessible to any user on https://\(_domain)"
@@ -44,87 +42,3 @@
 		}
 	}
 }
-
-// TODO(gio): import
-
-#Network: {
-	name: string
-	ingressClass: string
-	certificateIssuer: string
-	domain: string
-}
-
-#Image: {
-	registry: string | *"docker.io"
-	repository: string
-	name: string
-	tag: string
-	pullPolicy: string // TODO(gio): remove?
-	fullName: "\(registry)/\(repository)/\(name)"
-	fullNameWithTag: "\(fullName):\(tag)"
-}
-
-#Chart: {
-	chart: string
-	sourceRef: #SourceRef
-}
-
-#SourceRef: {
-	kind: "GitRepository" | "HelmRepository"
-	name: string
-	namespace: string // TODO(gio): default global.id
-}
-
-#Global: {
-	id: string
-	...
-}
-
-#Release: {
-	namespace: string
-}
-
-global: #Global
-release: #Release
-
-images: {
-	for key, value in images {
-		"\(key)": #Image & value
-	}
-}
-
-charts: {
-	for key, value in charts {
-		"\(key)": #Chart & value
-	}
-}
-
-#HelmRelease: {
-	_name: string
-	_chart: #Chart
-	_values: _
-
-	apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
-	kind: "HelmRelease"
-	metadata: {
-		name: _name
-   		namespace: release.namespace
-	}
-	spec: {
-		interval: "1m0s"
-		chart: {
-			spec: _chart
-		}
-		values: _values
-	}
-}
-
-output: {
-	for name, r in helm {
-		"\(name)": #HelmRelease & {
-			_name: name
-			_chart: r.chart
-			_values: r.values
-		}
-	}
-}
diff --git a/core/installer/values-tmpl/rpuppy.jsonschema b/core/installer/values-tmpl/rpuppy.jsonschema
deleted file mode 100644
index d6e0d0d..0000000
--- a/core/installer/values-tmpl/rpuppy.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Network": { "type": "string", "default": "Public", "role": "network" },
-    "Subdomain": { "type": "string", "default": "woof" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/rpuppy.md b/core/installer/values-tmpl/rpuppy.md
deleted file mode 100644
index ddd992f..0000000
--- a/core/installer/values-tmpl/rpuppy.md
+++ /dev/null
@@ -1 +0,0 @@
-rpuppy application will be installed on {{ .Values.Network.Name }} network and be accessible to any user on https://{{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
diff --git a/core/installer/values-tmpl/rpuppy.yaml b/core/installer/values-tmpl/rpuppy.yaml
deleted file mode 100644
index 10fac11..0000000
--- a/core/installer/values-tmpl/rpuppy.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: rpuppy
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/rpuppy
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    ingressClassName: {{ .Values.Network.IngressClass }}
-    certificateIssuer: {{ .Values.Network.CertificateIssuer }}
-    domain: {{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
diff --git a/core/installer/values-tmpl/soft-serve.cue b/core/installer/values-tmpl/soft-serve.cue
new file mode 100644
index 0000000..1ed8444
--- /dev/null
+++ b/core/installer/values-tmpl/soft-serve.cue
@@ -0,0 +1,50 @@
+input: {
+	subdomain: string
+	adminKey: string
+}
+
+_domain: "\(input.subdomain).\(global.privateDomain)"
+
+readme: "softserve application will be installed on private network and be accessible to any user on https://\(_domain)" // TODO(gio): make public network an option
+
+images: {
+	softserve: {
+		repository: "charmcli"
+		name: "soft-serve"
+		tag: "v0.7.1"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	softserve: {
+		chart: "charts/soft-serve"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	softserve: {
+		chart: charts.softserve
+		values: {
+			serviceType: "LoadBalancer"
+			reservedIP: ""
+			addressPool: global.id
+			adminKey: input.adminKey
+			privateKey: ""
+			publicKey: ""
+			ingress: {
+				enabled: false
+			}
+			image: {
+				repository: images.softserve.fullName
+				tag: images.softserve.tag
+				pullPolicy: images.softserve.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/soft-serve.jsonschema b/core/installer/values-tmpl/soft-serve.jsonschema
deleted file mode 100644
index 8142797..0000000
--- a/core/installer/values-tmpl/soft-serve.jsonschema
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Network": { "type": "string", "default": "Public", "role": "network" },
-    "Subdomain": { "type": "string", "default": "softserve" },
-	"AdminKey": { "type": "string" },
-	"SourcePort": { "type": "string", "default": "0" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/soft-serve.md b/core/installer/values-tmpl/soft-serve.md
deleted file mode 100644
index 28e43a8..0000000
--- a/core/installer/values-tmpl/soft-serve.md
+++ /dev/null
@@ -1 +0,0 @@
-Soft-Serve with TCP ingress
diff --git a/core/installer/values-tmpl/soft-serve.yaml b/core/installer/values-tmpl/soft-serve.yaml
deleted file mode 100644
index 1c6fa87..0000000
--- a/core/installer/values-tmpl/soft-serve.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: soft-serve
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/soft-serve
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ or .Values.ChartRepositoryNamespace .Global.Id }}
-  interval: 1m0s
-  values:
-    {{- if .Values.ServiceType }}
-    serviceType: {{ .Values.ServiceType }}
-    {{- end }}
-    reservedIP: ""
-    addressPool: {{ .Global.Id }}
-    adminKey: {{ .Values.AdminKey }}
-    {{- if and .Values.PrivateKey .Values.PublicKey }}
-    privateKey: |
-{{ .Values.PrivateKey | indent 6 }}
-    publicKey: {{ .Values.PublicKey }}
-    {{- end }}
-    {{- if .Values.Network }}
-    ingress:
-      enabled: {{ .Values.Ingress.Enabled }}
-      ingressClassName: {{ .Values.Network.IngressClass }}
-      certificateIssuer: {{ .Values.Network.CertificateIssuer }}
-      domain: {{ .Values.Subdomain }}.{{ .Values.Network.Domain }}
-      {{- if .Values.SourcePort }}
-      sourcePort: {{ .Values.SourcePort }}
-      {{- end }}
-    {{- end }}
diff --git a/core/installer/values-tmpl/tailscale-proxy.yaml b/core/installer/values-tmpl/tailscale-proxy.yaml
deleted file mode 100644
index 047c196..0000000
--- a/core/installer/values-tmpl/tailscale-proxy.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: tailscale-proxy
-  namespace: {{ .Release.Namespace }}
-spec:
-  dependsOn:
-    - name: headscale
-      namespace: {{ .Global.NamespacePrefix }}app-headscale
-  chart:
-    spec:
-      chart: charts/tailscale-proxy
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    hostname: {{ .Values.PrivateNetwork.Hostname}}
-    apiServer: http://headscale-api.{{ .Global.Id }}-app-headscale.svc.cluster.local
-    loginServer: https://headscale.{{ .Global.Domain }} # TODO(gio): take headscale subdomain from configuration
-    ipSubnet: {{ .Values.PrivateNetwork.IPSubnet }}
-    username: {{ .Values.PrivateNetwork.Username }} # TODO(gio): maybe install headscale-user chart separately?
-    preAuthKeySecret: headscale-preauth-key
diff --git a/core/installer/values-tmpl/vaultwarden.cue b/core/installer/values-tmpl/vaultwarden.cue
new file mode 100644
index 0000000..58eb476
--- /dev/null
+++ b/core/installer/values-tmpl/vaultwarden.cue
@@ -0,0 +1,44 @@
+input: {
+	network: #Network
+	subdomain: string
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+
+readme: "Installs vaultwarden on private network accessible at \(_domain)"
+
+images: {
+	vaultwarden: {
+		repository: "vaultwarden"
+		name: "server"
+		tag: "1.28.1"
+		pullPolicy: "IfNotPresent"
+	}
+}
+
+charts: {
+	vaultwarden: {
+		chart: "charts/vaultwarden"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	vaultwarden: {
+		chart: charts.vaultwarden
+		values: {
+			ingressClassName: input.network.ingressClass
+			certificateIssuer: input.network.certificateIssuer
+			domain: _domain
+			image: {
+				repository: images.vaultwarden.fullName
+				tag: images.vaultwarden.tag
+				pullPolicy: images.vaultwarden.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/vaultwarden.jsonschema b/core/installer/values-tmpl/vaultwarden.jsonschema
deleted file mode 100644
index 88c96cb..0000000
--- a/core/installer/values-tmpl/vaultwarden.jsonschema
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "Subdomain": { "type": "string", "default": "vaultwarden" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/vaultwarden.md b/core/installer/values-tmpl/vaultwarden.md
deleted file mode 100644
index ab9d05d..0000000
--- a/core/installer/values-tmpl/vaultwarden.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs vaultwarden on private network accessible at https://{{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
diff --git a/core/installer/values-tmpl/vaultwarden.yaml b/core/installer/values-tmpl/vaultwarden.yaml
deleted file mode 100644
index 4419776..0000000
--- a/core/installer/values-tmpl/vaultwarden.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: vaultwarden
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/vaultwarden
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    image:
-      repository: vaultwarden/server
-      tag: 1.28.1
-      pullPolicy: IfNotPresent
-    storage:
-      size: 3Gi
-    domain: {{ .Values.Subdomain }}.{{ .Global.PrivateDomain }}
-    certificateIssuer: {{ .Global.Id }}-private
-    ingressClassName: {{ .Global.Id }}-ingress-private
diff --git a/core/installer/values-tmpl/welcome.cue b/core/installer/values-tmpl/welcome.cue
new file mode 100644
index 0000000..2da8f67
--- /dev/null
+++ b/core/installer/values-tmpl/welcome.cue
@@ -0,0 +1,51 @@
+import (
+	"encoding/base64"
+)
+
+input: {
+	repoAddr: string
+	sshPrivateKey: string
+	createAccountAddr: string
+}
+
+images: {
+	welcome: {
+		repository: "giolekva"
+		name: "pcloud-installer"
+		tag: "latest"
+		pullPolicy: "Always"
+	}
+}
+
+charts: {
+	welcome: {
+		chart: "charts/welcome"
+		sourceRef: {
+			kind: "GitRepository"
+			name: "pcloud"
+			namespace: global.id
+		}
+	}
+}
+
+helm: {
+	welcome: {
+		chart: charts.welcome
+		values: {
+			repoAddr: input.repoAddr
+			sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
+			createAccountAddr: "http://api.\(global.namespacePrefix)core-auth.svc.cluster.local/identities"
+			ingress: {
+				className: _ingressPublic
+				domain: "welcome.\(global.domain)"
+				certificateIssuer: _issuerPublic
+			}
+			clusterRoleName: "\(global.id)-welcome"
+			image: {
+				repository: images.welcome.fullName
+				tag: images.welcome.tag
+				pullPolicy: images.welcome.pullPolicy
+			}
+		}
+	}
+}
diff --git a/core/installer/values-tmpl/welcome.jsonschema b/core/installer/values-tmpl/welcome.jsonschema
deleted file mode 100644
index 8a011ca..0000000
--- a/core/installer/values-tmpl/welcome.jsonschema
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "type": "object",
-  "properties": {
-    "RepoAddr": { "type": "string", "default": "ssh://192.168.0.11/example" },
-	"SSHPrivateKey": { "type": "string", "default": "foo bar" }
-  },
-  "additionalProperties": false
-}
diff --git a/core/installer/values-tmpl/welcome.md b/core/installer/values-tmpl/welcome.md
deleted file mode 100644
index 43d5021..0000000
--- a/core/installer/values-tmpl/welcome.md
+++ /dev/null
@@ -1 +0,0 @@
-Installs welcome service
diff --git a/core/installer/values-tmpl/welcome.yaml b/core/installer/values-tmpl/welcome.yaml
deleted file mode 100644
index d871c90..0000000
--- a/core/installer/values-tmpl/welcome.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-apiVersion: helm.toolkit.fluxcd.io/v2beta1
-kind: HelmRelease
-metadata:
-  name: welcome
-  namespace: {{ .Release.Namespace }}
-spec:
-  chart:
-    spec:
-      chart: charts/welcome
-      sourceRef:
-        kind: GitRepository
-        name: pcloud
-        namespace: {{ .Global.Id }}
-  interval: 1m0s
-  values:
-    repoAddr: {{ .Values.RepoAddr }}
-    sshPrivateKey: {{ .Values.SSHPrivateKey | b64enc }}
-    createAccountAddr: http://api.{{ .Global.NamespacePrefix}}core-auth.svc.cluster.local/identities
-    ingress:
-      className: {{ .Global.PCloudEnvName }}-ingress-public
-      domain: welcome.{{ .Global.Domain }}
-      certificateIssuer: {{ .Global.Id }}-public
-    clusterRoleName: {{ .Global.Id }}-welcome