Immich: Add Immich installer configuration

Change-Id: I88796e42d0b0b14d84b094d4a1c5213b43c56f2a
diff --git a/core/installer/app_repository.go b/core/installer/app_repository.go
index e0f97fc..cc3a35a 100644
--- a/core/installer/app_repository.go
+++ b/core/installer/app_repository.go
@@ -24,6 +24,7 @@
 	// "values-tmpl/coder.cue",
 	"values-tmpl/url-shortener.cue",
 	"values-tmpl/matrix.cue",
+	"values-tmpl/immich.cue",
 	"values-tmpl/vaultwarden.cue",
 	"values-tmpl/etherpad.cue",
 	// "values-tmpl/open-project.cue",
diff --git a/core/installer/values-tmpl/immich.cue b/core/installer/values-tmpl/immich.cue
new file mode 100644
index 0000000..6635d8b
--- /dev/null
+++ b/core/installer/values-tmpl/immich.cue
@@ -0,0 +1,343 @@
+import (
+	"encoding/yaml"
+	"encoding/json"
+)
+
+input: {
+	network:   #Network @name(Network)
+	subdomain: string   @name(Subdomain)
+	mapEnabled: bool | *false @name(Map)
+	storageSize: string | *"10Gi" @name(Storage Size)
+}
+
+_domain: "\(input.subdomain).\(input.network.domain)"
+url:     "https://\(_domain)"
+
+name:        "Immich"
+namespace:   "app-immich"
+readme:      "Self-hosted photo and video management solution"
+description: "Easily back up, organize, and manage your photos on your own server. Immich helps you browse, search and organize your photos and videos with ease, without sacrificing your privacy."
+icon: """
+<svg width='50px' height='50px' version="1.1" id="Flower" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 792 792" style="enable-background:new 0 0 792 792;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FA2921;}
+	.st1{fill:#ED79B5;}
+	.st2{fill:#FFB400;}
+	.st3{fill:#1E83F7;}
+	.st4{fill:#18C249;}
+</style>
+<g id="Flower_00000077325900055813483940000000694823054982625702_">
+	<path class="st0" d="M375.48,267.63c38.64,34.21,69.78,70.87,89.82,105.42c34.42-61.56,57.42-134.71,57.71-181.3
+		c0-0.33,0-0.63,0-0.91c0-68.94-68.77-95.77-128.01-95.77s-128.01,26.83-128.01,95.77c0,0.94,0,2.2,0,3.72
+		C300.01,209.24,339.15,235.47,375.48,267.63z"/>
+	<path class="st1" d="M164.7,455.63c24.15-26.87,61.2-55.99,103.01-80.61c44.48-26.18,88.97-44.47,128.02-52.84
+		c-47.91-51.76-110.37-96.24-154.6-110.91c-0.31-0.1-0.6-0.19-0.86-0.28c-65.57-21.3-112.34,35.81-130.64,92.15
+		c-18.3,56.34-14.04,130.04,51.53,151.34C162.05,454.77,163.25,455.16,164.7,455.63z"/>
+	<path class="st2" d="M681.07,302.19c-18.3-56.34-65.07-113.45-130.64-92.15c-0.9,0.29-2.1,0.68-3.54,1.15
+		c-3.75,35.93-16.6,81.27-35.96,125.76c-20.59,47.32-45.84,88.27-72.51,118c69.18,13.72,145.86,12.98,190.26-1.14
+		c0.31-0.1,0.6-0.2,0.86-0.28C695.11,432.22,699.37,358.52,681.07,302.19z"/>
+	<path class="st3" d="M336.54,510.71c-11.15-50.39-14.8-98.36-10.7-138.08c-64.03,29.57-125.63,75.23-153.26,112.76
+		c-0.19,0.26-0.37,0.51-0.53,0.73c-40.52,55.78-0.66,117.91,47.27,152.72c47.92,34.82,119.33,53.54,159.86-2.24
+		c0.56-0.76,1.3-1.78,2.19-3.01C363.28,602.32,347.02,558.08,336.54,510.71z"/>
+	<path class="st4" d="M617.57,482.52c-35.33,7.54-82.42,9.33-130.72,4.66c-51.37-4.96-98.11-16.32-134.63-32.5
+		c8.33,70.03,32.73,142.73,59.88,180.6c0.19,0.26,0.37,0.51,0.53,0.73c40.52,55.78,111.93,37.06,159.86,2.24
+		c47.92-34.82,87.79-96.95,47.27-152.72C619.2,484.77,618.46,483.75,617.57,482.52z"/>
+</g>
+</svg>"""
+
+out: {
+	ingress: {
+		immich: {
+			auth: enabled: false
+			network:   input.network
+			subdomain: input.subdomain
+			service: {
+				name: "immich-server"
+				port: _httpPort
+			}
+		}
+	}
+
+	// TODO(gio): configure busybox
+	images: {
+		immich: {
+			registry:   "ghcr.io"
+			repository: "immich-app"
+			name:       "immich-server"
+			tag:        "v1.135.3"
+			pullPolicy: "IfNotPresent"
+		}
+		ml: {
+			registry:   "ghcr.io"
+			repository: "immich-app"
+			name:       "immich-machine-learning"
+			tag:        "v1.135.3"
+			pullPolicy: "IfNotPresent"
+		}
+	}
+
+	charts: {
+		immich: {
+			kind:    "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch:  "main"
+			path:    "charts/immich"
+		}
+		oauth2Client: {
+			kind:    "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch:  "main"
+			path:    "charts/oauth2-client"
+		}
+		resourceRenderer: {
+			kind:    "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch:  "main"
+			path:    "charts/resource-renderer"
+		}
+	}
+
+	volume: immich: size: input.storageSize
+
+	_httpPort:               2283
+	_oauth2ClientSecretName: "oauth2-client"
+
+	helm: {
+		"oauth2-client": {
+			chart: charts.oauth2Client
+			info:  "Creating OAuth2 client"
+			// TODO(gio): remove once hydra maester is installed as part of dodo itself
+			dependsOn: [{
+				name:      "auth"
+				namespace: "\(global.namespacePrefix)core-auth"
+			}]
+			values: {
+				name:       "\(release.namespace)-immich"
+				secretName: _oauth2ClientSecretName
+				grantTypes: ["authorization_code"]
+				responseTypes: ["code"]
+				scope: "openid profile email groups"
+				redirectUris: [
+					"app.immich:///oauth-callback",
+					"https://\(_domain)/auth/login",
+					"https://\(_domain)/user-settings",
+ 			    ]
+				tokenEndpointAuthMethod: "client_secret_post"
+				hydraAdmin: "http://hydra-admin.\(global.namespacePrefix)core-auth.svc.cluster.local"
+			}
+		}
+		"config-renderer": {
+			chart: charts.resourceRenderer
+			info:  "Generating Immich configuration"
+			values: {
+				name:       "config-renderer"
+				secretName: _oauth2ClientSecretName
+				resourceTemplate: yaml.Marshal({
+					apiVersion: "v1"
+					kind:       "ConfigMap"
+					metadata: {
+						name:      _immichConfigMapName
+						namespace: "\(release.namespace)"
+					}
+					data: {
+						"immich-config.json": json.Marshal({
+							{
+								"ffmpeg": {
+									"crf":              23
+									"threads":          0
+									"preset":           "ultrafast"
+									"targetVideoCodec": "h264"
+									"acceptedVideoCodecs": ["h264"]
+									"targetAudioCodec": "aac"
+									"acceptedAudioCodecs": ["aac", "mp3", "libopus", "pcm_s16le"]
+									"acceptedContainers": ["mov", "ogg", "webm"]
+									"targetResolution":  "720"
+									"maxBitrate":        "0"
+									"bframes":           -1
+									"refs":              0
+									"gopSize":           0
+									"temporalAQ":        false
+									"cqMode":            "auto"
+									"twoPass":           false
+									"preferredHwDevice": "auto"
+									"transcode":         "required"
+									"tonemap":           "hable"
+									"accel":             "disabled"
+									"accelDecode":       false
+								}
+								"backup": {
+									"database": {
+										"enabled":        true
+										"cronExpression": "0 02 * * *"
+										"keepLastAmount": 14
+									}
+								}
+								"job": {
+									"backgroundTask": {
+										"concurrency": 5
+									}
+									"smartSearch": {
+										"concurrency": 2
+									}
+									"metadataExtraction": {
+										"concurrency": 5
+									}
+									"faceDetection": {
+										"concurrency": 2
+									}
+									"search": {
+										"concurrency": 5
+									}
+									"sidecar": {
+										"concurrency": 5
+									}
+									"library": {
+										"concurrency": 5
+									}
+									"migration": {
+										"concurrency": 5
+									}
+									"thumbnailGeneration": {
+										"concurrency": 3
+									}
+									"videoConversion": {
+										"concurrency": 1
+									}
+									"notifications": {
+										"concurrency": 5
+									}
+								}
+								"logging": {
+									"enabled": true
+									"level":   "log"
+								}
+								"machineLearning": {
+									"enabled": true
+									"urls": ["http://immich-machine-learning:3003"]
+									"clip": {
+										"enabled":   true
+										"modelName": "ViT-B-32__openai"
+									}
+									"duplicateDetection": {
+										"enabled":     true
+										"maxDistance": 0.01
+									}
+									"facialRecognition": {
+										"enabled":     true
+										"modelName":   "buffalo_l"
+										"minScore":    0.7
+										"maxDistance": 0.5
+										"minFaces":    3
+									}
+								}
+								"map": {
+									"enabled":    input.mapEnabled
+									"lightStyle": "https://tiles.immich.cloud/v1/style/light.json"
+									"darkStyle":  "https://tiles.immich.cloud/v1/style/dark.json"
+								}
+								"reverseGeocoding": {
+									"enabled": true
+								}
+								"metadata": {
+									"faces": {
+										"import": false
+									}
+								}
+								"oauth": {
+									"autoLaunch":              true
+									"autoRegister":            true
+									"buttonText":              "Login with dodo"
+									"clientId":                "{{ .client_id }}"
+									"clientSecret":            "{{ .client_secret }}"
+									"defaultStorageQuota":     null
+									"enabled":                 true
+									"issuerUrl":               "https://hydra.\(networks.public.domain)"
+									"mobileOverrideEnabled":   false
+									"mobileRedirectUri":       ""
+									"scope":                   "openid email profile"
+									"signingAlgorithm":        "RS256"
+									"profileSigningAlgorithm": "none"
+									"storageLabelClaim":       "preferred_username"
+									"storageQuotaClaim":       "immich_quota"
+								}
+								"passwordLogin": {
+									"enabled": false
+								}
+								"storageTemplate": {
+									"enabled":                 false
+									"hashVerificationEnabled": true
+									"template":                "{{`{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}`}}"
+								}
+								"image": {
+									"thumbnail": {
+										"format":  "webp"
+										"size":    250
+										"quality": 80
+									}
+									"preview": {
+										"format":  "jpeg"
+										"size":    1440
+										"quality": 80
+									}
+									"colorspace":      "p3"
+									"extractEmbedded": false
+								}
+								"newVersionCheck": {
+									"enabled": true
+								}
+								"trash": {
+									"enabled": true
+									"days":    30
+								}
+								"theme": {
+									"customCss": ""
+								}
+								"library": {
+									"scan": {
+										"enabled":        true
+										"cronExpression": "0 0 * * *"
+									}
+									"watch": {
+										"enabled": false
+									}
+								}
+								"server": {
+									"externalDomain":   "https://\(_domain)"
+									"loginPageMessage": ""
+								}
+								"notifications": {
+									"smtp": {
+										"enabled": false
+										"from":    ""
+										"replyTo": ""
+										"transport": {
+											"ignoreCert": false
+											"host":       ""
+											"port":       587
+											"username":   ""
+											"password":   ""
+										}
+									}
+								}
+								"user": {
+									"deleteDelay": 7
+								}
+							}})
+					}
+				})
+			}
+		}
+		immich: {
+			chart: charts.immich
+			info:  "Installing Immich server"
+			values: {
+				image: tag: "v1.135.3"
+				immich: existingConfiguration: _immichConfigMapName
+				persistence: library: existingClaim: "immich"
+			}
+		}
+	}
+}
+
+_immichConfigMapName: "immich-config"