env: create tailscale-proxy for ingress-private IP with new env
diff --git a/charts/headscale/templates/config.yaml b/charts/headscale/templates/config.yaml
index 31582e3..85d8b79 100644
--- a/charts/headscale/templates/config.yaml
+++ b/charts/headscale/templates/config.yaml
@@ -206,7 +206,7 @@
         # Path to a file containg ACL policies.
         # ACLs can be defined as YAML or HUJSON.
         # https://tailscale.com/kb/1018/acls/
-        acl_policy_path: ""
+        acl_policy_path: "/headscale/acls/config.hujson" # TODO(gio): mount path must be configurable
 
         ## DNS
         #
@@ -272,7 +272,7 @@
         # help us test it.
         # OpenID Connect
         oidc:
-          only_start_if_oidc_is_available: false
+          only_start_if_oidc_is_available: true
           issuer: {{ .Values.oauth2.hydraPublic }}
           client_id: {{`{{ .client_id }}`}}
           client_secret: {{`{{ .client_secret }}`}}
diff --git a/charts/headscale/templates/headscale.yaml b/charts/headscale/templates/headscale.yaml
index 6b38345..128a24c 100644
--- a/charts/headscale/templates/headscale.yaml
+++ b/charts/headscale/templates/headscale.yaml
@@ -68,6 +68,18 @@
     requests:
       storage: {{ .Values.storage.size }}
 ---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: acls
+  namespace: {{ .Release.Namespace }}
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi # TODO(gio): configurable
+---
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
@@ -88,6 +100,9 @@
       - name: data
         persistentVolumeClaim:
           claimName: data
+      - name: acls
+        persistentVolumeClaim:
+          claimName: acls
       - name: config
         configMap:
           name: config
@@ -115,6 +130,9 @@
         - name: config
           mountPath: /headscale/config
           readOnly: true
+        - name: acls
+          mountPath: /headscale/acls
+          readOnly: true
         - mountPath: /headscale-api
           name: api-socket
       - name: headscale-api
@@ -128,6 +146,8 @@
         - headscale-api
         - --port={{ .Values.api.port }}
         - --config=/headscale/config/config.yaml
+        - --domain={{ .Values.api.rootDomain }}
+        - --acls=/headscale/acls/config.hujson
         volumeMounts:
         - name: data
           mountPath: /headscale/data
@@ -135,5 +155,8 @@
         - name: config
           mountPath: /headscale/config
           readOnly: true
+        - name: acls
+          mountPath: /headscale/acls
+          readOnly: false
         - mountPath: /headscale-api
           name: api-socket
diff --git a/charts/headscale/values.yaml b/charts/headscale/values.yaml
index 78f40b6..e8ccc76 100644
--- a/charts/headscale/values.yaml
+++ b/charts/headscale/values.yaml
@@ -15,6 +15,7 @@
 ipAddressPool: example-headscale
 api:
   port: 8585
+  rootDomain: example.com
   image:
     repository: giolekva/headscale-api
     tag: latest
diff --git a/charts/tailscale/.helmignore b/charts/tailscale-proxy/.helmignore
similarity index 100%
rename from charts/tailscale/.helmignore
rename to charts/tailscale-proxy/.helmignore
diff --git a/charts/tailscale/Chart.yaml b/charts/tailscale-proxy/Chart.yaml
similarity index 100%
rename from charts/tailscale/Chart.yaml
rename to charts/tailscale-proxy/Chart.yaml
diff --git a/charts/tailscale/templates/install.yaml b/charts/tailscale-proxy/templates/install.yaml
similarity index 100%
rename from charts/tailscale/templates/install.yaml
rename to charts/tailscale-proxy/templates/install.yaml
diff --git a/charts/tailscale/values.yaml b/charts/tailscale-proxy/values.yaml
similarity index 100%
rename from charts/tailscale/values.yaml
rename to charts/tailscale-proxy/values.yaml
diff --git a/core/headscale/main.go b/core/headscale/main.go
index a2e429c..b9dbc22 100644
--- a/core/headscale/main.go
+++ b/core/headscale/main.go
@@ -6,12 +6,37 @@
 	"fmt"
 	"log"
 	"net/http"
