blob: 1befd7cc04d0af129986793f3d54fd6f815f10d8 [file] [log] [blame]
gio3cdee592024-04-17 10:15:56 +04001package installer
2
3import (
4 "archive/tar"
5 "bytes"
6 "compress/gzip"
7 "embed"
8 "fmt"
9 "io"
10 "log"
11 "net/http"
12
gio3cdee592024-04-17 10:15:56 +040013 "github.com/go-git/go-billy/v5"
14 "sigs.k8s.io/yaml"
15)
16
17//go:embed values-tmpl
18var valuesTmpls embed.FS
19
gio44f621b2024-04-29 09:44:38 +040020var storeEnvAppConfigs = []string{
gio0eaf2712024-04-14 13:08:46 +040021 "values-tmpl/dodo-app.cue",
gio932849f2024-08-02 09:01:48 +040022 "values-tmpl/coder.cue",
gio3cdee592024-04-17 10:15:56 +040023 "values-tmpl/url-shortener.cue",
gio44f621b2024-04-29 09:44:38 +040024 "values-tmpl/matrix.cue",
25 "values-tmpl/vaultwarden.cue",
gio18d5c682024-05-02 10:30:57 +040026 // "values-tmpl/open-project.cue",
gio3cdee592024-04-17 10:15:56 +040027 "values-tmpl/gerrit.cue",
28 "values-tmpl/jenkins.cue",
29 "values-tmpl/zot.cue",
gio18d5c682024-05-02 10:30:57 +040030 // "values-tmpl/penpot.cue",
gio44f621b2024-04-29 09:44:38 +040031 "values-tmpl/soft-serve.cue",
32 "values-tmpl/pihole.cue",
33 // "values-tmpl/maddy.cue",
gio18d5c682024-05-02 10:30:57 +040034 // "values-tmpl/qbittorrent.cue",
35 // "values-tmpl/jellyfin.cue",
gio44f621b2024-04-29 09:44:38 +040036 "values-tmpl/rpuppy.cue",
giocb34ad22024-07-11 08:01:13 +040037 "values-tmpl/certificate-issuer-custom.cue",
gio44f621b2024-04-29 09:44:38 +040038}
39
40var envAppConfigs = []string{
gio266c04f2024-07-03 14:18:45 +040041 "values-tmpl/dodo-app-instance.cue",
gio94904702024-07-26 16:58:34 +040042 "values-tmpl/dodo-app-instance-status.cue",
gio3cdee592024-04-17 10:15:56 +040043 "values-tmpl/certificate-issuer-private.cue",
44 "values-tmpl/certificate-issuer-public.cue",
45 "values-tmpl/appmanager.cue",
46 "values-tmpl/core-auth.cue",
47 "values-tmpl/headscale-user.cue",
48 "values-tmpl/metallb-ipaddresspool.cue",
49 "values-tmpl/private-network.cue",
50 "values-tmpl/welcome.cue",
51 "values-tmpl/memberships.cue",
52 "values-tmpl/headscale.cue",
Davit Tabidze56f86a42024-04-09 19:15:25 +040053 "values-tmpl/launcher.cue",
gioe72b54f2024-04-22 10:44:41 +040054 "values-tmpl/env-dns.cue",
gio09a3e5b2024-04-26 14:11:06 +040055 "values-tmpl/launcher.cue",
gio3cdee592024-04-17 10:15:56 +040056}
57
58var infraAppConfigs = []string{
59 "values-tmpl/cert-manager.cue",
60 "values-tmpl/config-repo.cue",
61 "values-tmpl/csi-driver-smb.cue",
gioe72b54f2024-04-22 10:44:41 +040062 "values-tmpl/dns-gateway.cue",
gio3cdee592024-04-17 10:15:56 +040063 "values-tmpl/env-manager.cue",
64 "values-tmpl/fluxcd-reconciler.cue",
65 "values-tmpl/headscale-controller.cue",
66 "values-tmpl/ingress-public.cue",
67 "values-tmpl/resource-renderer-controller.cue",
68 "values-tmpl/hydra-maester.cue",
69}
70
71type AppRepository interface {
72 GetAll() ([]App, error)
73 Find(name string) (App, error)
74}
75
76type InMemoryAppRepository struct {
77 apps []App
78}
79
80func NewInMemoryAppRepository(apps []App) InMemoryAppRepository {
81 return InMemoryAppRepository{apps}
82}
83
84func (r InMemoryAppRepository) Find(name string) (App, error) {
85 for _, a := range r.apps {
gio44f621b2024-04-29 09:44:38 +040086 if a.Slug() == name {
gio3cdee592024-04-17 10:15:56 +040087 return a, nil
88 }
89 }
90 return nil, fmt.Errorf("Application not found: %s", name)
91}
92
93func (r InMemoryAppRepository) GetAll() ([]App, error) {
94 return r.apps, nil
95}
96
97func CreateAllApps() []App {
98 return append(
99 createInfraApps(),
gio44f621b2024-04-29 09:44:38 +0400100 append(
101 CreateEnvApps(storeEnvAppConfigs),
102 CreateEnvApps(envAppConfigs)...,
103 )...,
gio3cdee592024-04-17 10:15:56 +0400104 )
105}
106
107func CreateStoreApps() []App {
gio44f621b2024-04-29 09:44:38 +0400108 return CreateEnvApps(storeEnvAppConfigs)
109}
110
111func CreateEnvApps(configs []string) []App {
gio3cdee592024-04-17 10:15:56 +0400112 ret := make([]App, 0)
gio44f621b2024-04-29 09:44:38 +0400113 for _, cfgFile := range configs {
gio308105e2024-04-19 13:12:13 +0400114 contents, err := valuesTmpls.ReadFile(cfgFile)
gio3cdee592024-04-17 10:15:56 +0400115 if err != nil {
116 panic(err)
117 }
gio308105e2024-04-19 13:12:13 +0400118 if app, err := NewCueEnvApp(CueAppData{
gioe72b54f2024-04-22 10:44:41 +0400119 "base.cue": []byte(cueBaseConfig),
120 "global.cue": []byte(cueEnvAppGlobal),
121 "app.cue": contents,
gio308105e2024-04-19 13:12:13 +0400122 }); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400123 fmt.Println(cfgFile)
gio3cdee592024-04-17 10:15:56 +0400124 panic(err)
125 } else {
126 ret = append(ret, app)
127 }
128 }
129 return ret
130}
131
132func createInfraApps() []App {
133 ret := make([]App, 0)
134 for _, cfgFile := range infraAppConfigs {
gio308105e2024-04-19 13:12:13 +0400135 contents, err := valuesTmpls.ReadFile(cfgFile)
gio3cdee592024-04-17 10:15:56 +0400136 if err != nil {
137 panic(err)
138 }
gio308105e2024-04-19 13:12:13 +0400139 if app, err := NewCueInfraApp(CueAppData{
gioe72b54f2024-04-22 10:44:41 +0400140 "base.cue": []byte(cueBaseConfig),
141 "global.cue": []byte(cueInfraAppGlobal),
142 "app.cue": contents,
gio308105e2024-04-19 13:12:13 +0400143 }); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400144 fmt.Println(cfgFile)
gio3cdee592024-04-17 10:15:56 +0400145 panic(err)
146 } else {
147 ret = append(ret, app)
148 }
149 }
150 return ret
151}
152
153type httpAppRepository struct {
154 apps []App
155}
156
157type appVersion struct {
158 Version string `json:"version"`
159 Urls []string `json:"urls"`
160}
161
162type allAppsResp struct {
163 ApiVersion string `json:"apiVersion"`
164 Entries map[string][]appVersion `json:"entries"`
165}
166
167func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
168 resp, err := http.Get(addr)
169 if err != nil {
170 return err
171 }
172 b, err := io.ReadAll(resp.Body)
173 if err != nil {
174 return err
175 }
176 var apps allAppsResp
177 if err := yaml.Unmarshal(b, &apps); err != nil {
178 return err
179 }
180 for name, conf := range apps.Entries {
181 for _, version := range conf {
182 resp, err := http.Get(version.Urls[0])
183 if err != nil {
184 return err
185 }
186 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
187 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
188 return err
189 }
190 sub, err := fs.Chroot(nameVersion)
191 if err != nil {
192 return err
193 }
194 if err := extractApp(resp.Body, sub); err != nil {
195 return err
196 }
197 }
198 }
199 return nil
200}
201
202func extractApp(archive io.Reader, fs billy.Filesystem) error {
203 uncompressed, err := gzip.NewReader(archive)
204 if err != nil {
205 return err
206 }
207 tarReader := tar.NewReader(uncompressed)
208 for true {
209 header, err := tarReader.Next()
210 if err == io.EOF {
211 break
212 }
213 if err != nil {
214 return err
215 }
216 switch header.Typeflag {
217 case tar.TypeDir:
218 if err := fs.MkdirAll(header.Name, 0755); err != nil {
219 return err
220 }
221 case tar.TypeReg:
222 out, err := fs.Create(header.Name)
223 if err != nil {
224 return err
225 }
226 defer out.Close()
227 if _, err := io.Copy(out, tarReader); err != nil {
228 return err
229 }
230 default:
231 return fmt.Errorf("Uknown type: %s", header.Name)
232 }
233 }
234 return nil
235}
236
237type fsAppRepository struct {
238 InMemoryAppRepository
239 fs billy.Filesystem
240}
241
242func NewFSAppRepository(fs billy.Filesystem) (AppRepository, error) {
243 all, err := fs.ReadDir(".")
244 if err != nil {
245 return nil, err
246 }
247 apps := make([]App, 0)
248 for _, e := range all {
249 if !e.IsDir() {
250 continue
251 }
252 appFS, err := fs.Chroot(e.Name())
253 if err != nil {
254 return nil, err
255 }
256 app, err := loadApp(appFS)
257 if err != nil {
258 log.Printf("Ignoring directory %s: %s", e.Name(), err)
259 continue
260 }
261 apps = append(apps, app)
262 }
263 return &fsAppRepository{
264 NewInMemoryAppRepository(apps),
265 fs,
266 }, nil
267}
268
269func loadApp(fs billy.Filesystem) (App, error) {
270 items, err := fs.ReadDir(".")
271 if err != nil {
272 return nil, err
273 }
274 var contents bytes.Buffer
275 for _, i := range items {
276 if i.IsDir() {
277 continue
278 }
279 f, err := fs.Open(i.Name())
280 if err != nil {
281 return nil, err
282 }
283 defer f.Close()
284 if _, err := io.Copy(&contents, f); err != nil {
285 return nil, err
286 }
287 }
gio308105e2024-04-19 13:12:13 +0400288 return NewCueEnvApp(CueAppData{
289 "base.cue": []byte(cueBaseConfig),
290 "app.cue": contents.Bytes(),
291 })
gio3cdee592024-04-17 10:15:56 +0400292}
293
gio308105e2024-04-19 13:12:13 +0400294// func readCueConfigFromFile(fs embed.FS, f string) (*cue.Value, error) {
295// contents, err := fs.ReadFile(f)
296// if err != nil {
297// return nil, err
298// }
299// return processCueConfig(string(contents))
300// }
gio3cdee592024-04-17 10:15:56 +0400301
gio308105e2024-04-19 13:12:13 +0400302// func processCueConfig(contents string) (*cue.Value, error) {
303// ctx := cuecontext.New()
304// cfg := ctx.CompileString(contents + cueBaseConfig)
305// if err := cfg.Err(); err != nil {
306// return nil, err
307// }
308// if err := cfg.Validate(); err != nil {
309// return nil, err
310// }
311// return &cfg, nil
312// }
gio3cdee592024-04-17 10:15:56 +0400313
314// func CreateAppMaddy(fs embed.FS, tmpls *template.Template) App {
315// schema, err := readJSONSchemaFromFile(fs, "values-tmpl/maddy.jsonschema")
316// if err != nil {
317// panic(err)
318// }
319// return StoreApp{
320// App{
321// "maddy",
322// []string{"app-maddy"},
323// []*template.Template{
324// tmpls.Lookup("maddy.yaml"),
325// },
326// schema,
327// nil,
328// nil,
329// },
330// `<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>`,
331// "SMPT/IMAP server to communicate via email.",
332// }
333// }
334
335func FindEnvApp(r AppRepository, name string) (EnvApp, error) {
336 app, err := r.Find(name)
337 if err != nil {
338 return nil, err
339 }
340 if a, ok := app.(EnvApp); ok {
341 return a, nil
342 } else {
343 return nil, fmt.Errorf("not found")
344 }
345}
346
347func FindInfraApp(r AppRepository, name string) (InfraApp, error) {
348 app, err := r.Find(name)
349 if err != nil {
350 return nil, err
351 }
352 if a, ok := app.(InfraApp); ok {
353 return a, nil
354 } else {
355 return nil, fmt.Errorf("not found")
356 }
357}