installer: env form
diff --git a/core/installer/welcome/index.html b/core/installer/welcome/create-admin-account.html
similarity index 100%
rename from core/installer/welcome/index.html
rename to core/installer/welcome/create-admin-account.html
diff --git a/core/installer/welcome/create-env.html b/core/installer/welcome/create-env.html
new file mode 100644
index 0000000..6633d65
--- /dev/null
+++ b/core/installer/welcome/create-env.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en" data-theme="light">
+ <head>
+ <link rel="stylesheet" href="/static/pico.min.css">
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ </head>
+ <body>
+ <div style="display: contents">
+ <main class="container">
+ <article class="grid">
+ <div>
+ <form action="/env" method="POST">
+ <input
+ type="text"
+ name="name"
+ placeholder="Name"
+ required
+ />
+ <input
+ type="test"
+ name="domain"
+ placeholder="Domain"
+ required
+ />
+ <input
+ type="email"
+ name="contact-email"
+ placeholder="Contact Email"
+ required
+ />
+ <button type="submit" class="contrast">Create Environment</button>
+ </form>
+ </div>
+ </article>
+ </main>
+ </div>
+ </body>
+</html>
diff --git a/core/installer/welcome/env-tmpl/config-kustomization.yaml b/core/installer/welcome/env-tmpl/config-kustomization.yaml
new file mode 100644
index 0000000..d76bf0f
--- /dev/null
+++ b/core/installer/welcome/env-tmpl/config-kustomization.yaml
@@ -0,0 +1,13 @@
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
+kind: Kustomization
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+spec:
+ interval: 1m
+ path: "./"
+ sourceRef:
+ kind: GitRepository
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+ prune: true
diff --git a/core/installer/welcome/env-tmpl/config-secret.yaml b/core/installer/welcome/env-tmpl/config-secret.yaml
new file mode 100644
index 0000000..3ea515b
--- /dev/null
+++ b/core/installer/welcome/env-tmpl/config-secret.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+data:
+ identity: {{ .PrivateKey }}
+ identity.pub: {{ .PublicKey }}
+ known_hosts: {{ .KnownHosts }}
+kind: Secret
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+type: Opaque
diff --git a/core/installer/welcome/env-tmpl/config-source.yaml b/core/installer/welcome/env-tmpl/config-source.yaml
new file mode 100644
index 0000000..113a0b4
--- /dev/null
+++ b/core/installer/welcome/env-tmpl/config-source.yaml
@@ -0,0 +1,14 @@
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: GitRepository
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+spec:
+ gitImplementation: go-git
+ interval: 1m0s
+ ref:
+ branch: master
+ secretRef:
+ name: {{ .Name }}
+ timeout: 60s
+ url: ssh://{{ .GitHost }}/{{ .Name }}
diff --git a/core/installer/welcome/env-tmpl/kustomization.yaml b/core/installer/welcome/env-tmpl/kustomization.yaml
new file mode 100644
index 0000000..70db25f
--- /dev/null
+++ b/core/installer/welcome/env-tmpl/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- namespace.yaml
+- config-secret.yaml
+- config-source.yaml
+- config-kustomization.yaml
diff --git a/core/installer/welcome/env-tmpl/namespace.yaml b/core/installer/welcome/env-tmpl/namespace.yaml
new file mode 100644
index 0000000..0c14654
--- /dev/null
+++ b/core/installer/welcome/env-tmpl/namespace.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: {{ .Name }}
+ labels:
+ pcloud-instance-id: {{ .Name }}
+ annotations:
+ helm.sh/resource-policy: keep
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
new file mode 100644
index 0000000..e871830
--- /dev/null
+++ b/core/installer/welcome/env.go
@@ -0,0 +1,304 @@
+package welcome
+
+import (
+ "embed"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "path"
+ "text/template"
+
+ "github.com/labstack/echo/v4"
+
+ "github.com/giolekva/pcloud/core/installer"
+ "github.com/giolekva/pcloud/core/installer/soft"
+)
+
+//go:embed env-tmpl
+var filesTmpls embed.FS
+
+//go:embed create-env.html
+var createEnvFormHtml string
+
+type EnvServer struct {
+ port int
+ ss *soft.Client
+ repo installer.RepoIO
+}
+
+func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO) *EnvServer {
+ return &EnvServer{
+ port,
+ ss,
+ repo,
+ }
+}
+
+func (s *EnvServer) Start() {
+ e := echo.New()
+ e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
+ e.GET("/env", s.createEnvForm)
+ e.POST("/env", s.createEnv)
+ log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
+}
+
+func (s *EnvServer) createEnvForm(c echo.Context) error {
+ return c.HTML(http.StatusOK, createEnvFormHtml)
+}
+
+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 := func() error {
+ var err error
+ f, err := c.FormParams()
+ if err != nil {
+ return err
+ }
+ if req.Name, err = getFormValue(f, "name"); err != nil {
+ return err
+ }
+ if req.Domain, err = getFormValue(f, "domain"); err != nil {
+ return err
+ }
+ if req.ContactEmail, err = getFormValue(f, "contact-email"); err != nil {
+ return err
+ }
+ return nil
+ }(); err != nil {
+ 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 c.String(http.StatusOK, "OK")
+}
+
+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
+ }
+ repoIP := "192.168.0.211" // TODO(giolekva): configure
+ 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": repoIP,
+ "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", 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
+ }
+ return nil
+}
diff --git a/core/installer/welcome/main.go b/core/installer/welcome/welcome.go
similarity index 98%
rename from core/installer/welcome/main.go
rename to core/installer/welcome/welcome.go
index 03a4047..04e08f5 100644
--- a/core/installer/welcome/main.go
+++ b/core/installer/welcome/welcome.go
@@ -13,7 +13,7 @@
"github.com/giolekva/pcloud/core/installer"
)
-//go:embed index.html
+//go:embed create-admin-account.html
var indexHtml string
//go:embed static/*