installer: fully automate initial bootstrap and env creation
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 6bf0161..5c1822a 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -2,18 +2,8 @@
 
 import (
 	"fmt"
-	"io/fs"
 	"io/ioutil"
-	"net"
-	"time"
 
-	"golang.org/x/crypto/ssh"
-	"golang.org/x/exp/slices"
-
-	"github.com/go-git/go-billy/v5/util"
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/plumbing/object"
-	gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
 	"sigs.k8s.io/yaml"
 )
 
@@ -22,23 +12,17 @@
 const kustomizationFileName = "kustomization.yaml"
 
 type AppManager struct {
-	repo   *git.Repository
-	signer ssh.Signer
+	repoIO RepoIO
 }
 
-func NewAppManager(repo *git.Repository, signer ssh.Signer) (*AppManager, error) {
+func NewAppManager(repoIO RepoIO) (*AppManager, error) {
 	return &AppManager{
-		repo,
-		signer,
+		repoIO,
 	}, nil
 }
 
 func (m *AppManager) Config() (Config, error) {
-	wt, err := m.repo.Worktree()
-	if err != nil {
-		return Config{}, err
-	}
-	configF, err := wt.Filesystem.Open(configFileName)
+	configF, err := m.repoIO.Reader(configFileName)
 	if err != nil {
 		return Config{}, err
 	}
@@ -51,11 +35,7 @@
 }
 
 func (m *AppManager) AppConfig(name string) (map[string]any, error) {
-	wt, err := m.repo.Worktree()
-	if err != nil {
-		return nil, err
-	}
-	configF, err := wt.Filesystem.Open(wt.Filesystem.Join(appDirName, name, configFileName))
+	configF, err := m.repoIO.Reader(fmt.Sprintf("%s/%s/%s", appDirName, name, configFileName))
 	if err != nil {
 		return nil, err
 	}
@@ -70,17 +50,9 @@
 }
 
 func (m *AppManager) Install(app App, config map[string]any) error {
-	if err := m.repo.Fetch(&git.FetchOptions{
-		RemoteName: "origin",
-		Auth:       auth(m.signer),
-		Force:      true,
-	}); err != nil {
-		return err
-	}
-	wt, err := m.repo.Worktree()
-	if err != nil {
-		return err
-	}
+	// if err := m.repoIO.Fetch(); err != nil {
+	// 	return err
+	// }
 	globalConfig, err := m.Config()
 	if err != nil {
 		return err
@@ -89,101 +61,5 @@
 		"Global": globalConfig.Values,
 		"Values": config,
 	}
-	appsRoot, err := wt.Filesystem.Chroot(appDirName)
-	if err != nil {
-		return err
-	}
-	rootKustF, err := appsRoot.Open(kustomizationFileName)
-	if err != nil {
-		return err
-	}
-	defer rootKustF.Close()
-	rootKust, err := ReadKustomization(rootKustF)
-	if err != nil {
-		return err
-	}
-	appRoot, err := appsRoot.Chroot(app.Name)
-	if err != nil {
-		return err
-	}
-	if err := util.RemoveAll(appRoot, app.Name); err != nil {
-		return err
-	}
-	if err := appRoot.MkdirAll(app.Name, fs.ModePerm); err != nil {
-		return nil
-	}
-	appKust := NewKustomization()
-	for _, t := range app.Templates {
-		out, err := appRoot.Create(t.Name())
-		if err != nil {
-			return err
-		}
-		defer out.Close()
-		if err := t.Execute(out, all); err != nil {
-			return err
-		}
-		appKust.AddResources(t.Name())
-	}
-	{
-		out, err := appRoot.Create(configFileName)
-		if err != nil {
-			return err
-		}
-		defer out.Close()
-		configBytes, err := yaml.Marshal(config)
-		if err != nil {
-			return err
-		}
-		if _, err := out.Write(configBytes); err != nil {
-			return err
-		}
-	}
-	appKustF, err := appRoot.Create(kustomizationFileName)
-	if err != nil {
-		return err
-	}
-	defer appKustF.Close()
-	if err := appKust.Write(appKustF); err != nil {
-		return err
-	}
-	if !slices.Contains(rootKust.Resources, app.Name) {
-		rootKust.AddResources(app.Name)
-		rootKustFW, err := appsRoot.Create(kustomizationFileName)
-		if err != nil {
-			return err
-		}
-		defer rootKustFW.Close()
-		if err := rootKust.Write(rootKustFW); err != nil {
-			return err
-		}
-	}
-	// Commit and push
-	if err := wt.AddGlob("*"); err != nil {
-		return err
-	}
-	if _, err := wt.Commit(fmt.Sprintf("install: %s", app.Name), &git.CommitOptions{
-		Author: &object.Signature{
-			Name: "pcloud-appmanager",
-			When: time.Now(),
-		},
-	}); err != nil {
-		return err
-	}
-	return m.repo.Push(&git.PushOptions{
-		RemoteName: "origin",
-		Auth:       auth(m.signer),
-	})
-}
-
-func auth(signer ssh.Signer) *gitssh.PublicKeys {
-	return &gitssh.PublicKeys{
-		Signer: signer,
-		HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
-			HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
-				// TODO(giolekva): verify server public key
-				// fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
-				return nil
-			},
-		},
-	}
+	return m.repoIO.InstallApp(app, "apps", all)
 }