+	"os"
+	"text/template"
 
 	"github.com/labstack/echo/v4"
 )
 
 var port = flag.Int("port", 3000, "Port to listen on")
 var config = flag.String("config", "", "Path to headscale config")
+var acls = flag.String("acls", "", "Path to the headscale acls file")
+var domain = flag.String("domain", "", "Environment domain")
+
+// TODO(gio): ingress-private user name must be configurable
+const defaultACLs = `
+{
+  "hosts": {
+    "private-network": "10.1.0.0/24",
+  },
+  "autoApprovers": {
+    "routes": {
+      "private-network": ["private-network-proxy@{{ .Domain }}"],
+    },
+  },
+  "acls": [
+    { // Everyone can access ingress-private service
+      "action": "accept",
+      "src": ["*"],
+      "dst": ["private-network:*"],
+    },
+  ],
+}
+`
 
 type server struct {
 	port   int
@@ -65,8 +90,24 @@
 	}
 }
 
+func updateACLs(domain, acls string) error {
+	tmpl, err := template.New("acls").Parse(defaultACLs)
+	if err != nil {
+		return err
+	}
+	out, err := os.Create(acls)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+	return tmpl.Execute(out, map[string]any{
+		"Domain": domain,
+	})
+}
+
 func main() {
 	flag.Parse()
+	updateACLs(*domain, *acls)
 	c := newClient(*config)
 	s := newServer(*port, c)
 	s.start()
diff --git a/core/installer/values-tmpl/headscale.yaml b/core/installer/values-tmpl/headscale.yaml
index 5b63d7e..c6c04a2 100644
--- a/core/installer/values-tmpl/headscale.yaml
+++ b/core/installer/values-tmpl/headscale.yaml
@@ -34,6 +34,7 @@
     ipAddressPool: {{ .Global.Id }}-headscale
     api:
       port: 8585
+      rootDomain: {{ .Global.Domain }}
       image:
         repository: giolekva/headscale-api
         tag: latest
diff --git a/core/installer/values-tmpl/tailscale-proxy.jsonschema b/core/installer/values-tmpl/tailscale-proxy.jsonschema
index 9452893..11f57c6 100644
--- a/core/installer/values-tmpl/tailscale-proxy.jsonschema
+++ b/core/installer/values-tmpl/tailscale-proxy.jsonschema
@@ -2,7 +2,8 @@
   "type": "object",
   "properties": {
 	"Username": { "type": "string", "default": "example" },
-	"IPSubnet": { "type": "string", "default": "10.1.0.1" }
+	"IPSubnet": { "type": "string", "default": "10.1.0.1" },
+	"HostnameSuffix": { "type": "string", "default": "10.1.0.1" }
   },
   "additionalProperties": false
 }
diff --git a/core/installer/values-tmpl/tailscale-proxy.yaml b/core/installer/values-tmpl/tailscale-proxy.yaml
index 5ceae75..1774f1e 100644
--- a/core/installer/values-tmpl/tailscale-proxy.yaml
+++ b/core/installer/values-tmpl/tailscale-proxy.yaml
@@ -9,14 +9,14 @@
       namespace: {{ .Global.NamespacePrefix }}app-headscale
   chart:
     spec:
-      chart: charts/tailscale
+      chart: charts/tailscale-proxy
       sourceRef:
         kind: GitRepository
         name: pcloud
         namespace: {{ .Global.Id }}
   interval: 1m0s
   values:
-    hostname: {{ .Global.PCloudEnvName }}-{{ .Global.Id }}-internal-proxy
+    hostname: {{ .Global.Id }}-{{ .Values.HostnameSuffix }}
     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.IPSubnet }}
diff --git a/core/installer/welcome/create-env.html b/core/installer/welcome/create-env.html
index d9f62c5..ea07476 100644
--- a/core/installer/welcome/create-env.html
+++ b/core/installer/welcome/create-env.html
@@ -6,39 +6,34 @@
 		<meta name="viewport" content="width=device-width, initial-scale=1" />
 	</head>
 	<body>
