installer: env form
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
 }