ClusterManager: Implements support of remote clusters.

After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
  to another server from that same cluster

This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
  default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
  remote cluster service from ingress installed on default cluster.

Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
  old cluster keeps running app pods even though no ingress can
  reach it anymore.

Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/installer/values-tmpl/cluster-network.cue b/core/installer/values-tmpl/cluster-network.cue
new file mode 100644
index 0000000..d470ff1
--- /dev/null
+++ b/core/installer/values-tmpl/cluster-network.cue
@@ -0,0 +1,138 @@
+import (
+	// "encoding/base64"
+)
+
+input: {
+	cluster: #Cluster
+	vpnUser: string
+	vpnProxyHostname: string
+	vpnAuthKey: string @role(VPNAuthKey) @usernameField(vpnUser)
+	// TODO(gio): support port allocator
+}
+
+name: "Cluster Network"
+namespace: "cluster-network"
+
+out: {
+	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"
+		}
+		// portAllocator: {
+		// 	repository: "giolekva"
+		// 	name: "port-allocator"
+		// 	tag: "latest"
+		// 	pullPolicy: "Always"
+		// }
+	}
+
+	charts: {
+		"access-secrets": {
+			kind: "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch: "main"
+			path: "charts/access-secrets"
+		}
+		"ingress-nginx": {
+			kind: "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch: "main"
+			path: "charts/ingress-nginx"
+		}
+		"tailscale-proxy": {
+			kind: "GitRepository"
+			address: "https://code.v1.dodo.cloud/helm-charts"
+			branch: "main"
+			path: "charts/tailscale-proxy"
+		}
+		// portAllocator: {
+		// 	kind: "GitRepository"
+		// 	address: "https://code.v1.dodo.cloud/helm-charts"
+		// 	branch: "main"
+		// 	path: "charts/port-allocator"
+		// }
+	}
+
+	helm: {
+		_fullnameOverride: "\(global.id)-nginx-cluster-\(input.cluster.name)"
+		"access-secrets": {
+			chart: charts["access-secrets"]
+			values: {
+				serviceAccountName: _fullnameOverride
+			}
+		}
+		"ingress-nginx": {
+			chart: charts["ingress-nginx"]
+			dependsOn: [{
+				name: "access-secrets"
+				namespace: release.namespace
+			}]
+			values: {
+				fullnameOverride: _fullnameOverride
+				controller: {
+					service: enabled: false
+					ingressClassByName: true
+					ingressClassResource: {
+						name: input.cluster.ingressClassName
+						enabled: true
+						default: false
+						controllerValue: "k8s.io/\(input.cluster.name)"
+					}
+					config: {
+						"proxy-body-size": "200M" // TODO(giolekva): configurable
+						"force-ssl-redirect": "true"
+						"server-snippet": """
+						more_clear_headers "X-Frame-Options";
+						"""
+					}
+					admissionWebhooks: {
+						enabled: false
+					}
+					image: {
+						registry: images["ingress-nginx"].registry
+						image: images["ingress-nginx"].imageName
+						tag: images["ingress-nginx"].tag
+						pullPolicy: images["ingress-nginx"].pullPolicy
+					}
+					extraContainers: [{
+						name: "proxy"
+						image: images["tailscale-proxy"].fullNameWithTag
+						env: [{
+							name: "TS_AUTHKEY"
+							value: input.vpnAuthKey
+					    }, {
+							name: "TS_HOSTNAME"
+							value: input.vpnProxyHostname
+						}, {
+							name: "TS_EXTRA_ARGS"
+							value: "--login-server=https://headscale.\(global.domain)"
+						}]
+  				    }]
+				}
+			}
+		}
+		// "port-allocator": {
+		// 	chart: charts.portAllocator
+		// 	values: {
+		// 		repoAddr: release.repoAddr
+		// 		sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
+		// 		ingressNginxPath: "\(release.appDir)/resources/ingress-nginx.yaml"
+		// 		image: {
+		// 			repository: images.portAllocator.fullName
+		// 			tag: images.portAllocator.tag
+		// 			pullPolicy: images.portAllocator.pullPolicy
+		// 		}
+		// 	}
+		// }
+	}
+}