installer: env form
diff --git a/core/installer/cmd/env-tmpl/config-kustomization.yaml b/core/installer/cmd/env-tmpl/config-kustomization.yaml
deleted file mode 100644
index d76bf0f..0000000
--- a/core/installer/cmd/env-tmpl/config-kustomization.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
-kind: Kustomization
-metadata:
-  name: {{ .Name }}
-  namespace: {{ .Name }}
-spec:
-  interval: 1m
-  path: "./"
-  sourceRef:
-    kind: GitRepository
-    name: {{ .Name }}
-    namespace: {{ .Name }}
-  prune: true
diff --git a/core/installer/cmd/env-tmpl/config-secret.yaml b/core/installer/cmd/env-tmpl/config-secret.yaml
deleted file mode 100644
index 3ea515b..0000000
--- a/core/installer/cmd/env-tmpl/config-secret.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: v1
-data:
-  identity: {{ .PrivateKey }}
-  identity.pub: {{ .PublicKey }}
-  known_hosts: {{ .KnownHosts }}
-kind: Secret
-metadata:
-  name: {{ .Name }}
-  namespace: {{ .Name }}
-type: Opaque
diff --git a/core/installer/cmd/env-tmpl/config-source.yaml b/core/installer/cmd/env-tmpl/config-source.yaml
deleted file mode 100644
index 113a0b4..0000000
--- a/core/installer/cmd/env-tmpl/config-source.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: GitRepository
-metadata:
-  name: {{ .Name }}
-  namespace: {{ .Name }}
-spec:
-  gitImplementation: go-git
-  interval: 1m0s
-  ref:
-    branch: master
-  secretRef:
-    name: {{ .Name }}
-  timeout: 60s
-  url: ssh://{{ .GitHost }}/{{ .Name }}
diff --git a/core/installer/cmd/env-tmpl/kustomization.yaml b/core/installer/cmd/env-tmpl/kustomization.yaml
deleted file mode 100644
index 70db25f..0000000
--- a/core/installer/cmd/env-tmpl/kustomization.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
-- namespace.yaml
-- config-secret.yaml
-- config-source.yaml
-- config-kustomization.yaml
diff --git a/core/installer/cmd/env-tmpl/namespace.yaml b/core/installer/cmd/env-tmpl/namespace.yaml
deleted file mode 100644
index 0c14654..0000000
--- a/core/installer/cmd/env-tmpl/namespace.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: {{ .Name }}
-  labels:
-    pcloud-instance-id: {{ .Name }}
-  annotations:
-    helm.sh/resource-policy: keep
diff --git a/core/installer/cmd/env.go b/core/installer/cmd/env.go
deleted file mode 100644
index 045b258..0000000
--- a/core/installer/cmd/env.go
+++ /dev/null
@@ -1,268 +0,0 @@
-// TODO
-// * flux -n lekva create source git pcloud --url=ssh://192.168.0.211/pcloud-apps --branch=main --private-key-file=/Users/lekva/.ssh/id_rsa
-
-package main
-
-import (
-	"embed"
-	"encoding/base64"
-	"fmt"
-	"github.com/spf13/cobra"
-	"log"
-	"os"
-	"path"
-	"text/template"
-
-	"github.com/giolekva/pcloud/core/installer"
-	"github.com/giolekva/pcloud/core/installer/soft"
-)
-
-//go:embed env-tmpl
-var filesTmpls embed.FS
-
-var createEnvFlags struct {
-	name          string
-	ip            string
-	port          int
-	adminPrivKey  string
-	adminUsername string
-}
-
-func createEnvCmd() *cobra.Command {
-	cmd := &cobra.Command{
-		Use:  "create-env",
-		RunE: createEnvCmdRun,
-	}
-	cmd.Flags().StringVar(
-		&createEnvFlags.name,
-		"name",
-		"",
-		"",
-	)
-	cmd.Flags().StringVar(
-		&createEnvFlags.ip,
-		"ip",
-		"",
-		"",
-	)
-	cmd.Flags().IntVar(
-		&createEnvFlags.port,
-		"port",
-		22,
-		"",
-	)
-	cmd.Flags().StringVar(
-		&createEnvFlags.adminPrivKey,
-		"admin-priv-key",
-		"",
-		"",
-	)
-	cmd.Flags().StringVar(
-		&createEnvFlags.adminUsername,
-		"admin-username",
-		"",
-		"",
-	)
-	return cmd
-}
-
-func createEnvCmdRun(cmd *cobra.Command, args []string) error {
-	adminPrivKey, err := os.ReadFile(createEnvFlags.adminPrivKey)
-	if err != nil {
-		return err
-	}
-	ss, err := soft.NewClient(createEnvFlags.ip, createEnvFlags.port, adminPrivKey, log.Default())
-	if err != nil {
-		return err
-	}
-	ssPubKey, err := ss.GetPublicKey()
-	if err != nil {
-		return err
-	}
-	keys, err := installer.NewSSHKeyPair()
-	if err != nil {
-		return err
-	}
-	if 1 == 2 {
-		readme := fmt.Sprintf("# %s PCloud environment", createEnvFlags.name)
-		if err := ss.AddRepository(createEnvFlags.name, readme); err != nil {
-			return err
-		}
-		fluxUserName := fmt.Sprintf("flux-%s", createEnvFlags.name)
-		if err := ss.AddUser(fluxUserName, keys.Public); err != nil {
-			return err
-		}
-		if err := ss.AddCollaborator(createEnvFlags.name, fluxUserName); err != nil {
-			return err
-		}
-	}
-	envRepo, err := ss.GetRepo(createEnvFlags.name)
-	if envRepo == nil {
-		return err
-	}
-	if err := initEnvRepo(installer.NewRepoIO(envRepo, ss.Signer)); err != nil {
-		return err
-	}
-	if 1 == 2 {
-		repo, err := ss.GetRepo("pcloud")
-		if err != nil {
-			return err
-		}
-		repoIO := installer.NewRepoIO(repo, ss.Signer)
-		kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
-		if err != nil {
-			return err
-		}
-		kust.AddResources(createEnvFlags.name)
-		tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
-		if err != nil {
-			return err
-		}
-		for _, tmpl := range tmpls.Templates() {
-			dstPath := path.Join("environments", createEnvFlags.name, tmpl.Name())
-			dst, err := repoIO.Writer(dstPath)
-			if err != nil {
-				return err
-			}
-			defer dst.Close()
-			if err := tmpl.Execute(dst, map[string]string{
-				"Name":       createEnvFlags.name,
-				"PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
-				"PublicKey":  base64.StdEncoding.EncodeToString([]byte(keys.Public)),
-				"GitHost":    createEnvFlags.ip,
-				"KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", createEnvFlags.ip, ssPubKey))),
-			}); err != nil {
-				return err
-			}
-		}
-		if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
-			return err
-		}
-		if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", createEnvFlags.name)); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func initEnvRepo(r installer.RepoIO) error {
-	appManager, err := installer.NewAppManager(r)
-	if err != nil {
-		return err
-	}
-	appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
-	if 1 == 2 {
-		config := installer.Config{ // TODO(gioleka): configurable
-			Values: installer.Values{
-				PCloudEnvName:   "pcloud",
-				Id:              "lekva",
-				ContactEmail:    "giolekva@gmail.com",
-				Domain:          "lekva.me",
-				PrivateDomain:   "p.lekva.me",
-				PublicIP:        "46.49.35.44",
-				NamespacePrefix: "lekva-",
-			},
-		}
-		if err := r.WriteYaml("config.yaml", config); err != nil {
-			return err
-		}
-		{
-			out, err := r.Writer("pcloud-charts.yaml")
-			if err != nil {
-				return err
-			}
-			defer out.Close()
-			_, err = out.Write([]byte(`
-apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: GitRepository
-metadata:
-  name: pcloud
-  namespace: lekva
-spec:
-  interval: 1m0s
-  url: https://github.com/giolekva/pcloud
-  ref:
-    branch: main
-`))
-			if err != nil {
-				return err
-			}
-		}
-		rootKust := installer.NewKustomization()
-		rootKust.AddResources("pcloud-charts.yaml", "apps")
-		if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
-			return err
-		}
-		appsKust := installer.NewKustomization()
-		if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
-			return err
-		}
-		r.CommitAndPush("initialize config")
-		{
-			app, err := appsRepo.Find("metallb-config-env")
-			if err != nil {
-				return err
-			}
-			if err := appManager.Install(*app, map[string]any{
-				"IngressPrivate": "10.1.0.1",
-				"Headscale":      "10.1.0.2",
-				"SoftServe":      "10.1.0.3",
-				"Rest": map[string]any{
-					"From": "10.1.0.100",
-					"To":   "10.1.0.255",
-				},
-			}); err != nil {
-				return err
-			}
-		}
-		{
-			app, err := appsRepo.Find("ingress-private")
-			if err != nil {
-				return err
-			}
-			if err := appManager.Install(*app, map[string]any{
-				"GandiAPIToken": "", // TODO(gioleka): configurable
-			}); err != nil {
-				return err
-			}
-		}
-		{
-			app, err := appsRepo.Find("core-auth")
-			if err != nil {
-				return err
-			}
-			if err := appManager.Install(*app, map[string]any{
-				"Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
-			}); err != nil {
-				return err
-			}
-		}
-	}
-	{
-		app, err := appsRepo.Find("headscale")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{
-			"Subdomain": "headscale",
-		}); err != nil {
-			return err
-		}
-	}
-	if 1 == 2 {
-		{
-			app, err := appsRepo.Find("tailscale-proxy")
-			if err != nil {
-				return err
-			}
-			if err := appManager.Install(*app, map[string]any{
-				"Username": createEnvFlags.adminUsername,
-				"IPSubnet": "10.1.0.0/24",
-			}); err != nil {
-				return err
-			}
-			// TODO(giolekva): headscale accept routes
-		}
-	}
-	return nil
-}
diff --git a/core/installer/cmd/env_manager.go b/core/installer/cmd/env_manager.go
index 33b4058..647850d 100644
--- a/core/installer/cmd/env_manager.go
+++ b/core/installer/cmd/env_manager.go
@@ -1,19 +1,14 @@
 package main
 
 import (
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
 	"log"
 	"os"
-	"path"
-	"text/template"
 
-	"github.com/labstack/echo/v4"
 	"github.com/spf13/cobra"
 
 	"github.com/giolekva/pcloud/core/installer"
 	"github.com/giolekva/pcloud/core/installer/soft"
+	"github.com/giolekva/pcloud/core/installer/welcome"
 )
 
 var envManagerFlags struct {
@@ -60,277 +55,20 @@
 	if err != nil {
 		return err
 	}
-	fmt.Println(string(sshKey))
 	ss, err := soft.NewClient(envManagerFlags.repoIP, envManagerFlags.repoPort, sshKey, log.Default())
 	if err != nil {
 		return err
 	}
-	b, err := ss.GetPublicKey()
-	if err != nil {
-		return err
-	}
-	fmt.Println(string(b))
-	fmt.Println(111)
 	repo, err := ss.GetRepo("pcloud")
-	fmt.Println(222)
 	if err != nil {
 		return err
 	}
-	fmt.Println(333)
 	repoIO := installer.NewRepoIO(repo, ss.Signer)
-	s := &envServer{
-		port: envManagerFlags.port,
-		ss:   ss,
-		repo: repoIO,
-	}
-	s.start()
-	return nil
-}
-
-type envServer struct {
-	port int
-	ss   *soft.Client
-	repo installer.RepoIO
-}
-
-func (s *envServer) start() {
-	e := echo.New()
-	e.POST("/env", s.createEnv)
-	log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
-}
-
-type createEnvReq struct {
-	Name         string `json:"name"`
-	ContactEmail string `json:"contactEmail"`
-	Domain       string `json:"domain"`
-}
-
-func (s *envServer) createEnv(c echo.Context) error {
-	var req createEnvReq
-	if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
-		return err
-	}
-	keys, err := installer.NewSSHKeyPair()
-	if err != nil {
-		return err
-	}
-	{
-		readme := fmt.Sprintf("# %s PCloud environment", req.Name)
-		if err := s.ss.AddRepository(req.Name, readme); err != nil {
-			return err
-		}
-		fluxUserName := fmt.Sprintf("flux-%s", req.Name)
-		if err := s.ss.AddUser(fluxUserName, keys.Public); err != nil {
-			return err
-		}
-		if err := s.ss.AddCollaborator(req.Name, fluxUserName); err != nil {
-			return err
-		}
-	}
-	{
-		repo, err := s.ss.GetRepo(req.Name)
-		if repo == nil {
-			return err
-		}
-		if err := initNewEnv(s.ss, installer.NewRepoIO(repo, s.ss.Signer), req); err != nil {
-			return err
-		}
-	}
-	{
-		repo, err := s.ss.GetRepo("pcloud")
-		if err != nil {
-			return err
-		}
-		ssPubKey, err := s.ss.GetPublicKey()
-		if err != nil {
-			return err
-		}
-		if err := addNewEnv(
-			installer.NewRepoIO(repo, s.ss.Signer),
-			req,
-			keys,
-			ssPubKey,
-		); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func initNewEnv(ss *soft.Client, r installer.RepoIO, req createEnvReq) error {
-	appManager, err := installer.NewAppManager(r)
-	if err != nil {
-		return err
-	}
-	appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
-	// TODO(giolekva): env name and ip should come from pcloud repo config.yaml
-	// TODO(giolekva): private domain can be configurable as well
-	config := installer.Config{
-		Values: installer.Values{
-			PCloudEnvName:   "pcloud",
-			Id:              req.Name,
-			ContactEmail:    req.ContactEmail,
-			Domain:          req.Domain,
-			PrivateDomain:   fmt.Sprintf("p.%s", req.Domain),
-			PublicIP:        "46.49.35.44",
-			NamespacePrefix: fmt.Sprintf("%s-", req.Name),
-		},
-	}
-	if err := r.WriteYaml("config.yaml", config); err != nil {
-		return err
-	}
-	{
-		out, err := r.Writer("pcloud-charts.yaml")
-		if err != nil {
-			return err
-		}
-		defer out.Close()
-		_, err = out.Write([]byte(`
-apiVersion: source.toolkit.fluxcd.io/v1beta2
-kind: GitRepository
-metadata:
-  name: pcloud
-  namespace: lekva
-spec:
-  interval: 1m0s
-  url: https://github.com/giolekva/pcloud
-  ref:
-    branch: main
-`))
-		if err != nil {
-			return err
-		}
-	}
-	rootKust := installer.NewKustomization()
-	rootKust.AddResources("pcloud-charts.yaml", "apps")
-	if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
-		return err
-	}
-	appsKust := installer.NewKustomization()
-	if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
-		return err
-	}
-	r.CommitAndPush("initialize config")
-	{
-		app, err := appsRepo.Find("metallb-config-env")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{
-			"IngressPrivate": "10.1.0.1",
-			"Headscale":      "10.1.0.2",
-			"SoftServe":      "10.1.0.3",
-			"Rest": map[string]any{
-				"From": "10.1.0.100",
-				"To":   "10.1.0.255",
-			},
-		}); err != nil {
-			return err
-		}
-	}
-	{
-		app, err := appsRepo.Find("ingress-private")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{}); err != nil {
-			return err
-		}
-	}
-	{
-		app, err := appsRepo.Find("certificate-issuer-public")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{}); err != nil {
-			return err
-		}
-	}
-	{
-		app, err := appsRepo.Find("core-auth")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{
-			"Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
-		}); err != nil {
-			return err
-		}
-	}
-	{
-		app, err := appsRepo.Find("headscale")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{
-			"Subdomain": "headscale",
-		}); err != nil {
-			return err
-		}
-	}
-	{
-		keys, err := installer.NewSSHKeyPair()
-		if err != nil {
-			return err
-		}
-		user := fmt.Sprintf("%s-welcome", req.Name)
-		if err := ss.AddUser(user, keys.Public); err != nil {
-			return err
-		}
-		if err := ss.AddCollaborator(req.Name, user); err != nil {
-			return err
-		}
-		app, err := appsRepo.Find("welcome")
-		if err != nil {
-			return err
-		}
-		if err := appManager.Install(*app, map[string]any{
-			"RepoAddr":      ss.GetRepoAddress(req.Name),
-			"SSHPrivateKey": keys.Private,
-		}); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func addNewEnv(
-	repoIO installer.RepoIO,
-	req createEnvReq,
-	keys installer.KeyPair,
-	pcloudRepoPublicKey []byte,
-) error {
-	kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
-	if err != nil {
-		return err
-	}
-	kust.AddResources(req.Name)
-	tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
-	if err != nil {
-		return err
-	}
-	for _, tmpl := range tmpls.Templates() {
-		dstPath := path.Join("environments", req.Name, tmpl.Name())
-		dst, err := repoIO.Writer(dstPath)
-		if err != nil {
-			return err
-		}
-		defer dst.Close()
-		if err := tmpl.Execute(dst, map[string]string{
-			"Name":       req.Name,
-			"PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
-			"PublicKey":  base64.StdEncoding.EncodeToString([]byte(keys.Public)),
-			"GitHost":    envManagerFlags.repoIP,
-			"KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", envManagerFlags.repoIP, pcloudRepoPublicKey))),
-		}); err != nil {
-			return err
-		}
-	}
-	if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
-		return err
-	}
-	if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
-		return err
-	}
+	s := welcome.NewEnvServer(
+		envManagerFlags.port,
+		ss,
+		repoIO,
+	)
+	s.Start()
 	return nil
 }
diff --git a/core/installer/cmd/main.go b/core/installer/cmd/main.go
index 8a5e04b..e879c62 100644
--- a/core/installer/cmd/main.go
+++ b/core/installer/cmd/main.go
@@ -23,7 +23,6 @@
 		"",
 	)
 	rootCmd.AddCommand(bootstrapCmd())
-	rootCmd.AddCommand(createEnvCmd())
 	rootCmd.AddCommand(installCmd())
 	rootCmd.AddCommand(appManagerCmd())
 	rootCmd.AddCommand(envManagerCmd())