PrivateNetwork: Configure cluster proxy backend

Change-Id: Ieeca2da6bd69ee3a440960ff3d4cdb9371f3a8c6
diff --git a/core/headscale/main.go b/core/headscale/main.go
index c2cfcf5..d4a129f 100644
--- a/core/headscale/main.go
+++ b/core/headscale/main.go
@@ -131,7 +131,7 @@
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	if err := s.client.createUser(req.Name); err != nil {
+	if err := s.client.createUser(req.Name); err != nil && !errors.Is(err, ErrorAlreadyExists) {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
diff --git a/core/installer/values-tmpl/private-network.cue b/core/installer/values-tmpl/private-network.cue
index c22e5fc..b43ab1d 100644
--- a/core/installer/values-tmpl/private-network.cue
+++ b/core/installer/values-tmpl/private-network.cue
@@ -23,7 +23,13 @@
 			tag: "v1.8.0"
 			pullPolicy: "IfNotPresent"
 		}
-		"tailscale-proxy": {
+		nginx: {
+			repository: "library"
+			name: "nginx"
+			tag: "1.27.1-alpine3.20-slim"
+			pullPolicy: "IfNotPresent"
+		}
+		tailscale: {
 			repository: "tailscale"
 			name: "tailscale"
 			tag: "v1.42.0"
@@ -44,6 +50,12 @@
 			branch: "main"
 			path: "charts/access-secrets"
 		}
+		service: {
+			kind: "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch: "main"
+			path: "charts/service"
+		}
 		"ingress-nginx": {
 			kind: "GitRepository"
 			address: "https://code.v1.dodo.cloud/helm-charts"
@@ -62,6 +74,12 @@
 			branch: "main"
 			path: "charts/port-allocator"
 		}
+		headscaleUser: {
+			kind: "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch: "main"
+			path: "charts/headscale-user"
+		}
 	}
 
 	_ingressPrivate: "\(global.id)-ingress-private"
@@ -79,6 +97,18 @@
 				serviceAccountName: "\(global.id)-nginx-private"
 			}
 		}
+		"headscale-user": {
+			chart: charts.headscaleUser
+			values: {
+				resourceName: "private-network-proxy-backend"
+				username: "private-network-proxy"
+				headscaleApiAddress: "http://headscale-api.\(global.namespacePrefix)app-headscale.svc.cluster.local"
+				preAuthKey: {
+					enabled: true
+					secretName: _clusterProxySecretName
+				}
+			}
+		}
 		"ingress-nginx": {
 			chart: charts["ingress-nginx"]
 			values: {
@@ -108,6 +138,49 @@
 					extraArgs: {
 						"default-ssl-certificate": "\(_ingressPrivate)/cert-wildcard.\(global.privateDomain)"
 					}
+					extraVolumes: [{
+						name: _proxyBackendConfigName
+						configMap: {
+							name: _proxyBackendConfigName
+						}
+					}]
+					extraContainers: [{
+						name: "proxy"
+						image: images.tailscale.fullNameWithTag
+						securityContext: {
+							capabilities: {
+								add: ["NET_ADMIN"]
+							}
+							privileged: true
+						}
+						env: [{
+							name: "TS_KUBE_SECRET"
+							value: _clusterProxySecretName
+						}, {
+							name: "TS_HOSTNAME"
+							value: "cluster-proxy"
+						}, {
+							name: "TS_EXTRA_ARGS"
+							value: "--login-server=https://headscale.\(global.domain)"
+						}, {
+							name: "TS_USERSPACE"
+							value: "false"
+						}]
+					}, {
+						name: "proxy-backend"
+						image: images.nginx.fullNameWithTag
+						imagePullPolicy: images.nginx.pullPolicy
+						ports: [{
+							name: "proxy"
+							containerPort: 9090
+							protocol: "TCP"
+						}]
+						volumeMounts: [{
+							name: _proxyBackendConfigName
+							mountPath: "/etc/nginx"
+							readOnly: true
+						}]
+					}]
 					admissionWebhooks: {
 						enabled: false
 					}
@@ -130,9 +203,9 @@
 				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
+					repository: images.tailscale.fullName
+					tag: images.tailscale.tag
+					pullPolicy: images.tailscale.pullPolicy
 				}
 			}
 		}
@@ -149,5 +222,57 @@
 				}
 			}
 		}
+		// TODO(gio): Generate proxy-backend-config as well
+		"proxy-backend-service": {
+			chart: charts.service
+			values: {
+				name: "proxy-backend-service"
+				type: "ClusterIP"
+				selector: {
+					"app.kubernetes.io/component": "controller"
+					"app.kubernetes.io/instance": "ingress-nginx"
+					"app.kubernetes.io/name": "ingress-nginx"
+				}
+				ports:[{
+					name: "http"
+					port: 80
+					targetPort: 9090
+					protocol: "TCP"
+				}]
+			}
+		}
 	}
 }
+resources: {
+	"proxy-backend-config": {
+		apiVersion: "v1"
+		kind: "ConfigMap"
+		metadata: {
+			name: "proxy-backend-config"
+			namespace: release.namespace
+		}
+		data: {
+			"nginx.conf": """
+worker_processes  1;
+worker_rlimit_nofile 8192;
+events {
+	worker_connections  1024;
+}
+http {
+	map $http_host $backend {
+	}
+	server {
+		listen 9090;
+		location / {
+			resolver 135.181.48.180;
+			proxy_pass http://$backend;
+		}
+	}
+}
+"""
+		}
+	}
+}
+
+_clusterProxySecretName: "cluster-proxy-preauthkey"
+_proxyBackendConfigName: "proxy-backend-config"