blob: 9d99e170364a23e1ffcbbd0cb7fc1af7e76d0525 [file] [log] [blame]
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +04001// TODO
2// * ns pcloud not found
3
giolekva8aa73e82022-07-09 11:34:39 +04004package main
5
6import (
7 "context"
giolekva8aa73e82022-07-09 11:34:39 +04008 _ "embed"
giolekva8aa73e82022-07-09 11:34:39 +04009 "fmt"
giolekva8aa73e82022-07-09 11:34:39 +040010 "log"
11 "os"
12 "path/filepath"
13 "time"
14
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040015 "github.com/giolekva/pcloud/core/installer"
giolekva8aa73e82022-07-09 11:34:39 +040016 "github.com/giolekva/pcloud/core/installer/soft"
17 "github.com/spf13/cobra"
18 "helm.sh/helm/v3/pkg/action"
19 "helm.sh/helm/v3/pkg/chart/loader"
20 "helm.sh/helm/v3/pkg/kube"
21)
22
23var bootstrapFlags struct {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040024 chartsDir string
25 adminPubKey string
26 adminPrivKey string
27 storageDir string
28 volumeDefaultReplicaCount int
29 softServeIP string
giolekva8aa73e82022-07-09 11:34:39 +040030}
31
32func bootstrapCmd() *cobra.Command {
33 cmd := &cobra.Command{
34 Use: "bootstrap",
35 RunE: bootstrapCmdRun,
36 }
37 cmd.Flags().StringVar(
38 &bootstrapFlags.chartsDir,
39 "charts-dir",
40 "",
41 "",
42 )
43 cmd.Flags().StringVar(
44 &bootstrapFlags.adminPubKey,
45 "admin-pub-key",
46 "",
47 "",
48 )
49 cmd.Flags().StringVar(
50 &bootstrapFlags.adminPrivKey,
51 "admin-priv-key",
52 "",
53 "",
54 )
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040055 cmd.Flags().StringVar(
56 &bootstrapFlags.storageDir,
57 "storage-dir",
58 "",
59 "",
60 )
61 cmd.Flags().IntVar(
62 &bootstrapFlags.volumeDefaultReplicaCount,
63 "volume-default-replica-count",
64 3,
65 "",
66 )
67 cmd.Flags().StringVar(
68 &bootstrapFlags.softServeIP,
69 "soft-serve-ip",
70 "",
71 "",
72 )
giolekva8aa73e82022-07-09 11:34:39 +040073 return cmd
74}
75
76func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +040077 adminPubKey, adminPrivKey, err := readAdminKeys()
78 if err != nil {
79 return err
80 }
81 fluxPub, fluxPriv, err := installer.GenerateSSHKeys()
82 if err != nil {
83 return err
84 }
85 softServePub, softServePriv, err := installer.GenerateSSHKeys()
86 if err != nil {
87 return err
88 }
89 if err := installMetallbNamespace(); err != nil {
90 return err
91 }
92 if err := installMetallb(); err != nil {
93 return err
94 }
95 time.Sleep(3 * time.Minute)
96 if err := installMetallbConfig(); err != nil {
97 return err
98 }
99 if err := installLonghorn(); err != nil {
100 return err
101 }
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400102 time.Sleep(2 * time.Minute)
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400103 if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
104 return err
105 }
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400106 time.Sleep(2 * time.Minute)
107 ss, err := soft.NewClient(bootstrapFlags.softServeIP, 2222, adminPrivKey, log.Default())
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400108 if err != nil {
109 return err
110 }
111 if err := ss.AddUser("flux", fluxPub); err != nil {
112 return err
113 }
114 if err := ss.MakeUserAdmin("flux"); err != nil {
115 return err
116 }
117 fmt.Println("Creating /pcloud repo")
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400118 if err := ss.AddRepository("pcloud", "# PCloud Systems"); err != nil {
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400119 return err
120 }
121 fmt.Println("Installing Flux")
122 if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
123 return err
124 }
125 // TODO(giolekva): everything below must be installed using Flux
126 if err := installIngressPublic(); err != nil {
127 return err
128 }
129 if err := installCertManager(); err != nil {
130 return err
131 }
132 if err := installCertManagerWebhookGandi(); err != nil {
133 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400134 }
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400135 if err := installSmbDriver(); err != nil {
136 return err
137 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400138 return nil
139}
140
141func installMetallbNamespace() error {
142 fmt.Println("Installing metallb namespace")
143 // config, err := createActionConfig("default")
144 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400145 if err != nil {
146 return err
147 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400148 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "namespace"))
giolekva8aa73e82022-07-09 11:34:39 +0400149 if err != nil {
150 return err
151 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400152 values := map[string]interface{}{
153 // "namespace": "pcloud-metallb",
154 "namespace": "metallb-system",
155 "labels": []string{
156 "pod-security.kubernetes.io/audit: privileged",
157 "pod-security.kubernetes.io/enforce: privileged",
158 "pod-security.kubernetes.io/warn: privileged",
159 },
160 }
161 installer := action.NewInstall(config)
162 installer.Namespace = "pcloud"
163 installer.ReleaseName = "metallb-ns"
164 installer.Wait = true
165 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
166 return err
167 }
168 return nil
169}
170
171func installMetallb() error {
172 fmt.Println("Installing metallb")
173 // config, err := createActionConfig("default")
174 config, err := createActionConfig("metallb-system")
giolekva8aa73e82022-07-09 11:34:39 +0400175 if err != nil {
176 return err
177 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400178 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb"))
giolekva8aa73e82022-07-09 11:34:39 +0400179 if err != nil {
180 return err
181 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400182 values := map[string]interface{}{ // TODO(giolekva): add loadBalancerClass?
183 "controller": map[string]interface{}{
184 "image": map[string]interface{}{
185 "repository": "quay.io/metallb/controller",
186 "tag": "v0.13.9",
187 "pullPolicy": "IfNotPresent",
188 },
189 "logLevel": "info",
190 },
191 "speaker": map[string]interface{}{
192 "image": map[string]interface{}{
193 "repository": "quay.io/metallb/speaker",
194 "tag": "v0.13.9",
195 "pullPolicy": "IfNotPresent",
196 },
197 "logLevel": "info",
198 },
199 }
200 installer := action.NewInstall(config)
201 installer.Namespace = "metallb-system" // "pcloud-metallb"
202 installer.CreateNamespace = true
203 installer.ReleaseName = "metallb"
204 installer.IncludeCRDs = true
205 // installer.Wait = true
206 installer.Timeout = 20 * time.Minute
207 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400208 return err
209 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400210 return nil
211}
212
213func installMetallbConfig() error {
214 fmt.Println("Installing metallb-config")
215 // config, err := createActionConfig("default")
216 config, err := createActionConfig("metallb-system")
217 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400218 return err
219 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400220 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb-config"))
221 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400222 return err
223 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400224 values := map[string]interface{}{
225 "from": "192.168.0.210",
226 "to": "192.168.0.240",
227 }
228 installer := action.NewInstall(config)
229 installer.Namespace = "metallb-system" // "pcloud-metallb"
230 installer.CreateNamespace = true
231 installer.ReleaseName = "metallb-cfg"
232 installer.Wait = true
233 installer.Timeout = 20 * time.Minute
234 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
235 return err
236 }
237 return nil
238}
239
240func installLonghorn() error {
241 fmt.Println("Installing Longhorn")
242 config, err := createActionConfig("pcloud")
243 if err != nil {
244 return err
245 }
246 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "longhorn"))
247 if err != nil {
248 return err
249 }
250 values := map[string]interface{}{
251 "defaultSettings": map[string]interface{}{
252 "defaultDataPath": bootstrapFlags.storageDir,
253 },
254 "persistence": map[string]interface{}{
255 "defaultClassReplicaCount": bootstrapFlags.volumeDefaultReplicaCount,
256 },
257 "service": map[string]interface{}{
258 "ui": map[string]interface{}{
259 "type": "LoadBalancer",
260 },
261 },
262 "ingress": map[string]interface{}{
263 "enabled": false,
264 },
265 }
266 installer := action.NewInstall(config)
267 installer.Namespace = "longhorn-system"
268 installer.CreateNamespace = true
269 installer.ReleaseName = "longhorn"
270 installer.Wait = true
271 installer.Timeout = 20 * time.Minute
272 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400273 return err
274 }
275 return nil
276}
277
278func installSoftServe(pubKey, privKey, adminKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400279 fmt.Println("Installing SoftServe")
280 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400281 if err != nil {
282 return err
283 }
284 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "soft-serve"))
285 if err != nil {
286 return err
287 }
288 values := map[string]interface{}{
289 "privateKey": privKey,
290 "publicKey": pubKey,
291 "adminKey": adminKey,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400292 "reservedIP": bootstrapFlags.softServeIP,
giolekva8aa73e82022-07-09 11:34:39 +0400293 }
294 installer := action.NewInstall(config)
295 installer.Namespace = "pcloud"
296 installer.CreateNamespace = true
297 installer.ReleaseName = "soft-serve"
298 installer.Wait = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400299 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400300 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
301 return err
302 }
303 return nil
304}
305
306func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400307 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400308 if err != nil {
309 return err
310 }
311 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "flux-bootstrap"))
312 if err != nil {
313 return err
314 }
315 values := map[string]interface{}{
316 "repositoryAddress": repoAddr,
317 "repositoryHost": repoHost,
318 "repositoryHostPublicKey": repoHostPubKey,
319 "privateKey": privateKey,
320 }
321 installer := action.NewInstall(config)
322 installer.Namespace = "pcloud"
323 installer.CreateNamespace = true
324 installer.ReleaseName = "flux"
325 installer.Wait = true
326 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400327 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400328 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
329 return err
330 }
331 return nil
332}
333
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400334func installIngressPublic() error {
335 config, err := createActionConfig("pcloud")
336 if err != nil {
337 return err
338 }
339 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
340 if err != nil {
341 return err
342 }
343 values := map[string]interface{}{
344 "fullnameOverride": "pcloud-ingress-public",
345 "controller": map[string]interface{}{
346 "service": map[string]interface{}{
347 "type": "LoadBalancer",
348 },
349 "ingressClassByName": true,
350 "ingressClassResource": map[string]interface{}{
351 "name": "pcloud-ingress-public",
352 "enabled": true,
353 "default": false,
354 "controllerValue": "k8s.io/pcloud-ingress-public",
355 },
356 "config": map[string]interface{}{
357 "proxy-body-size": "100M",
358 },
359 },
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400360 "udp": map[string]interface{}{
361 "6881": "lekva-app-qbittorrent/torrent:6881",
362 },
363 "tcp": map[string]interface{}{
364 "6881": "lekva-app-qbittorrent/torrent:6881",
365 },
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400366 }
367 installer := action.NewInstall(config)
368 installer.Namespace = "pcloud-ingress-public"
369 installer.CreateNamespace = true
370 installer.ReleaseName = "ingress-public"
371 installer.Wait = true
372 installer.WaitForJobs = true
373 installer.Timeout = 20 * time.Minute
374 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
375 return err
376 }
377 return nil
378}
379
380func installCertManager() error {
381 config, err := createActionConfig("pcloud-cert-manager")
382 if err != nil {
383 return err
384 }
385 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
386 if err != nil {
387 return err
388 }
389 values := map[string]interface{}{
390 "fullnameOverride": "pcloud-cert-manager",
391 "installCRDs": true,
392 "image": map[string]interface{}{
393 "tag": "v1.11.1",
394 "pullPolicy": "IfNotPresent",
395 },
396 }
397 installer := action.NewInstall(config)
398 installer.Namespace = "pcloud-cert-manager"
399 installer.CreateNamespace = true
400 installer.ReleaseName = "cert-manager"
401 installer.Wait = true
402 installer.WaitForJobs = true
403 installer.Timeout = 20 * time.Minute
404 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
405 return err
406 }
407 return nil
408}
409
410func installCertManagerWebhookGandi() error {
411 config, err := createActionConfig("pcloud-cert-manager")
412 if err != nil {
413 return err
414 }
415 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
416 if err != nil {
417 return err
418 }
419 values := map[string]interface{}{
420 "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
421 "certManager": map[string]interface{}{
422 "namespace": "pcloud-cert-manager",
423 "serviceAccountName": "pcloud-cert-manager",
424 },
425 "image": map[string]interface{}{
426 "repository": "giolekva/cert-manager-webhook-gandi",
427 "tag": "v0.2.0",
428 "pullPolicy": "IfNotPresent",
429 },
430 "logLevel": 2,
431 }
432 installer := action.NewInstall(config)
433 installer.Namespace = "pcloud-cert-manager"
434 installer.CreateNamespace = false
435 installer.ReleaseName = "cert-manager-webhook-gandi"
436 installer.Wait = true
437 installer.WaitForJobs = true
438 installer.Timeout = 20 * time.Minute
439 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
440 return err
441 }
442 return nil
443}
444
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400445func installSmbDriver() error {
446 config, err := createActionConfig("pcloud-csi-driver-smb")
447 if err != nil {
448 return err
449 }
450 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "csi-driver-smb"))
451 if err != nil {
452 return err
453 }
454 values := map[string]interface{}{}
455 installer := action.NewInstall(config)
456 installer.Namespace = "pcloud-csi-driver-smb"
457 installer.CreateNamespace = true
458 installer.ReleaseName = "csi-driver-smb"
459 installer.Wait = true
460 installer.WaitForJobs = true
461 installer.Timeout = 20 * time.Minute
462 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
463 return err
464 }
465 return nil
466}
467
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400468func createActionConfig(namespace string) (*action.Configuration, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400469 config := new(action.Configuration)
470 if err := config.Init(
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400471 kube.GetConfig(rootFlags.kubeConfig, "", namespace),
472 namespace,
giolekva8aa73e82022-07-09 11:34:39 +0400473 "",
474 func(fmtString string, args ...interface{}) {
475 fmt.Printf(fmtString, args...)
476 fmt.Println()
477 },
478 ); err != nil {
479 return nil, err
480 }
481 return config, nil
482}
483
giolekva8aa73e82022-07-09 11:34:39 +0400484func readAdminKeys() ([]byte, []byte, error) {
485 pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
486 if err != nil {
487 return nil, nil, err
488 }
489 privKey, err := os.ReadFile(bootstrapFlags.adminPrivKey)
490 if err != nil {
491 return nil, nil, err
492 }
493 return pubKey, privKey, nil
494}