blob: 7d22c0bb715884dc0bde1bfdc6613037a978c901 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package installer
giolekva050609f2021-12-29 15:51:40 +04002
giolekva8aa73e82022-07-09 11:34:39 +04003import (
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04004 "archive/tar"
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +04005 "bytes"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04006 "compress/gzip"
giolekva8aa73e82022-07-09 11:34:39 +04007 "embed"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +04008 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04009 "fmt"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040010 htemplate "html/template"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040011 "io"
giolekva8aa73e82022-07-09 11:34:39 +040012 "log"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040013 "net/http"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040014 "strings"
giolekva8aa73e82022-07-09 11:34:39 +040015 "text/template"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040016
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040017 "cuelang.org/go/cue"
18 "cuelang.org/go/cue/cuecontext"
19 cueyaml "cuelang.org/go/encoding/yaml"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040020 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040021 "github.com/go-git/go-billy/v5"
22 "sigs.k8s.io/yaml"
giolekva8aa73e82022-07-09 11:34:39 +040023)
giolekva050609f2021-12-29 15:51:40 +040024
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040025//go:embed values-tmpl
26var valuesTmpls embed.FS
27
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040028const cueBaseConfigImports = `
29import (
30 "list"
31)
32`
33
34// TODO(gio): import
35const cueBaseConfig = `
36readme: string | *""
37
38#Network: {
39 name: string
40 ingressClass: string
41 certificateIssuer: string | *""
42 domain: string
43}
44
45#Image: {
46 registry: string | *"docker.io"
47 repository: string
48 name: string
49 tag: string
50 pullPolicy: string | *"IfNotPresent"
51 imageName: "\(repository)/\(name)"
52 fullName: "\(registry)/\(imageName)"
53 fullNameWithTag: "\(fullName):\(tag)"
54}
55
56#Chart: {
57 chart: string
58 sourceRef: #SourceRef
59}
60
61#SourceRef: {
62 kind: "GitRepository" | "HelmRepository"
63 name: string
64 namespace: string // TODO(gio): default global.id
65}
66
67#Global: {
68 id: string | *""
69 pcloudEnvName: string | *""
70 domain: string | *""
71 privateDomain: string | *""
72 namespacePrefix: string | *""
73 ...
74}
75
76#Release: {
77 namespace: string
78}
79
80global: #Global
81release: #Release
82
83_ingressPrivate: "\(global.id)-ingress-private"
84_ingressPublic: "\(global.pcloudEnvName)-ingress-public"
85_issuerPrivate: "\(global.id)-private"
86_issuerPublic: "\(global.id)-public"
87
88images: {
89 for key, value in images {
90 "\(key)": #Image & value
91 }
92}
93
94charts: {
95 for key, value in charts {
96 "\(key)": #Chart & value
97 }
98}
99
100#ResourceReference: {
101 name: string
102 namespace: string
103}
104
105#Helm: {
106 name: string
107 dependsOn: [...#Helm] | *[]
108 dependsOnExternal: [...#ResourceReference] | *[]
109 ...
110}
111
112helm: {
113 for key, value in helm {
114 "\(key)": #Helm & value & {
115 name: key
116 }
117 }
118}
119
120#HelmRelease: {
121 _name: string
122 _chart: #Chart
123 _values: _
124 _dependencies: [...#Helm] | *[]
125 _externalDependencies: [...#ResourceReference] | *[]
126
127 apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
128 kind: "HelmRelease"
129 metadata: {
130 name: _name
131 namespace: release.namespace
132 }
133 spec: {
134 interval: "1m0s"
135 dependsOn: list.Concat([_externalDependencies, [
136 for d in _dependencies {
137 name: d.name
138 namespace: release.namespace
139 }
140 ]])
141 chart: {
142 spec: _chart
143 }
144 values: _values
145 }
146}
147
148output: {
149 for name, r in helm {
150 "\(name)": #HelmRelease & {
151 _name: name
152 _chart: r.chart
153 _values: r.values
154 _dependencies: r.dependsOn
155 _externalDependencies: r.dependsOnExternal
156 }
157 }
158}
159`
160
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400161type Named interface {
162 Nam() string
163}
164
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400165type appConfig struct {
166 Name string `json:"name"`
167 Version string `json:"version"`
168 Description string `json:"description"`
169 Namespaces []string `json:"namespaces"`
170 Icon htemplate.HTML `json:"icon"`
171}
172
giolekva050609f2021-12-29 15:51:40 +0400173type App struct {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400174 Name string
175 Namespaces []string
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400176 templates []*template.Template
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400177 schema Schema
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400178 Readme *template.Template
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400179 cfg *cue.Value
giolekva050609f2021-12-29 15:51:40 +0400180}
181
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400182func (a App) Schema() Schema {
183 return a.schema
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400184}
185
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400186type Rendered struct {
187 Readme string
188 Resources map[string][]byte
189}
190
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400191func cleanName(s string) string {
192 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
193}
194
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400195func (a App) Render(derived Derived) (Rendered, error) {
196 ret := Rendered{
197 Resources: make(map[string][]byte),
198 }
199 if a.cfg != nil {
200 var buf bytes.Buffer
201 if err := json.NewEncoder(&buf).Encode(derived); err != nil {
202 return Rendered{}, err
203 }
204 ctx := a.cfg.Context()
205 d := ctx.CompileBytes(buf.Bytes())
206 res := a.cfg.Unify(d).Eval()
207 if err := res.Err(); err != nil {
208 return Rendered{}, err
209 }
210 if err := res.Validate(); err != nil {
211 return Rendered{}, err
212 }
213 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
214 if err != nil {
215 return Rendered{}, err
216 }
217 ret.Readme = readme
218 output := res.LookupPath(cue.ParsePath("output"))
219 i, err := output.Fields()
220 if err != nil {
221 return Rendered{}, err
222 }
223 for i.Next() {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400224 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400225 contents, err := cueyaml.Encode(i.Value())
226 if err != nil {
227 return Rendered{}, err
228 }
229 ret.Resources[name] = contents
230 }
231 return ret, nil
232 }
233 var readme bytes.Buffer
234 if err := a.Readme.Execute(&readme, derived); err != nil {
235 return Rendered{}, err
236 }
237 ret.Readme = readme.String()
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400238 for _, t := range a.templates {
239 var buf bytes.Buffer
240 if err := t.Execute(&buf, derived); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400241 return Rendered{}, err
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400242 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400243 ret.Resources[t.Name()] = buf.Bytes()
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400244 }
245 return ret, nil
246}
247
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400248type StoreApp struct {
249 App
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400250 Icon htemplate.HTML
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400251 ShortDescription string
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400252}
253
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400254func (a App) Nam() string {
255 return a.Name
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400256}
257
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400258func (a StoreApp) Nam() string {
259 return a.Name
260}
261
262type AppRepository[A Named] interface {
263 GetAll() ([]A, error)
264 Find(name string) (*A, error)
265}
266
267type InMemoryAppRepository[A Named] struct {
268 apps []A
269}
270
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400271func NewInMemoryAppRepository[A Named](apps []A) InMemoryAppRepository[A] {
272 return InMemoryAppRepository[A]{
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400273 apps,
274 }
275}
276
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400277func (r InMemoryAppRepository[A]) Find(name string) (*A, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400278 for _, a := range r.apps {
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400279 if a.Nam() == name {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400280 return &a, nil
281 }
282 }
283 return nil, fmt.Errorf("Application not found: %s", name)
284}
giolekva8aa73e82022-07-09 11:34:39 +0400285
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400286func (r InMemoryAppRepository[A]) GetAll() ([]A, error) {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400287 return r.apps, nil
288}
289
giolekva8aa73e82022-07-09 11:34:39 +0400290func CreateAllApps() []App {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400291 tmpls, err := template.New("root").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(valuesTmpls, "values-tmpl/*")
giolekva8aa73e82022-07-09 11:34:39 +0400292 if err != nil {
293 log.Fatal(err)
294 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400295 ret := []App{
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400296 CreateAppIngressPrivate(valuesTmpls, tmpls),
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400297 CreateCertificateIssuerPublic(valuesTmpls, tmpls),
298 CreateCertificateIssuerPrivate(valuesTmpls, tmpls),
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400299 CreateAppCoreAuth(valuesTmpls, tmpls),
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400300 CreateAppHeadscale(valuesTmpls, tmpls),
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400301 CreateAppHeadscaleUser(valuesTmpls, tmpls),
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400302 CreateMetallbIPAddressPool(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400303 CreateEnvManager(valuesTmpls, tmpls),
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400304 CreateWelcome(valuesTmpls, tmpls),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400305 CreateAppManager(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400306 CreateIngressPublic(valuesTmpls, tmpls),
307 CreateCertManager(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400308 CreateCSIDriverSMB(valuesTmpls, tmpls),
309 CreateResourceRendererController(valuesTmpls, tmpls),
310 CreateHeadscaleController(valuesTmpls, tmpls),
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400311 CreateDNSZoneManager(valuesTmpls, tmpls),
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400312 CreateFluxcdReconciler(valuesTmpls, tmpls),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400313 CreateAppConfigRepo(valuesTmpls, tmpls),
giolekvaef76a3e2022-01-10 12:22:28 +0400314 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400315 for _, a := range CreateStoreApps() {
316 ret = append(ret, a.App)
317 }
318 return ret
319}
320
321func CreateStoreApps() []StoreApp {
322 tmpls, err := template.New("root").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(valuesTmpls, "values-tmpl/*")
323 if err != nil {
324 log.Fatal(err)
325 }
326 return []StoreApp{
327 CreateAppVaultwarden(valuesTmpls, tmpls),
328 CreateAppMatrix(valuesTmpls, tmpls),
329 CreateAppPihole(valuesTmpls, tmpls),
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400330 CreateAppPenpot(valuesTmpls, tmpls),
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400331 CreateAppMaddy(valuesTmpls, tmpls),
332 CreateAppQBittorrent(valuesTmpls, tmpls),
333 CreateAppJellyfin(valuesTmpls, tmpls),
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400334 CreateAppSoftServe(valuesTmpls, tmpls),
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400335 CreateAppRpuppy(valuesTmpls, tmpls),
336 }
giolekvaef76a3e2022-01-10 12:22:28 +0400337}
338
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400339func readJSONSchemaFromFile(fs embed.FS, f string) (Schema, error) {
340 schema, err := fs.ReadFile(f)
341 if err != nil {
342 return nil, err
343 }
344 ret, err := NewJSONSchema(string(schema))
345 if err != nil {
346 return nil, err
347 }
348 return ret, nil
349}
350
Giorgi Lekveishvili4d2784d2023-06-01 14:27:32 +0400351// TODO(gio): service account needs permission to create/update secret
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400352func CreateAppIngressPrivate(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400353 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/private-network.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400354 if err != nil {
355 panic(err)
356 }
giolekva050609f2021-12-29 15:51:40 +0400357 return App{
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400358 "private-network",
359 []string{"ingress-private"}, // TODO(gio): rename to private network
giolekva050609f2021-12-29 15:51:40 +0400360 []*template.Template{
giolekva050609f2021-12-29 15:51:40 +0400361 tmpls.Lookup("ingress-private.yaml"),
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400362 tmpls.Lookup("tailscale-proxy.yaml"),
giolekva050609f2021-12-29 15:51:40 +0400363 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400364 schema,
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400365 tmpls.Lookup("private-network.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400366 cfg,
giolekva050609f2021-12-29 15:51:40 +0400367 }
368}
369
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400370func CreateCertificateIssuerPrivate(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400371 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/certificate-issuer-private.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400372 if err != nil {
373 panic(err)
374 }
375 return App{
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400376 "certificate-issuer-private",
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400377 []string{"ingress-private"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400378 []*template.Template{
379 tmpls.Lookup("certificate-issuer-private.yaml"),
380 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400381 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400382 tmpls.Lookup("certificate-issuer-private.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400383 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400384 }
385}
386
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400387func CreateCertificateIssuerPublic(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400388 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/certificate-issuer-public.cue")
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400389 if err != nil {
390 panic(err)
391 }
392 return App{
393 "certificate-issuer-public",
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400394 []string{"ingress-private"},
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400395 []*template.Template{
396 tmpls.Lookup("certificate-issuer-public.yaml"),
397 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400398 schema,
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400399 tmpls.Lookup("certificate-issuer-public.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400400 cfg,
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400401 }
402}
403
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400404func CreateAppCoreAuth(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400405 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/core-auth.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400406 if err != nil {
407 panic(err)
408 }
giolekva050609f2021-12-29 15:51:40 +0400409 return App{
410 "core-auth",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400411 []string{"core-auth"},
giolekva050609f2021-12-29 15:51:40 +0400412 []*template.Template{
413 tmpls.Lookup("core-auth-storage.yaml"),
414 tmpls.Lookup("core-auth.yaml"),
415 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400416 schema,
Giorgi Lekveishvili3ca1f3f2023-05-30 14:33:02 +0400417 tmpls.Lookup("core-auth.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400418 cfg,
giolekva050609f2021-12-29 15:51:40 +0400419 }
420}
421
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400422func CreateAppVaultwarden(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400423 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/vaultwarden.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400424 if err != nil {
425 panic(err)
426 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400427 return StoreApp{
428 App: App{
429 "vaultwarden",
430 []string{"app-vaultwarden"},
431 []*template.Template{
432 tmpls.Lookup("vaultwarden.yaml"),
433 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400434 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400435 tmpls.Lookup("vaultwarden.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400436 cfg,
giolekva050609f2021-12-29 15:51:40 +0400437 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400438 Icon: `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M35.38 25.63V9.37H24v28.87a34.93 34.93 0 0 0 5.41-3.48q6-4.66 6-9.14Zm4.87-19.5v19.5A11.58 11.58 0 0 1 39.4 30a16.22 16.22 0 0 1-2.11 3.81a23.52 23.52 0 0 1-3 3.24a34.87 34.87 0 0 1-3.22 2.62c-1 .69-2 1.35-3.07 2s-1.82 1-2.27 1.26l-1.08.51a1.53 1.53 0 0 1-1.32 0l-1.08-.51c-.45-.22-1.21-.64-2.27-1.26s-2.09-1.27-3.07-2A34.87 34.87 0 0 1 13.7 37a23.52 23.52 0 0 1-3-3.24A16.22 16.22 0 0 1 8.6 30a11.58 11.58 0 0 1-.85-4.32V6.13A1.64 1.64 0 0 1 9.38 4.5h29.24a1.64 1.64 0 0 1 1.63 1.63Z"/></svg>`,
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400439 ShortDescription: "Alternative implementation of the Bitwarden server API written in Rust and compatible with upstream Bitwarden clients, perfect for self-hosted deployment where running the official resource-heavy service might not be ideal.",
giolekva050609f2021-12-29 15:51:40 +0400440 }
441}
442
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400443func CreateAppMatrix(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400444 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/matrix.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400445 if err != nil {
446 panic(err)
447 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400448 return StoreApp{
449 App{
450 "matrix",
451 []string{"app-matrix"},
452 []*template.Template{
453 tmpls.Lookup("matrix-storage.yaml"),
454 tmpls.Lookup("matrix.yaml"),
455 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400456 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400457 nil,
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400458 cfg,
giolekva050609f2021-12-29 15:51:40 +0400459 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400460 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24"><path fill="currentColor" d="M.632.55v22.9H2.28V24H0V0h2.28v.55zm7.043 7.26v1.157h.033a3.312 3.312 0 0 1 1.117-1.024c.433-.245.936-.365 1.5-.365c.54 0 1.033.107 1.481.314c.448.208.785.582 1.02 1.108c.254-.374.6-.706 1.034-.992c.434-.287.95-.43 1.546-.43c.453 0 .872.056 1.26.167c.388.11.716.286.993.53c.276.245.489.559.646.951c.152.392.23.863.23 1.417v5.728h-2.349V11.52c0-.286-.01-.559-.032-.812a1.755 1.755 0 0 0-.18-.66a1.106 1.106 0 0 0-.438-.448c-.194-.11-.457-.166-.785-.166c-.332 0-.6.064-.803.189a1.38 1.38 0 0 0-.48.499a1.946 1.946 0 0 0-.231.696a5.56 5.56 0 0 0-.06.785v4.768h-2.35v-4.8c0-.254-.004-.503-.018-.752a2.074 2.074 0 0 0-.143-.688a1.052 1.052 0 0 0-.415-.503c-.194-.125-.476-.19-.854-.19c-.111 0-.259.024-.439.074c-.18.051-.36.143-.53.282a1.637 1.637 0 0 0-.439.595c-.12.259-.18.6-.18 1.02v4.966H5.46V7.81zm15.693 15.64V.55H21.72V0H24v24h-2.28v-.55z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400461 "An open network for secure, decentralised communication",
giolekva050609f2021-12-29 15:51:40 +0400462 }
463}
464
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400465func CreateAppPihole(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400466 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/pihole.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400467 if err != nil {
468 panic(err)
469 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400470 return StoreApp{
471 App{
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400472 "pihnole",
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400473 []string{"app-pihole"},
474 []*template.Template{
475 tmpls.Lookup("pihole.yaml"),
476 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400477 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400478 tmpls.Lookup("pihole.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400479 cfg,
giolekva050609f2021-12-29 15:51:40 +0400480 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400481 // "simple-icons:pihole",
482 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24"><path fill="currentColor" d="M4.344 0c.238 4.792 3.256 7.056 6.252 7.376c.165-1.692-4.319-5.6-4.319-5.6c-.008-.011.009-.025.019-.014c0 0 4.648 4.01 5.423 5.645c2.762-.15 5.196-1.947 5-4.912c0 0-4.12-.613-5 4.618C11.48 2.753 8.993 0 4.344 0zM12 7.682v.002a3.68 3.68 0 0 0-2.591 1.077L4.94 13.227a3.683 3.683 0 0 0-.86 1.356a3.31 3.31 0 0 0-.237 1.255A3.681 3.681 0 0 0 4.92 18.45l4.464 4.466a3.69 3.69 0 0 0 2.251 1.06l.002.001c.093.01.187.015.28.017l-.1-.008c.06.003.117.009.177.009l-.077-.001L12 24l-.004-.005a3.68 3.68 0 0 0 2.61-1.077l4.469-4.465a3.683 3.683 0 0 0 1.006-1.888l.012-.063a3.682 3.682 0 0 0 .057-.541l.003-.061c0-.017.003-.05.004-.06h-.002a3.683 3.683 0 0 0-1.077-2.607l-4.466-4.468a3.694 3.694 0 0 0-1.564-.927l-.07-.02a3.43 3.43 0 0 0-.946-.133L12 7.682zm3.165 3.357c.023 1.748-1.33 3.078-1.33 4.806c.164 2.227 1.733 3.207 3.266 3.146c-.035.003-.068.007-.104.009c-1.847.135-3.209-1.326-5.002-1.326c-2.23.164-3.21 1.736-3.147 3.27l-.008-.104c-.133-1.847 1.328-3.21 1.328-5.002c-.173-2.32-1.867-3.284-3.46-3.132c.1-.011.203-.021.31-.027c1.847-.133 3.209 1.328 5.002 1.328c2.082-.155 3.074-1.536 3.145-2.968zM4.344 0c.238 4.792 3.256 7.056 6.252 7.376c.165-1.692-4.319-5.6-4.319-5.6c-.008-.011.009-.025.019-.014c0 0 4.648 4.01 5.423 5.645c2.762-.15 5.196-1.947 5-4.912c0 0-4.12-.613-5 4.618C11.48 2.753 8.993 0 4.344 0zM12 7.682v.002a3.68 3.68 0 0 0-2.591 1.077L4.94 13.227a3.683 3.683 0 0 0-.86 1.356a3.31 3.31 0 0 0-.237 1.255A3.681 3.681 0 0 0 4.92 18.45l4.464 4.466a3.69 3.69 0 0 0 2.251 1.06l.002.001c.093.01.187.015.28.017l-.1-.008c.06.003.117.009.177.009l-.077-.001L12 24l-.004-.005a3.68 3.68 0 0 0 2.61-1.077l4.469-4.465a3.683 3.683 0 0 0 1.006-1.888l.012-.063a3.682 3.682 0 0 0 .057-.541l.003-.061c0-.017.003-.05.004-.06h-.002a3.683 3.683 0 0 0-1.077-2.607l-4.466-4.468a3.694 3.694 0 0 0-1.564-.927l-.07-.02a3.43 3.43 0 0 0-.946-.133L12 7.682zm3.165 3.357c.023 1.748-1.33 3.078-1.33 4.806c.164 2.227 1.733 3.207 3.266 3.146c-.035.003-.068.007-.104.009c-1.847.135-3.209-1.326-5.002-1.326c-2.23.164-3.21 1.736-3.147 3.27l-.008-.104c-.133-1.847 1.328-3.21 1.328-5.002c-.173-2.32-1.867-3.284-3.46-3.132c.1-.011.203-.021.31-.027c1.847-.133 3.209 1.328 5.002 1.328c2.082-.155 3.074-1.536 3.145-2.968z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400483 "Pi-hole is a Linux network-level advertisement and Internet tracker blocking application which acts as a DNS sinkhole and optionally a DHCP server, intended for use on a private network.",
giolekva050609f2021-12-29 15:51:40 +0400484 }
485}
486
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400487func CreateAppPenpot(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400488 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/penpot.cue")
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400489 if err != nil {
490 panic(err)
491 }
492 return StoreApp{
493 App{
494 "penpot",
495 []string{"app-penpot"},
496 []*template.Template{
497 tmpls.Lookup("penpot.yaml"),
498 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400499 schema,
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400500 tmpls.Lookup("penpot.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400501 cfg,
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400502 },
503 // "simple-icons:pihole",
504 `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M7.654 0L5.13 3.554v2.01L2.934 6.608l-.02-.009v13.109l8.563 4.045L12 24l.523-.247l8.563-4.045V6.6l-.017.008l-2.196-1.045V3.555l-.077-.108L16.349.001l-2.524 3.554v.004L11.989.973l-1.823 2.566l-.065-.091zm.447 2.065l.976 1.374H6.232l.964-1.358zm8.694 0l.976 1.374h-2.845l.965-1.358zm-4.36.971l.976 1.375h-2.845l.965-1.359zM5.962 4.132h1.35v4.544l-1.35-.638Zm2.042 0h1.343v5.506l-1.343-.635zm6.652 0h1.35V9l-1.35.637zm2.042 0h1.343v3.905l-1.343.634zm-6.402.972h1.35v5.62l-1.35-.638zm2.042 0h1.343v4.993l-1.343.634zm6.534 1.493l1.188.486l-1.188.561zM5.13 6.6v1.047l-1.187-.561ZM3.96 8.251l7.517 3.55v10.795l-7.516-3.55zm16.08 0v10.794l-7.517 3.55V11.802z"/></svg>`,
505 "Penpot is the first Open Source design and prototyping platform meant for cross-domain teams. Non dependent on operating systems, Penpot is web based and works with open standards (SVG). Penpot invites designers all over the world to fall in love with open source while getting developers excited about the design process in return.",
506 }
507}
508
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400509func CreateAppMaddy(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400510 schema, err := readJSONSchemaFromFile(fs, "values-tmpl/maddy.jsonschema")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400511 if err != nil {
512 panic(err)
513 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400514 return StoreApp{
515 App{
516 "maddy",
517 []string{"app-maddy"},
518 []*template.Template{
519 tmpls.Lookup("maddy.yaml"),
520 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400521 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400522 nil,
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400523 nil,
giolekva050609f2021-12-29 15:51:40 +0400524 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400525 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M9.5 13c13.687 13.574 14.825 13.09 29 0"/><rect width="37" height="31" x="5.5" y="8.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" rx="2"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400526 "SMPT/IMAP server to communicate via email.",
giolekva050609f2021-12-29 15:51:40 +0400527 }
528}
giolekvaef76a3e2022-01-10 12:22:28 +0400529
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400530func CreateAppQBittorrent(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400531 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/qbittorrent.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400532 if err != nil {
533 panic(err)
534 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400535 return StoreApp{
536 App{
537 "qbittorrent",
538 []string{"app-qbittorrent"},
539 []*template.Template{
540 tmpls.Lookup("qbittorrent.yaml"),
541 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400542 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400543 tmpls.Lookup("qbittorrent.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400544 cfg,
giolekvaef76a3e2022-01-10 12:22:28 +0400545 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400546 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><circle cx="24" cy="24" r="21.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M26.651 22.364a5.034 5.034 0 0 1 5.035-5.035h0a5.034 5.034 0 0 1 5.034 5.035v3.272a5.034 5.034 0 0 1-5.034 5.035h0a5.034 5.034 0 0 1-5.035-5.035m0 5.035V10.533m-5.302 15.103a5.034 5.034 0 0 1-5.035 5.035h0a5.034 5.034 0 0 1-5.034-5.035v-3.272a5.034 5.034 0 0 1 5.034-5.035h0a5.034 5.034 0 0 1 5.035 5.035m0-5.035v20.138"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400547 "qBittorrent is a cross-platform free and open-source BitTorrent client written in native C++. It relies on Boost, Qt 6 toolkit and the libtorrent-rasterbar library, with an optional search engine written in Python.",
giolekvaef76a3e2022-01-10 12:22:28 +0400548 }
549}
550
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400551func CreateAppJellyfin(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400552 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/jellyfin.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400553 if err != nil {
554 panic(err)
555 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400556 return StoreApp{
557 App{
558 "jellyfin",
559 []string{"app-jellyfin"},
560 []*template.Template{
561 tmpls.Lookup("jellyfin.yaml"),
562 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400563 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400564 nil,
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400565 cfg,
giolekvaef76a3e2022-01-10 12:22:28 +0400566 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400567 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M24 20c-1.62 0-6.85 9.48-6.06 11.08s11.33 1.59 12.12 0S25.63 20 24 20Z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M24 5.5c-4.89 0-20.66 28.58-18.25 33.4s34.13 4.77 36.51 0S28.9 5.5 24 5.5Zm12 29.21c-1.56 3.13-22.35 3.17-23.93 0S20.8 12.83 24 12.83s13.52 18.76 12 21.88Z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400568 "Jellyfin is a free and open-source media server and suite of multimedia applications designed to organize, manage, and share digital media files to networked devices.",
giolekvaef76a3e2022-01-10 12:22:28 +0400569 }
570}
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400571
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400572func processCueConfig(contents string) (*cue.Value, Schema, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400573 ctx := cuecontext.New()
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400574 cfg := ctx.CompileString(cueBaseConfigImports + contents + cueBaseConfig)
575 if err := cfg.Err(); err != nil {
576 return nil, nil, err
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400577 }
578 if err := cfg.Validate(); err != nil {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400579 return nil, nil, err
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400580 }
581 schema, err := NewCueSchema(cfg.LookupPath(cue.ParsePath("input")))
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400582 if err != nil {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400583 return nil, nil, err
584 }
585 return &cfg, schema, nil
586}
587
588func readCueConfigFromFile(fs embed.FS, f string) (*cue.Value, Schema, error) {
589 contents, err := fs.ReadFile(f)
590 if err != nil {
591 return nil, nil, err
592 }
593 return processCueConfig(string(contents))
594}
595
596func CreateAppRpuppy(fs embed.FS, tmpls *template.Template) StoreApp {
597 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/rpuppy.cue")
598 if err != nil {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400599 panic(err)
600 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400601 return StoreApp{
602 App{
603 "rpuppy",
604 []string{"app-rpuppy"},
605 []*template.Template{
606 tmpls.Lookup("rpuppy.yaml"),
607 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400608 schema,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400609 tmpls.Lookup("rpuppy.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400610 cfg,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400611 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400612 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 256 256"><path fill="currentColor" d="M100 140a8 8 0 1 1-8-8a8 8 0 0 1 8 8Zm64 8a8 8 0 1 0-8-8a8 8 0 0 0 8 8Zm64.94-9.11a12.12 12.12 0 0 1-5 1.11a11.83 11.83 0 0 1-9.35-4.62l-2.59-3.29V184a36 36 0 0 1-36 36H80a36 36 0 0 1-36-36v-51.91l-2.53 3.27A11.88 11.88 0 0 1 32.1 140a12.08 12.08 0 0 1-5-1.11a11.82 11.82 0 0 1-6.84-13.14l16.42-88a12 12 0 0 1 14.7-9.43h.16L104.58 44h46.84l53.08-15.6h.16a12 12 0 0 1 14.7 9.43l16.42 88a11.81 11.81 0 0 1-6.84 13.06ZM97.25 50.18L49.34 36.1a4.18 4.18 0 0 0-.92-.1a4 4 0 0 0-3.92 3.26l-16.42 88a4 4 0 0 0 7.08 3.22ZM204 121.75L150 52h-44l-54 69.75V184a28 28 0 0 0 28 28h44v-18.34l-14.83-14.83a4 4 0 0 1 5.66-5.66L128 186.34l13.17-13.17a4 4 0 0 1 5.66 5.66L132 193.66V212h44a28 28 0 0 0 28-28Zm23.92 5.48l-16.42-88a4 4 0 0 0-4.84-3.16l-47.91 14.11l62.11 80.28a4 4 0 0 0 7.06-3.23Z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400613 "Delights users with randomly generate puppy pictures. Can be configured to be reachable only from private network or publicly.",
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400614 }
615}
616
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400617func CreateAppConfigRepo(fs embed.FS, tmpls *template.Template) App {
618 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/config-repo.cue")
619 if err != nil {
620 panic(err)
621 }
622 return App{
623 "config-repo",
624 []string{"config-repo"},
625 []*template.Template{},
626 schema,
627 nil,
628 cfg,
629 }
630}
631
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400632func CreateAppSoftServe(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400633 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/soft-serve.cue")
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400634 if err != nil {
635 panic(err)
636 }
637 return StoreApp{
638 App{
639 "soft-serve",
640 []string{"app-soft-serve"},
641 []*template.Template{
642 tmpls.Lookup("soft-serve.yaml"),
643 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400644 schema,
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400645 tmpls.Lookup("soft-serve.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400646 cfg,
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400647 },
648 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="4"><path stroke-linejoin="round" d="M15.34 22.5L21 37l3 6l3-6l5.66-14.5"/><path d="M19 32h10"/><path stroke-linejoin="round" d="M24 3c-6 0-8 6-8 6s-6 2-6 7s5 7 5 7s3.5-2 9-2s9 2 9 2s5-2 5-7s-6-7-6-7s-2-6-8-6Z"/></g></svg>`,
649 "A tasty, self-hostable Git server for the command line. 🍦",
650 }
651}
652
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400653func CreateAppHeadscale(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400654 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/headscale.cue")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400655 if err != nil {
656 panic(err)
657 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400658 return App{
659 "headscale",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400660 []string{"app-headscale"},
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400661 []*template.Template{
662 tmpls.Lookup("headscale.yaml"),
663 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400664 schema,
Giorgi Lekveishvili3a907052023-05-30 13:33:32 +0400665 tmpls.Lookup("headscale.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400666 cfg,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400667 }
668}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400669
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400670func CreateAppHeadscaleUser(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400671 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/headscale-user.cue")
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400672 if err != nil {
673 panic(err)
674 }
675 return App{
676 "headscale-user",
677 []string{"app-headscale"},
678 []*template.Template{
679 tmpls.Lookup("headscale-user.yaml"),
680 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400681 schema,
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400682 tmpls.Lookup("headscale-user.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400683 cfg,
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400684 }
685}
686
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400687func CreateMetallbIPAddressPool(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400688 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/metallb-ipaddresspool.cue")
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400689 if err != nil {
690 panic(err)
691 }
692 return App{
693 "metallb-ipaddresspool",
694 []string{"metallb-ipaddresspool"},
695 []*template.Template{
696 tmpls.Lookup("metallb-ipaddresspool.yaml"),
697 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400698 schema,
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400699 tmpls.Lookup("metallb-ipaddresspool.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400700 cfg,
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400701 }
702}
703
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400704func CreateEnvManager(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400705 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/env-manager.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400706 if err != nil {
707 panic(err)
708 }
709 return App{
710 "env-manager",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400711 []string{"env-manager"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400712 []*template.Template{
713 tmpls.Lookup("env-manager.yaml"),
714 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400715 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400716 tmpls.Lookup("env-manager.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400717 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400718 }
719}
720
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400721func CreateWelcome(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400722 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/welcome.cue")
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400723 if err != nil {
724 panic(err)
725 }
726 return App{
727 "welcome",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400728 []string{"app-welcome"},
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400729 []*template.Template{
730 tmpls.Lookup("welcome.yaml"),
731 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400732 schema,
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400733 tmpls.Lookup("welcome.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400734 cfg,
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400735 }
736}
737
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400738func CreateAppManager(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400739 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/appmanager.cue")
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400740 if err != nil {
741 panic(err)
742 }
743 return App{
744 "app-manager",
745 []string{"core-appmanager"},
746 []*template.Template{
747 tmpls.Lookup("appmanager.yaml"),
748 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400749 schema,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400750 tmpls.Lookup("appmanager.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400751 cfg,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400752 }
753}
754
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400755func CreateIngressPublic(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400756 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/ingress-public.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400757 if err != nil {
758 panic(err)
759 }
760 return App{
761 "ingress-public",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400762 []string{"ingress-public"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400763 []*template.Template{
764 tmpls.Lookup("ingress-public.yaml"),
765 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400766 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400767 tmpls.Lookup("ingress-public.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400768 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400769 }
770}
771
772func CreateCertManager(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400773 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/cert-manager.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400774 if err != nil {
775 panic(err)
776 }
777 return App{
778 "cert-manager",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400779 []string{"cert-manager"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400780 []*template.Template{
781 tmpls.Lookup("cert-manager.yaml"),
782 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400783 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400784 tmpls.Lookup("cert-manager.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400785 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400786 }
787}
788
789func CreateCSIDriverSMB(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400790 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/csi-driver-smb.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400791 if err != nil {
792 panic(err)
793 }
794 return App{
795 "csi-driver-smb",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400796 []string{"csi-driver-smb"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400797 []*template.Template{
798 tmpls.Lookup("csi-driver-smb.yaml"),
799 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400800 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400801 tmpls.Lookup("csi-driver-smb.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400802 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400803 }
804}
805
806func CreateResourceRendererController(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400807 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/resource-renderer-controller.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400808 if err != nil {
809 panic(err)
810 }
811 return App{
812 "resource-renderer-controller",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400813 []string{"rr-controller"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400814 []*template.Template{
815 tmpls.Lookup("resource-renderer-controller.yaml"),
816 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400817 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400818 tmpls.Lookup("resource-renderer-controller.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400819 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400820 }
821}
822
823func CreateHeadscaleController(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400824 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/headscale-controller.cue")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400825 if err != nil {
826 panic(err)
827 }
828 return App{
829 "headscale-controller",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400830 []string{"headscale-controller"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400831 []*template.Template{
832 tmpls.Lookup("headscale-controller.yaml"),
833 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400834 schema,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400835 tmpls.Lookup("headscale-controller.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400836 cfg,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400837 }
838}
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400839
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400840func CreateDNSZoneManager(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400841 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/dns-zone-manager.cue")
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400842 if err != nil {
843 panic(err)
844 }
845 return App{
846 "dns-zone-manager",
847 []string{"dns-zone-manager"},
848 []*template.Template{
849 tmpls.Lookup("dns-zone-storage.yaml"),
850 tmpls.Lookup("coredns.yaml"),
851 tmpls.Lookup("dns-zone-controller.yaml"),
852 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400853 schema,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400854 tmpls.Lookup("dns-zone-controller.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400855 cfg,
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400856 }
857}
858
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400859func CreateFluxcdReconciler(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400860 cfg, schema, err := readCueConfigFromFile(fs, "values-tmpl/fluxcd-reconciler.cue")
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400861 if err != nil {
862 panic(err)
863 }
864 return App{
865 "fluxcd-reconciler",
866 []string{"fluxcd-reconciler"},
867 []*template.Template{
868 tmpls.Lookup("fluxcd-reconciler.yaml"),
869 },
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400870 schema,
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400871 tmpls.Lookup("fluxcd-reconciler.md"),
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400872 cfg,
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400873 }
874}
875
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400876type httpAppRepository struct {
877 apps []StoreApp
878}
879
880type appVersion struct {
881 Version string `json:"version"`
882 Urls []string `json:"urls"`
883}
884
885type allAppsResp struct {
886 ApiVersion string `json:"apiVersion"`
887 Entries map[string][]appVersion `json:"entries"`
888}
889
890func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
891 resp, err := http.Get(addr)
892 if err != nil {
893 return err
894 }
895 b, err := io.ReadAll(resp.Body)
896 if err != nil {
897 return err
898 }
899 var apps allAppsResp
900 if err := yaml.Unmarshal(b, &apps); err != nil {
901 return err
902 }
903 for name, conf := range apps.Entries {
904 for _, version := range conf {
905 resp, err := http.Get(version.Urls[0])
906 if err != nil {
907 return err
908 }
909 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
910 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
911 return err
912 }
913 sub, err := fs.Chroot(nameVersion)
914 if err != nil {
915 return err
916 }
917 if err := extractApp(resp.Body, sub); err != nil {
918 return err
919 }
920 }
921 }
922 return nil
923}
924
925func extractApp(archive io.Reader, fs billy.Filesystem) error {
926 uncompressed, err := gzip.NewReader(archive)
927 if err != nil {
928 return err
929 }
930 tarReader := tar.NewReader(uncompressed)
931 for true {
932 header, err := tarReader.Next()
933 if err == io.EOF {
934 break
935 }
936 if err != nil {
937 return err
938 }
939 switch header.Typeflag {
940 case tar.TypeDir:
941 if err := fs.MkdirAll(header.Name, 0755); err != nil {
942 return err
943 }
944 case tar.TypeReg:
945 out, err := fs.Create(header.Name)
946 if err != nil {
947 return err
948 }
949 defer out.Close()
950 if _, err := io.Copy(out, tarReader); err != nil {
951 return err
952 }
953 default:
954 return fmt.Errorf("Uknown type: %s", header.Name)
955 }
956 }
957 return nil
958}
959
960type fsAppRepository struct {
961 InMemoryAppRepository[StoreApp]
962 fs billy.Filesystem
963}
964
965func NewFSAppRepository(fs billy.Filesystem) (AppRepository[StoreApp], error) {
966 all, err := fs.ReadDir(".")
967 if err != nil {
968 return nil, err
969 }
970 apps := make([]StoreApp, 0)
971 for _, e := range all {
972 if !e.IsDir() {
973 continue
974 }
975 appFS, err := fs.Chroot(e.Name())
976 if err != nil {
977 return nil, err
978 }
979 app, err := loadApp(appFS)
980 if err != nil {
981 log.Printf("Ignoring directory %s: %s", e.Name(), err)
982 continue
983 }
984 apps = append(apps, app)
985 }
986 return &fsAppRepository{
987 NewInMemoryAppRepository[StoreApp](apps),
988 fs,
989 }, nil
990}
991
992func loadApp(fs billy.Filesystem) (StoreApp, error) {
993 cfg, err := fs.Open("Chart.yaml")
994 if err != nil {
995 return StoreApp{}, err
996 }
997 defer cfg.Close()
998 b, err := io.ReadAll(cfg)
999 if err != nil {
1000 return StoreApp{}, err
1001 }
1002 var appCfg appConfig
1003 if err := yaml.Unmarshal(b, &appCfg); err != nil {
1004 return StoreApp{}, err
1005 }
1006 rb, err := fs.Open("README.md")
1007 if err != nil {
1008 return StoreApp{}, err
1009 }
1010 defer rb.Close()
1011 readme, err := io.ReadAll(rb)
1012 if err != nil {
1013 return StoreApp{}, err
1014 }
1015 readmeTmpl, err := template.New("README.md").Parse(string(readme))
1016 if err != nil {
1017 return StoreApp{}, err
1018 }
1019 sb, err := fs.Open("schema.json")
1020 if err != nil {
1021 return StoreApp{}, err
1022 }
1023 defer sb.Close()
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04001024 scm, err := io.ReadAll(sb)
1025 if err != nil {
1026 return StoreApp{}, err
1027 }
1028 schema, err := NewJSONSchema(string(scm))
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04001029 if err != nil {
1030 return StoreApp{}, err
1031 }
1032 tFiles, err := fs.ReadDir("templates")
1033 if err != nil {
1034 return StoreApp{}, err
1035 }
1036 tmpls := make([]*template.Template, 0)
1037 for _, t := range tFiles {
1038 if !strings.HasSuffix(t.Name(), ".yaml") {
1039 continue
1040 }
1041 inp, err := fs.Open(fs.Join("templates", t.Name()))
1042 if err != nil {
1043 return StoreApp{}, err
1044 }
1045 b, err := io.ReadAll(inp)
1046 if err != nil {
1047 return StoreApp{}, err
1048 }
1049 tmpl, err := template.New(t.Name()).Parse(string(b))
1050 if err != nil {
1051 return StoreApp{}, err
1052 }
1053 tmpls = append(tmpls, tmpl)
1054 }
1055 return StoreApp{
1056 App: App{
1057 Name: appCfg.Name,
1058 Readme: readmeTmpl,
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04001059 schema: schema,
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04001060 Namespaces: appCfg.Namespaces,
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +04001061 templates: tmpls,
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04001062 },
1063 ShortDescription: appCfg.Description,
1064 Icon: appCfg.Icon,
1065 }, nil
1066}