-		<div style="display: contents">
-            <main class="container">
-              <article class="grid">
-                <div>
-                  <form action="/env" method="POST">
-                    <input
-                      type="test"
-                      name="domain"
-                      placeholder="Domain"
-                      required
-                      />
-                    <input
-                      type="email"
-                      name="contact-email"
-                      placeholder="Contact Email"
-                      required
-                    />
-                    <input
-                      type="string"
-                      name="admin-public-key"
-                      placeholder="Admin SSH Public Key"
-                      required
-                    /> <!-- TODO(gio): remove-->
-                    <textarea
-                      name="secret-token"
-                      placeholder="Secret Token"
-                      required
-                    ></textarea>
-                    <button type="submit" class="contrast">Create Environment</button>
-                  </form>
-                </div>
-              </article>
-            </main>
-        </div>
+		<main class="container">
+			<form action="" method="POST">
+			  <input
+				type="test"
+				name="domain"
+				placeholder="Domain"
+				required
+				autofocus
+		      />
+			  <input
+				type="email"
+				name="contact-email"
+				placeholder="Contact Email"
+				required
+			  />
+			  <input
+				type="string"
+				name="admin-public-key"
+				placeholder="Admin SSH Public Key"
+				required
+			  /> <!-- TODO(gio): remove-->
+			  <textarea
+				name="secret-token"
+				placeholder="Secret Token"
+				required
+			  ></textarea>
+			  <button type="submit">Create Environment</button>
+			</form>
+		</main>
 	</body>
 </html>
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index 3d0ed70..4e74181 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -12,6 +12,7 @@
 	"io/fs"
 	"log"
 	"net/http"
+	"net/netip"
 	"path"
 	"path/filepath"
 	"strings"
@@ -502,15 +503,20 @@
 	r.CommitAndPush("initialize config")
 	nsGen := installer.NewPrefixGenerator(req.Name + "-")
 	emptySuffixGen := installer.NewEmptySuffixGenerator()
+	ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
+	if err != nil {
+		return err
+	}
 	{
+		headscaleIP := ingressPrivateIP.Next()
 		app, err := appsRepo.Find("metallb-ipaddresspool")
 		if err != nil {
 			return err
 		}
 		if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
 			"Name":       fmt.Sprintf("%s-ingress-private", req.Name),
-			"From":       "10.1.0.1",
-			"To":         "10.1.0.1",
+			"From":       ingressPrivateIP.String(),
+			"To":         ingressPrivateIP.String(),
 			"AutoAssign": false,
 			"Namespace":  "metallb-system",
 		}); err != nil {
@@ -518,8 +524,8 @@
 		}
 		if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
 			"Name":       fmt.Sprintf("%s-headscale", req.Name),
-			"From":       "10.1.0.2",
-			"To":         "10.1.0.2",
+			"From":       headscaleIP.String(),
+			"To":         headscaleIP.String(),
 			"AutoAssign": false,
 			"Namespace":  "metallb-system",
 		}); err != nil {
@@ -527,7 +533,7 @@
 		}
 		if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
 			"Name":       req.Name,
-			"From":       "10.1.0.100",
+			"From":       "10.1.0.100", // TODO(gio): auto-generate
 			"To":         "10.1.0.254",
 			"AutoAssign": false,
 			"Namespace":  "metallb-system",
@@ -545,6 +551,20 @@
 		}
 	}
 	{
+		app, err := appsRepo.Find("tailscale-proxy")
+		if err != nil {
+			return err
+		}
+		if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+			"Username":       "private-network-proxy",
+			"IPSubnet":       "10.1.0.0/24",
+			"HostnameSuffix": "private-network-proxy",
+		}); err != nil {
+			return err
+		}
+		// TODO(giolekva): headscale accept routes
+	}
+	{
 		app, err := appsRepo.Find("certificate-issuer-public")
 		if err != nil {
 			return err