installer: welcome
diff --git a/core/installer/welcome/main.go b/core/installer/welcome/main.go
new file mode 100644
index 0000000..c3279d8
--- /dev/null
+++ b/core/installer/welcome/main.go
@@ -0,0 +1,90 @@
+package welcome
+
+import (
+ "embed"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+
+ "github.com/giolekva/pcloud/core/installer"
+)
+
+//go:embed index.html
+var indexHtml string
+
+//go:embed static/*
+var staticAssets embed.FS
+
+type Server struct {
+ port int
+ repo installer.RepoIO
+}
+
+func NewServer(port int, repo installer.RepoIO) *Server {
+ return &Server{
+ port,
+ repo,
+ }
+}
+
+func (s *Server) Start() {
+ e := echo.New()
+ e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
+ e.POST("/create-admin-account", s.createAdminAccount)
+ e.GET("/", s.createAdminAccountForm)
+ log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
+}
+
+func (s *Server) createAdminAccountForm(c echo.Context) error {
+ return c.HTML(http.StatusOK, indexHtml)
+}
+
+type createAdminAccountReq struct {
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"` // TODO(giolekva): actually use this
+ GandiAPIToken string `json:"gandiAPIToken,omitempty"`
+ SecretToken string `json:"secretToken,omitempty"`
+}
+
+func (s *Server) createAdminAccount(c echo.Context) error {
+ var req createAdminAccountReq
+ if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
+ return err
+ }
+ // TODO(giolekva): accounts-ui create user req
+ {
+ appManager, err := installer.NewAppManager(s.repo)
+ if err != nil {
+ return err
+ }
+ appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ {
+ app, err := appsRepo.Find("certificate-issuer-private")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, map[string]any{
+ "GandiAPIToken": req.GandiAPIToken,
+ }); err != nil {
+ return err
+ }
+ }
+ {
+ app, err := appsRepo.Find("tailscale-proxy")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, map[string]any{
+ "Username": req.Username,
+ "IPSubnet": "10.1.0.0/24", // TODO(giolekva): this should be taken from the config generated during new env creation
+ }); err != nil {
+ return err
+ }
+ // TODO(giolekva): headscale accept routes
+ }
+ }
+ return c.String(http.StatusOK, "OK")
+}