installer: fully automate initial bootstrap and env creation
diff --git a/core/installer/cmd/bootstrap.go b/core/installer/cmd/bootstrap.go
index e5f3f2b..135b6b4 100644
--- a/core/installer/cmd/bootstrap.go
+++ b/core/installer/cmd/bootstrap.go
@@ -1,6 +1,3 @@
-// TODO
-// * ns pcloud not found
-
package main
import (
@@ -22,12 +19,12 @@
)
var bootstrapFlags struct {
+ pcloudEnvName string
chartsDir string
adminPubKey string
- adminPrivKey string
storageDir string
volumeDefaultReplicaCount int
- softServeIP string
+ softServeIP string // TODO(giolekva): reserve using metallb IPAddressPool
}
func bootstrapCmd() *cobra.Command {
@@ -36,6 +33,12 @@
RunE: bootstrapCmdRun,
}
cmd.Flags().StringVar(
+ &bootstrapFlags.pcloudEnvName,
+ "pcloud-env-name",
+ "pcloud",
+ "",
+ )
+ cmd.Flags().StringVar(
&bootstrapFlags.chartsDir,
"charts-dir",
"",
@@ -48,12 +51,6 @@
"",
)
cmd.Flags().StringVar(
- &bootstrapFlags.adminPrivKey,
- "admin-priv-key",
- "",
- "",
- )
- cmd.Flags().StringVar(
&bootstrapFlags.storageDir,
"storage-dir",
"",
@@ -75,73 +72,68 @@
}
func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
- adminPubKey, adminPrivKey, err := readAdminKeys()
+ adminPubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
if err != nil {
return err
}
- softServePub, softServePriv, err := installer.GenerateSSHKeys()
+ bootstrapJobKeys, err := installer.NewSSHKeyPair()
if err != nil {
return err
}
- if err := installMetallbNamespace(); err != nil {
- return err
- }
if err := installMetallb(); err != nil {
return err
}
- time.Sleep(1 * time.Minute)
- if err := installMetallbConfig(); err != nil {
- return err
- }
if err := installLonghorn(); err != nil {
return err
}
- time.Sleep(2 * time.Minute)
- if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
+ time.Sleep(5 * time.Minute) // TODO(giolekva): implement proper wait
+ if err := installSoftServe(bootstrapJobKeys.Public); err != nil {
return err
}
- time.Sleep(2 * time.Minute)
- ss, err := soft.NewClient(bootstrapFlags.softServeIP, 22, adminPrivKey, log.Default())
+ time.Sleep(2 * time.Minute) // TODO(giolekva): implement proper wait
+ ss, err := soft.NewClient(bootstrapFlags.softServeIP, 22, []byte(bootstrapJobKeys.Private), log.Default())
if err != nil {
return err
}
- fluxPub, fluxPriv, err := installer.GenerateSSHKeys()
+ if ss.AddPublicKey("admin", string(adminPubKey)); err != nil {
+ return err
+ }
+ if err := installFluxcd(ss, bootstrapFlags.pcloudEnvName); err != nil {
+ return err
+ }
+ repo, err := ss.GetRepo(bootstrapFlags.pcloudEnvName)
if err != nil {
return err
}
- if err := ss.AddUser("flux", fluxPub); err != nil {
+ repoIO := installer.NewRepoIO(repo, ss.Signer)
+ if err := configurePCloudRepo(repoIO); err != nil {
return err
}
- if err := ss.MakeUserAdmin("flux"); err != nil {
+ // TODO(giolekva): commit this to the repo above
+ global := map[string]any{
+ "PCloudEnvName": bootstrapFlags.pcloudEnvName,
+ }
+ if err := installInfrastructureServices(repoIO, global); err != nil {
return err
}
- fmt.Println("Creating /pcloud repo")
- if err := ss.AddRepository("pcloud", "# PCloud Systems"); err != nil {
+ if err := installEnvManager(ss, repoIO, global); 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 {
+ if ss.RemovePublicKey("admin", bootstrapJobKeys.Public); err != nil {
return err
}
- pcloudRepo, err := ss.GetRepo("pcloud") // TODO(giolekva): configurable
- if err != nil {
+
+ return nil
+}
+
+func installMetallb() error {
+ if err := installMetallbNamespace(); err != nil {
return err
}
- if err := configurePCloudRepo(installer.NewRepoIO(pcloudRepo, ss.Signer)); err != nil {
+ if err := installMetallbService(); err != nil {
return err
}
- // TODO(giolekva): everything below must be installed using Flux
- if err := installIngressPublic(); err != nil {
- return err
- }
- if err := installCertManager(); err != nil {
- return err
- }
- if err := installCertManagerWebhookGandi(); err != nil {
- return err
- }
- // TODO(giolekva): ideally should be installed automatically if any of the user installed apps requires it
- if err := installSmbDriver(); err != nil {
+ if err := installMetallbConfig(); err != nil {
return err
}
return nil
@@ -150,7 +142,7 @@
func installMetallbNamespace() error {
fmt.Println("Installing metallb namespace")
// config, err := createActionConfig("default")
- config, err := createActionConfig("pcloud")
+ config, err := createActionConfig(bootstrapFlags.pcloudEnvName)
if err != nil {
return err
}
@@ -168,7 +160,7 @@
},
}
installer := action.NewInstall(config)
- installer.Namespace = "pcloud"
+ installer.Namespace = bootstrapFlags.pcloudEnvName
installer.ReleaseName = "metallb-ns"
installer.Wait = true
installer.WaitForJobs = true
@@ -178,7 +170,7 @@
return nil
}
-func installMetallb() error {
+func installMetallbService() error {
fmt.Println("Installing metallb")
// config, err := createActionConfig("default")
config, err := createActionConfig("metallb-system")
@@ -251,7 +243,7 @@
func installLonghorn() error {
fmt.Println("Installing Longhorn")
- config, err := createActionConfig("pcloud")
+ config, err := createActionConfig(bootstrapFlags.pcloudEnvName)
if err != nil {
return err
}
@@ -288,9 +280,13 @@
return nil
}
-func installSoftServe(pubKey, privKey, adminKey string) error {
+func installSoftServe(adminPublicKey string) error {
fmt.Println("Installing SoftServe")
- config, err := createActionConfig("pcloud")
+ keys, err := installer.NewSSHKeyPair()
+ if err != nil {
+ return err
+ }
+ config, err := createActionConfig(bootstrapFlags.pcloudEnvName)
if err != nil {
return err
}
@@ -299,13 +295,13 @@
return err
}
values := map[string]interface{}{
- "privateKey": privKey,
- "publicKey": pubKey,
- "adminKey": adminKey,
+ "privateKey": keys.Private,
+ "publicKey": keys.Public,
+ "adminKey": adminPublicKey,
"reservedIP": bootstrapFlags.softServeIP,
}
installer := action.NewInstall(config)
- installer.Namespace = "pcloud"
+ installer.Namespace = bootstrapFlags.pcloudEnvName
installer.CreateNamespace = true
installer.ReleaseName = "soft-serve"
installer.Wait = true
@@ -317,8 +313,39 @@
return nil
}
-func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
- config, err := createActionConfig("pcloud")
+func installFluxcd(ss *soft.Client, pcloudEnvName string) error {
+ keys, err := installer.NewSSHKeyPair()
+ if err != nil {
+ return err
+ }
+ if err := ss.AddUser("flux", keys.Public); err != nil {
+ return err
+ }
+ if err := ss.MakeUserAdmin("flux"); err != nil {
+ return err
+ }
+ fmt.Printf("Creating /%s repo", pcloudEnvName)
+ if err := ss.AddRepository(pcloudEnvName, "# PCloud Systems"); err != nil {
+ return err
+ }
+ fmt.Println("Installing Flux")
+ ssPublic, err := ss.GetPublicKey()
+ if err != nil {
+ return err
+ }
+ if err := installFluxBootstrap(
+ ss.GetRepoAddress(pcloudEnvName),
+ ss.IP,
+ string(ssPublic),
+ keys.Private,
+ ); err != nil {
+ return err
+ }
+ return nil
+}
+
+func installFluxBootstrap(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
+ config, err := createActionConfig(bootstrapFlags.pcloudEnvName)
if err != nil {
return err
}
@@ -333,7 +360,7 @@
"privateKey": privateKey,
}
installer := action.NewInstall(config)
- installer.Namespace = "pcloud"
+ installer.Namespace = bootstrapFlags.pcloudEnvName
installer.CreateNamespace = true
installer.ReleaseName = "flux"
installer.Wait = true
@@ -345,150 +372,103 @@
return nil
}
-func installIngressPublic() error {
- config, err := createActionConfig("pcloud")
- if err != nil {
- return err
+func installInfrastructureServices(repo installer.RepoIO, global map[string]any) error {
+ values := map[string]any{
+ "Global": global,
}
- chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
- if err != nil {
- return err
+ appRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ install := func(name string) error {
+ app, err := appRepo.Find(name)
+ if err != nil {
+ return err
+ }
+ return repo.InstallApp(*app, "infrastructure", values)
}
- values := map[string]interface{}{
- "fullnameOverride": "pcloud-ingress-public",
- "controller": map[string]interface{}{
- "service": map[string]interface{}{
- "type": "LoadBalancer",
- },
- "ingressClassByName": true,
- "ingressClassResource": map[string]interface{}{
- "name": "pcloud-ingress-public",
- "enabled": true,
- "default": false,
- "controllerValue": "k8s.io/pcloud-ingress-public",
- },
- "config": map[string]interface{}{
- "proxy-body-size": "100M",
- },
- },
- "udp": map[string]interface{}{
- "6881": "lekva-app-qbittorrent/torrent:6881",
- },
- "tcp": map[string]interface{}{
- "6881": "lekva-app-qbittorrent/torrent:6881",
- },
+ appsToInstall := []string{
+ "resource-renderer-controller",
+ "headscale-controller",
+ "csi-driver-smb",
+ "ingress-public",
+ "cert-manager",
+ "cert-manager-webhook-gandi",
+ "cert-manager-webhook-gandi-role",
}
- installer := action.NewInstall(config)
- installer.Namespace = "pcloud-ingress-public"
- installer.CreateNamespace = true
- installer.ReleaseName = "ingress-public"
- installer.Wait = true
- installer.WaitForJobs = true
- installer.Timeout = 20 * time.Minute
- if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
- return err
- }
- return nil
-}
-
-func installCertManager() error {
- config, err := createActionConfig("pcloud-cert-manager")
- if err != nil {
- return err
- }
- chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
- if err != nil {
- return err
- }
- values := map[string]interface{}{
- "fullnameOverride": "pcloud-cert-manager",
- "installCRDs": true,
- "image": map[string]interface{}{
- "tag": "v1.11.1",
- "pullPolicy": "IfNotPresent",
- },
- }
- installer := action.NewInstall(config)
- installer.Namespace = "pcloud-cert-manager"
- installer.CreateNamespace = true
- installer.ReleaseName = "cert-manager"
- installer.Wait = true
- installer.WaitForJobs = true
- installer.Timeout = 20 * time.Minute
- if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
- return err
- }
- return nil
-}
-
-func installCertManagerWebhookGandi() error {
- config, err := createActionConfig("pcloud-cert-manager")
- if err != nil {
- return err
- }
- chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
- if err != nil {
- return err
- }
- values := map[string]interface{}{
- "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
- "certManager": map[string]interface{}{
- "namespace": "pcloud-cert-manager",
- "serviceAccountName": "pcloud-cert-manager",
- },
- "image": map[string]interface{}{
- "repository": "giolekva/cert-manager-webhook-gandi",
- "tag": "v0.2.0",
- "pullPolicy": "IfNotPresent",
- },
- "logLevel": 2,
- }
- installer := action.NewInstall(config)
- installer.Namespace = "pcloud-cert-manager"
- installer.CreateNamespace = false
- installer.ReleaseName = "cert-manager-webhook-gandi"
- installer.Wait = true
- installer.WaitForJobs = true
- installer.Timeout = 20 * time.Minute
- if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
- return err
- }
- return nil
-}
-
-func installSmbDriver() error {
- config, err := createActionConfig("pcloud-csi-driver-smb")
- if err != nil {
- return err
- }
- chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "csi-driver-smb"))
- if err != nil {
- return err
- }
- values := map[string]interface{}{}
- installer := action.NewInstall(config)
- installer.Namespace = "pcloud-csi-driver-smb"
- installer.CreateNamespace = true
- installer.ReleaseName = "csi-driver-smb"
- installer.Wait = true
- installer.WaitForJobs = true
- installer.Timeout = 20 * time.Minute
- if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
- return err
+ for _, name := range appsToInstall {
+ if err := install(name); err != nil {
+ return err
+ }
}
return nil
}
func configurePCloudRepo(repo installer.RepoIO) error {
- kust := installer.NewKustomization()
- kust.AddResources("pcloud-flux", "environments")
- if err := repo.WriteKustomization("kustomization.yaml", kust); err != nil {
+ {
+ kust := installer.NewKustomization()
+ kust.AddResources("pcloud-flux", "infrastructure", "environments")
+ if err := repo.WriteKustomization("kustomization.yaml", kust); err != nil {
+ return err
+ }
+ {
+ out, err := repo.Writer("infrastructure/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 # TODO(giolekva): use more generic name
+ namespace: pcloud # TODO(giolekva): configurable
+spec:
+ interval: 1m0s
+ url: https://github.com/giolekva/pcloud
+ ref:
+ branch: main
+`))
+ if err != nil {
+ return err
+ }
+ }
+ infraKust := installer.NewKustomization()
+ infraKust.AddResources("pcloud-charts.yaml")
+ if err := repo.WriteKustomization("infrastructure/kustomization.yaml", infraKust); err != nil {
+ return err
+ }
+ if err := repo.WriteKustomization("environments/kustomization.yaml", installer.NewKustomization()); err != nil {
+ return err
+ }
+ if err := repo.CommitAndPush("initialize pcloud directory structure"); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func installEnvManager(ss *soft.Client, repo installer.RepoIO, global map[string]any) error {
+ keys, err := installer.NewSSHKeyPair()
+ if err != nil {
return err
}
- if err := repo.WriteKustomization("environments/kustomization.yaml", installer.NewKustomization()); err != nil {
+ user := fmt.Sprintf("%s-env-manager", bootstrapFlags.pcloudEnvName)
+ if err := ss.AddUser(user, keys.Public); err != nil {
return err
}
- return repo.CommitAndPush("initialize pcloud directory structure, environments with kustomization.yaml-s")
+ if err := ss.MakeUserAdmin(user); err != nil {
+ return err
+ }
+ appRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ envManager, err := appRepo.Find("env-manager")
+ if err != nil {
+ return err
+ }
+ return repo.InstallApp(*envManager, "infrastructure", map[string]any{
+ "Global": global,
+ "Values": map[string]any{
+ "RepoIP": bootstrapFlags.softServeIP,
+ "SSHPrivateKey": keys.Private,
+ },
+ })
}
func createActionConfig(namespace string) (*action.Configuration, error) {
@@ -506,15 +486,3 @@
}
return config, 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
-}