blob: 3d3e332e9d358351c4019bd093ea313f743c3d46 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +04004 "bytes"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04005 "embed"
6 "encoding/base64"
7 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04008 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04009 "fmt"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040010 htemplate "html/template"
11 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040012 "io/fs"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040013 "log"
14 "net/http"
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +040015 "net/netip"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040016 "path"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040017 "path/filepath"
18 "strings"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040019 "text/template"
20
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040021 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040022 "github.com/charmbracelet/keygen"
23 "github.com/gorilla/mux"
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +040024 "github.com/miekg/dns"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040025
26 "github.com/giolekva/pcloud/core/installer"
27 "github.com/giolekva/pcloud/core/installer/soft"
28)
29
30//go:embed env-tmpl
31var filesTmpls embed.FS
32
33//go:embed create-env.html
34var createEnvFormHtml string
35
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040036//go:embed env-created.html
37var envCreatedHtml string
38
39type Status string
40
41const (
42 StatusActive Status = "ACTIVE"
43 StatusAccepted Status = "ACCEPTED"
44)
45
46// TODO(giolekva): add CreatedAt and ValidUntil
47type invitation struct {
48 Token string `json:"token"`
49 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040050}
51
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040052type EnvServer struct {
53 port int
54 ss *soft.Client
55 repo installer.RepoIO
56 nsCreator installer.NamespaceCreator
57 nameGenerator installer.NameGenerator
58}
59
60func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator, nameGenerator installer.NameGenerator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040061 return &EnvServer{
62 port,
63 ss,
64 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040065 nsCreator,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040066 nameGenerator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040067 }
68}
69
70func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040071 r := mux.NewRouter()
72 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040073 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
74 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040075 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040076 http.Handle("/", r)
77 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040078}
79
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040080func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040081 if _, err := w.Write([]byte(createEnvFormHtml)); err != nil {
82 http.Error(w, err.Error(), http.StatusInternalServerError)
83 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040084}
85
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040086func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
87 invitations, err := s.readInvitations()
88 if err != nil {
89 http.Error(w, err.Error(), http.StatusInternalServerError)
90 return
91 }
92 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
93 if err != nil {
94 http.Error(w, err.Error(), http.StatusInternalServerError)
95 return
96
97 }
98 invitations = append(invitations, invitation{token, StatusActive})
99 if err := s.writeInvitations(invitations); err != nil {
100 http.Error(w, err.Error(), http.StatusInternalServerError)
101 return
102 }
103 if _, err := w.Write([]byte("OK")); err != nil {
104 http.Error(w, err.Error(), http.StatusInternalServerError)
105 return
106 }
107}
108
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400109type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400110 Name string
111 ContactEmail string `json:"contactEmail"`
112 Domain string `json:"domain"`
113 AdminPublicKey string `json:"adminPublicKey"`
114 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400115}
116
117func (s *EnvServer) readInvitations() ([]invitation, error) {
118 r, err := s.repo.Reader("invitations")
119 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400120 if errors.Is(err, fs.ErrNotExist) {
121 return make([]invitation, 0), nil
122 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400123 return nil, err
124 }
125 defer r.Close()
126 dec := json.NewDecoder(r)
127 invitations := make([]invitation, 0)
128 for {
129 var i invitation
130 if err := dec.Decode(&i); err == io.EOF {
131 break
132 }
133 invitations = append(invitations, i)
134 }
135 return invitations, nil
136}
137
138func (s *EnvServer) writeInvitations(invitations []invitation) error {
139 w, err := s.repo.Writer("invitations")
140 if err != nil {
141 return err
142 }
143 defer w.Close()
144 enc := json.NewEncoder(w)
145 for _, i := range invitations {
146 if err := enc.Encode(i); err != nil {
147 return err
148 }
149 }
150 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400151}
152
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400153func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400154 var req createEnvReq
155 if err := func() error {
156 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400157 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400158 return err
159 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400160 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400161 return err
162 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400163 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400164 return err
165 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400166 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400167 return err
168 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400169 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
170 return err
171 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400172 return nil
173 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400174 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400175 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400176 }
177 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400178 return req, nil
179}
180
181func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400182 invitations, err := s.readInvitations()
183 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400184 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400185 }
186 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400187 for i := range invitations {
188 if invitations[i].Token == token && invitations[i].Status == StatusActive {
189 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400190 found = true
191 break
192 }
193 }
194 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400195 return fmt.Errorf("Invitation not found")
196 }
197 return s.writeInvitations(invitations)
198}
199
200func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
201 req, err := extractRequest(r)
202 if err != nil {
203 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400204 return
205 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400206 var env installer.EnvConfig
207 cr, err := s.repo.Reader("config.yaml")
208 if err != nil {
209 http.Error(w, err.Error(), http.StatusInternalServerError)
210 return
211 }
212 defer cr.Close()
213 if err := installer.ReadYaml(cr, &env); err != nil {
214 http.Error(w, err.Error(), http.StatusInternalServerError)
215 return
216 }
217 if err := s.acceptInvitation(req.SecretToken); err != nil {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400218 http.Error(w, err.Error(), http.StatusInternalServerError)
219 return
220 }
221 if name, err := s.nameGenerator.Generate(); err != nil {
222 http.Error(w, err.Error(), http.StatusInternalServerError)
223 return
224 } else {
225 req.Name = name
226 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400227 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
228 ssApp, err := appsRepo.Find("soft-serve")
229 if err != nil {
230 http.Error(w, err.Error(), http.StatusInternalServerError)
231 return
232 }
233 ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", req.Name))
234 if err != nil {
235 http.Error(w, err.Error(), http.StatusInternalServerError)
236 return
237 }
238 ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", req.Name))
239 if err != nil {
240 http.Error(w, err.Error(), http.StatusInternalServerError)
241 return
242 }
243 ssValues := map[string]any{
244 "ChartRepositoryNamespace": env.Name,
245 "ServiceType": "ClusterIP",
246 "PrivateKey": string(ssKeys.RawPrivateKey()),
247 "PublicKey": string(ssKeys.RawAuthorizedKey()),
248 "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
249 "Ingress": map[string]any{
250 "Enabled": false,
251 },
252 }
253 derived := installer.Derived{
254 Global: installer.Values{
255 Id: req.Name,
256 PCloudEnvName: env.Name,
257 },
258 Release: installer.Release{
259 Namespace: req.Name,
260 },
261 Values: ssValues,
262 }
263 if err := s.nsCreator.Create(req.Name); err != nil {
264 http.Error(w, err.Error(), http.StatusInternalServerError)
265 return
266 }
267 if err := s.repo.InstallApp(*ssApp, filepath.Join("/environments", req.Name, "config-repo"), ssValues, derived); err != nil {
268 http.Error(w, err.Error(), http.StatusInternalServerError)
269 return
270 }
271 k := installer.NewKustomization()
272 k.AddResources("config-repo")
273 if err := s.repo.WriteKustomization(filepath.Join("/environments", req.Name, "kustomization.yaml"), k); err != nil {
274 http.Error(w, err.Error(), http.StatusInternalServerError)
275 return
276 }
277 ssClient, err := soft.WaitForClient(
278 fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", req.Name, 22),
279 ssAdminKeys.RawPrivateKey(),
280 log.Default())
281 if err != nil {
282 http.Error(w, err.Error(), http.StatusInternalServerError)
283 return
284 }
285 if err := ssClient.AddPublicKey("admin", req.AdminPublicKey); err != nil {
286 http.Error(w, err.Error(), http.StatusInternalServerError)
287 return
288 }
289 defer func() {
290 if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
291 http.Error(w, err.Error(), http.StatusInternalServerError)
292 return
293 }
294 }()
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400295 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
296 keys, err := installer.NewSSHKeyPair(fluxUserName)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400297 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400298 http.Error(w, err.Error(), http.StatusInternalServerError)
299 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400300 }
301 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400302 if err := ssClient.AddRepository("config"); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400303 http.Error(w, err.Error(), http.StatusInternalServerError)
304 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400305 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400306 repo, err := ssClient.GetRepo("config")
307 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400308 http.Error(w, err.Error(), http.StatusInternalServerError)
309 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400310 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400311 repoIO := installer.NewRepoIO(repo, ssClient.Signer)
312 if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", req.Name), "readme"); err != nil {
313 http.Error(w, err.Error(), http.StatusInternalServerError)
314 return
315 }
316 if err := ssClient.AddUser(fluxUserName, keys.AuthorizedKey()); err != nil {
317 http.Error(w, err.Error(), http.StatusInternalServerError)
318 return
319 }
320 if err := ssClient.AddReadOnlyCollaborator("config", fluxUserName); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400321 http.Error(w, err.Error(), http.StatusInternalServerError)
322 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400323 }
324 }
325 {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400326 repo, err := ssClient.GetRepo("config")
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400327 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400328 http.Error(w, err.Error(), http.StatusInternalServerError)
329 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400330 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400331 if err := initNewEnv(ssClient, installer.NewRepoIO(repo, ssClient.Signer), s.nsCreator, req, env.Name, env.PublicIP); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400332 http.Error(w, err.Error(), http.StatusInternalServerError)
333 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400334 }
335 }
336 {
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400337 ssPublicKeys, err := ssClient.GetPublicKeys()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400338 if err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400339 http.Error(w, err.Error(), http.StatusInternalServerError)
340 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400341 }
342 if err := addNewEnv(
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400343 s.repo,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400344 req,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400345 strings.Split(ssClient.Addr, ":")[0],
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400346 keys,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400347 ssPublicKeys,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400348 ); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400349 http.Error(w, err.Error(), http.StatusInternalServerError)
350 return
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400351 }
352 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400353 tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
354 if err != nil {
355 http.Error(w, err.Error(), http.StatusInternalServerError)
356 return
357 }
358 if err := tmpl.Execute(w, map[string]any{
359 "Domain": req.Domain,
360 }); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400361 http.Error(w, err.Error(), http.StatusInternalServerError)
362 return
363 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400364}
365
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400366type DNSSecKey struct {
367 Basename string `json:"basename,omitempty"`
368 Key []byte `json:"key,omitempty"`
369 Private []byte `json:"private,omitempty"`
370 DS []byte `json:"ds,omitempty"`
371}
372
373func newDNSSecKey(zone string) (DNSSecKey, error) {
374 key := &dns.DNSKEY{
375 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
376 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
377 }
378 priv, err := key.Generate(256)
379 if err != nil {
380 return DNSSecKey{}, err
381 }
382 return DNSSecKey{
383 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
384 Key: []byte(key.String()),
385 Private: []byte(key.PrivateKeyString(priv)),
386 DS: []byte(key.ToDS(dns.SHA256).String()),
387 }, nil
388}
389
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400390func initNewEnv(
391 ss *soft.Client,
392 r installer.RepoIO,
393 nsCreator installer.NamespaceCreator,
394 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400395 pcloudEnvName string,
396 pcloudPublicIP string,
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400397) error {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400398 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400399 if err != nil {
400 return err
401 }
402 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400403 // TODO(giolekva): private domain can be configurable as well
404 config := installer.Config{
405 Values: installer.Values{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400406 PCloudEnvName: pcloudEnvName,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400407 Id: req.Name,
408 ContactEmail: req.ContactEmail,
409 Domain: req.Domain,
410 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400411 PublicIP: pcloudPublicIP,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400412 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
413 },
414 }
415 if err := r.WriteYaml("config.yaml", config); err != nil {
416 return err
417 }
418 {
419 out, err := r.Writer("pcloud-charts.yaml")
420 if err != nil {
421 return err
422 }
423 defer out.Close()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400424 _, err = fmt.Fprintf(out, `
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400425apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400426kind: GitRepository
427metadata:
428 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400429 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400430spec:
431 interval: 1m0s
432 url: https://github.com/giolekva/pcloud
433 ref:
434 branch: main
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400435`, req.Name)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400436 if err != nil {
437 return err
438 }
439 }
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400440 {
441 key, err := newDNSSecKey(req.Domain)
442 if err != nil {
443 return err
444 }
445 out, err := r.Writer("dns-zone.yaml")
446 if err != nil {
447 return err
448 }
449 defer out.Close()
450 dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
451apiVersion: dodo.cloud.dodo.cloud/v1
452kind: DNSZone
453metadata:
454 name: dns-zone
455 namespace: {{ .namespace }}
456spec:
457 zone: {{ .zone }}
458 privateIP: 10.1.0.1
459 publicIPs:
460 - 135.181.48.180
461 - 65.108.39.172
462 - 65.108.39.171
463 nameservers:
464 - 135.181.48.180
465 - 65.108.39.172
466 - 65.108.39.171
467 dnssec:
468 enabled: true
469 secretName: dnssec-key
470---
471apiVersion: v1
472kind: Secret
473metadata:
474 name: dnssec-key
475 namespace: {{ .namespace }}
476type: Opaque
477data:
478 basename: {{ .dnssec.Basename | b64enc }}
479 key: {{ .dnssec.Key | toString | b64enc }}
480 private: {{ .dnssec.Private | toString | b64enc }}
481 ds: {{ .dnssec.DS | toString | b64enc }}
482`)
483 if err != nil {
484 return err
485 }
486 if err := dnsZoneTmpl.Execute(out, map[string]any{
487 "namespace": req.Name,
488 "zone": req.Domain,
489 "dnssec": key,
490 }); err != nil {
491 return err
492 }
493 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400494 rootKust := installer.NewKustomization()
Giorgi Lekveishvilie8b2f012023-11-30 19:05:03 +0400495 rootKust.AddResources("pcloud-charts.yaml", "dns-zone.yaml", "apps")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400496 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
497 return err
498 }
499 appsKust := installer.NewKustomization()
500 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
501 return err
502 }
503 r.CommitAndPush("initialize config")
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400504 nsGen := installer.NewPrefixGenerator(req.Name + "-")
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400505 emptySuffixGen := installer.NewEmptySuffixGenerator()
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400506 ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
507 if err != nil {
508 return err
509 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400510 {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400511 headscaleIP := ingressPrivateIP.Next()
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400512 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400513 if err != nil {
514 return err
515 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400516 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400517 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400518 "From": ingressPrivateIP.String(),
519 "To": ingressPrivateIP.String(),
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400520 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400521 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400522 }); err != nil {
523 return err
524 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400525 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400526 "Name": fmt.Sprintf("%s-headscale", req.Name),
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400527 "From": headscaleIP.String(),
528 "To": headscaleIP.String(),
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400529 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400530 "Namespace": "metallb-system",
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400531 }); err != nil {
532 return err
533 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400534 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400535 "Name": req.Name,
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400536 "From": "10.1.0.100", // TODO(gio): auto-generate
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400537 "To": "10.1.0.254",
538 "AutoAssign": false,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400539 "Namespace": "metallb-system",
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400540 }); err != nil {
541 return err
542 }
543 }
544 {
545 app, err := appsRepo.Find("ingress-private")
546 if err != nil {
547 return err
548 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400549 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400550 return err
551 }
552 }
553 {
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400554 app, err := appsRepo.Find("tailscale-proxy")
555 if err != nil {
556 return err
557 }
558 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400559 "Hostname": "private-network-proxy",
560 "Username": "private-network-proxy",
561 "IPSubnet": "10.1.0.0/24",
Giorgi Lekveishvili6ae65d12023-12-04 15:37:53 +0400562 }); err != nil {
563 return err
564 }
565 // TODO(giolekva): headscale accept routes
566 }
567 {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400568 app, err := appsRepo.Find("certificate-issuer-public")
569 if err != nil {
570 return err
571 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400572 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400573 return err
574 }
575 }
576 {
577 app, err := appsRepo.Find("core-auth")
578 if err != nil {
579 return err
580 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400581 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400582 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
583 }); err != nil {
584 return err
585 }
586 }
587 {
588 app, err := appsRepo.Find("headscale")
589 if err != nil {
590 return err
591 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400592 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400593 "Subdomain": "headscale",
594 }); err != nil {
595 return err
596 }
597 }
598 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400599 keys, err := installer.NewSSHKeyPair("welcome")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400600 if err != nil {
601 return err
602 }
603 user := fmt.Sprintf("%s-welcome", req.Name)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400604 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400605 return err
606 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400607 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400608 return err
609 }
610 app, err := appsRepo.Find("welcome")
611 if err != nil {
612 return err
613 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400614 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400615 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400616 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400617 }); err != nil {
618 return err
619 }
620 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400621 {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400622 user := fmt.Sprintf("%s-appmanager", req.Name)
623 keys, err := installer.NewSSHKeyPair(user)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400624 if err != nil {
625 return err
626 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400627 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400628 return err
629 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400630 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400631 return err
632 }
633 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
634 if err != nil {
635 return err
636 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400637 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400638 "RepoAddr": ss.GetRepoAddress("config"),
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400639 "SSHPrivateKey": string(keys.RawPrivateKey()),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400640 }); err != nil {
641 return err
642 }
643 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400644 return nil
645}
646
647func addNewEnv(
648 repoIO installer.RepoIO,
649 req createEnvReq,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400650 repoHost string,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400651 keys *keygen.KeyPair,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400652 configRepoPublicKeys []string,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400653) error {
654 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
655 if err != nil {
656 return err
657 }
658 kust.AddResources(req.Name)
659 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
660 if err != nil {
661 return err
662 }
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400663 var knownHosts bytes.Buffer
664 for _, key := range configRepoPublicKeys {
665 fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
666 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400667 for _, tmpl := range tmpls.Templates() {
668 dstPath := path.Join("environments", req.Name, tmpl.Name())
669 dst, err := repoIO.Writer(dstPath)
670 if err != nil {
671 return err
672 }
673 defer dst.Close()
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400674
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400675 if err := tmpl.Execute(dst, map[string]string{
676 "Name": req.Name,
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400677 "PrivateKey": base64.StdEncoding.EncodeToString(keys.RawPrivateKey()),
678 "PublicKey": base64.StdEncoding.EncodeToString(keys.RawAuthorizedKey()),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400679 "RepoHost": repoHost,
680 "RepoName": "config",
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400681 "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400682 }); err != nil {
683 return err
684 }
685 }
686 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
687 return err
688 }
689 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
690 return err
691 }
692 return nil
693}