installer: clean up codebase

* introduce helper soft package to work with SoftServe
* move commands to cmd/
diff --git a/core/installer/cmd/bootstrap.go b/core/installer/cmd/bootstrap.go
new file mode 100644
index 0000000..151dd90
--- /dev/null
+++ b/core/installer/cmd/bootstrap.go
@@ -0,0 +1,199 @@
+package main
+
+import (
+	"context"
+	"crypto/ed25519"
+	"crypto/rand"
+	"crypto/x509"
+	_ "embed"
+	"encoding/pem"
+	"fmt"
+	"golang.org/x/crypto/ssh"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/giolekva/pcloud/core/installer/soft"
+	"github.com/spf13/cobra"
+	"helm.sh/helm/v3/pkg/action"
+	"helm.sh/helm/v3/pkg/chart/loader"
+	"helm.sh/helm/v3/pkg/kube"
+)
+
+var bootstrapFlags struct {
+	chartsDir    string
+	adminPubKey  string
+	adminPrivKey string
+}
+
+func bootstrapCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:  "bootstrap",
+		RunE: bootstrapCmdRun,
+	}
+	cmd.Flags().StringVar(
+		&bootstrapFlags.chartsDir,
+		"charts-dir",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&bootstrapFlags.adminPubKey,
+		"admin-pub-key",
+		"",
+		"",
+	)
+	cmd.Flags().StringVar(
+		&bootstrapFlags.adminPrivKey,
+		"admin-priv-key",
+		"",
+		"",
+	)
+	return cmd
+}
+
+func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
+	adminPubKey, adminPrivKey, err := readAdminKeys()
+	if err != nil {
+		return err
+	}
+	fluxPub, fluxPriv, err := generateSSHKeys()
+	if err != nil {
+		return err
+	}
+	softServePub, softServePriv, err := generateSSHKeys()
+	if err != nil {
+		return err
+	}
+	fmt.Println("Installing SoftServe")
+	if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
+		return err
+	}
+	time.Sleep(30 * time.Second)
+	ss, err := soft.NewClient("192.168.0.208", 22, adminPrivKey, log.Default())
+	if err != nil {
+		return err
+	}
+	if err := ss.UpdateConfig(
+		soft.DefaultConfig([]string{string(adminPubKey), fluxPub}),
+		"set admin keys"); err != nil {
+		return err
+	}
+	if err := ss.ReloadConfig(); err != nil {
+		return err
+	}
+	fmt.Println("Creating /pcloud repo")
+	if err := ss.AddRepository("pcloud", "# PCloud Systems\n"); err != nil {
+		return err
+	}
+	fmt.Println("Installing Flux")
+	if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
+		return err
+	}
+	return nil
+}
+
+func installSoftServe(pubKey, privKey, adminKey string) error {
+	config, err := createActionConfig()
+	if err != nil {
+		return err
+	}
+	chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "soft-serve"))
+	if err != nil {
+		return err
+	}
+	values := map[string]interface{}{
+		"privateKey": privKey,
+		"publicKey":  pubKey,
+		"adminKey":   adminKey,
+	}
+	installer := action.NewInstall(config)
+	installer.Namespace = "pcloud"
+	installer.CreateNamespace = true
+	installer.ReleaseName = "soft-serve"
+	installer.Wait = true
+	installer.Timeout = 5 * time.Minute
+	if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+		return err
+	}
+	return nil
+}
+
+func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
+	config, err := createActionConfig()
+	if err != nil {
+		return err
+	}
+	chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "flux-bootstrap"))
+	if err != nil {
+		return err
+	}
+	values := map[string]interface{}{
+		"repositoryAddress":       repoAddr,
+		"repositoryHost":          repoHost,
+		"repositoryHostPublicKey": repoHostPubKey,
+		"privateKey":              privateKey,
+	}
+	installer := action.NewInstall(config)
+	installer.Namespace = "pcloud"
+	installer.CreateNamespace = true
+	installer.ReleaseName = "flux"
+	installer.Wait = true
+	installer.WaitForJobs = true
+	installer.Timeout = 5 * time.Minute
+	if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+		return err
+	}
+	return nil
+}
+
+func createActionConfig() (*action.Configuration, error) {
+	config := new(action.Configuration)
+	if err := config.Init(
+		kube.GetConfig(rootFlags.kubeConfig, "", ""),
+		"pcloud",
+		"",
+		func(fmtString string, args ...interface{}) {
+			fmt.Printf(fmtString, args...)
+			fmt.Println()
+		},
+	); err != nil {
+		return nil, err
+	}
+	return config, nil
+}
+
+func generateSSHKeys() (string, string, error) {
+	pub, priv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		return "", "", err
+	}
+	privEnc, err := x509.MarshalPKCS8PrivateKey(priv)
+	if err != nil {
+		return "", "", err
+	}
+	privPem := pem.EncodeToMemory(
+		&pem.Block{
+			Type:  "PRIVATE KEY",
+			Bytes: privEnc,
+		},
+	)
+	pubKey, err := ssh.NewPublicKey(pub)
+	if err != nil {
+		return "", "", err
+	}
+	return string(ssh.MarshalAuthorizedKey(pubKey)), string(privPem), nil
+}
+
+func readAdminKeys() ([]byte, []byte, error) {
+	pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	privKey, err := os.ReadFile(bootstrapFlags.adminPrivKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	return pubKey, privKey, nil
+}