installer: appmanager cmd
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 4b66fa5..0678114 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -1,60 +1,74 @@
 package installer
 
 import (
+	"fmt"
 	"io/fs"
+	"net"
+	"time"
 
+	"golang.org/x/crypto/ssh"
 	"golang.org/x/exp/slices"
 
-	"github.com/go-git/go-billy/v5"
 	"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"
 )
 
+const appDirName = "apps"
+const configFileName = "config.yaml"
 const kustomizationFileName = "kustomization.yaml"
 
-type AppRepository interface {
-	Find(name string) (*App, error)
-}
-
 type AppManager struct {
-	fs       billy.Filesystem
-	config   Config
-	appRepo  AppRepository
-	rootKust *Kustomization
+	repo   *git.Repository
+	signer ssh.Signer
 }
 
-func NewAppManager(fs billy.Filesystem, config Config, appRepo AppRepository) (*AppManager, error) {
-	rootKustF, err := fs.Open(kustomizationFileName)
+// func NewAppManager(repo *git.Repository, fs billy.Filesystem, config Config, appRepo AppRepository) (*AppManager, error) {
+func NewAppManager(repo *git.Repository, signer ssh.Signer) (*AppManager, error) {
+	return &AppManager{
+		repo,
+		signer,
+	}, nil
+}
+
+func (m *AppManager) Install(app App) error {
+	wt, err := m.repo.Worktree()
 	if err != nil {
-		return nil, err
+		return err
+	}
+	configF, err := wt.Filesystem.Open(configFileName)
+	if err != nil {
+		return err
+	}
+	defer configF.Close()
+	config, err := ReadConfig(configF)
+	if err != nil {
+		return err
+	}
+	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 nil, err
-	}
-	return &AppManager{
-		fs,
-		config,
-		appRepo,
-		rootKust,
-	}, nil
-}
-
-func (m *AppManager) Install(name string) error {
-	app, err := m.appRepo.Find(name)
-	if err != nil {
-		return nil
-	}
-	if err := util.RemoveAll(m.fs, name); err != nil {
 		return err
 	}
-	if err := m.fs.MkdirAll(name, fs.ModePerm); err != nil {
-		return nil
-	}
-	appRoot, err := m.fs.Chroot(name)
+	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())
@@ -62,7 +76,7 @@
 			return err
 		}
 		defer out.Close()
-		if err := t.Execute(out, m.config); err != nil {
+		if err := t.Execute(out, config); err != nil {
 			return err
 		}
 		appKust.Resources = append(appKust.Resources, t.Name())
@@ -75,14 +89,44 @@
 	if err := appKust.Write(appKustF); err != nil {
 		return err
 	}
-	if slices.Contains(m.rootKust.Resources, name) {
-		return nil
+	if !slices.Contains(rootKust.Resources, app.Name) {
+		rootKust.Resources = append(rootKust.Resources, app.Name)
+		rootKustFW, err := appsRoot.Create(kustomizationFileName)
+		if err != nil {
+			return err
+		}
+		defer rootKustFW.Close()
+		if err := rootKust.Write(rootKustFW); err != nil {
+			return err
+		}
 	}
-	m.rootKust.Resources = append(m.rootKust.Resources, name)
-	rootKustF, err := m.fs.Create(kustomizationFileName)
-	if err != nil {
+	// Commit and push
+	if err := wt.AddGlob("*"); err != nil {
 		return err
 	}
-	defer rootKustF.Close()
-	return m.rootKust.Write(rootKustF)
+	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
+			},
+		},
+	}
 }