1package config
2
3import (
4 "os/exec"
5 "path/filepath"
6 "strings"
7
8 "gopkg.in/yaml.v2"
9
10 "fmt"
11 "os"
12
13 "github.com/charmbracelet/soft-serve/config"
14 "github.com/charmbracelet/soft-serve/internal/git"
15 gg "github.com/go-git/go-git/v5"
16 "github.com/go-git/go-git/v5/plumbing/object"
17)
18
19// Config is the Soft Serve configuration.
20type Config struct {
21 Name string `yaml:"name"`
22 Host string `yaml:"host"`
23 Port int `yaml:"port"`
24 AnonAccess string `yaml:"anon-access"`
25 AllowKeyless bool `yaml:"allow-keyless"`
26 Users []User `yaml:"users"`
27 Repos []Repo `yaml:"repos"`
28 Source *git.RepoSource
29 Cfg *config.Config
30}
31
32// User contains user-level configuration for a repository.
33type User struct {
34 Name string `yaml:"name"`
35 Admin bool `yaml:"admin"`
36 PublicKeys []string `yaml:"public-keys"`
37 CollabRepos []string `yaml:"collab-repos"`
38}
39
40// Repo contains repository configuration information.
41type Repo struct {
42 Name string `yaml:"name"`
43 Repo string `yaml:"repo"`
44 Note string `yaml:"note"`
45 Private bool `yaml:"private"`
46}
47
48// NewConfig creates a new internal Config struct.
49func NewConfig(cfg *config.Config) (*Config, error) {
50 var anonAccess string
51 var yamlUsers string
52 var displayHost string
53 host := cfg.Host
54 port := cfg.Port
55 pk := cfg.InitialAdminKey
56 rs := git.NewRepoSource(cfg.RepoPath)
57 c := &Config{
58 Cfg: cfg,
59 }
60 c.Host = cfg.Host
61 c.Port = port
62 c.Source = rs
63 if pk == "" {
64 anonAccess = "read-write"
65 } else {
66 anonAccess = "no-access"
67 }
68 if host == "" {
69 displayHost = "localhost"
70 } else {
71 displayHost = host
72 }
73 yamlConfig := fmt.Sprintf(defaultConfig, displayHost, port, anonAccess)
74 if pk != "" {
75 pks := ""
76 for _, key := range strings.Split(strings.TrimSpace(pk), "\n") {
77 pks += fmt.Sprintf(" - %s\n", key)
78 }
79 yamlUsers = fmt.Sprintf(hasKeyUserConfig, pks)
80 } else {
81 yamlUsers = defaultUserConfig
82 }
83 yaml := fmt.Sprintf("%s%s%s", yamlConfig, yamlUsers, exampleUserConfig)
84 err := c.createDefaultConfigRepo(yaml)
85 if err != nil {
86 return nil, err
87 }
88 return c, nil
89}
90
91// Reload reloads the configuration.
92func (cfg *Config) Reload() error {
93 err := cfg.Source.LoadRepos()
94 if err != nil {
95 return err
96 }
97 cr, err := cfg.Source.GetRepo("config")
98 if err != nil {
99 return err
100 }
101 cs, err := cr.LatestFile("config.yaml")
102 if err != nil {
103 return err
104 }
105 err = yaml.Unmarshal([]byte(cs), cfg)
106 if err != nil {
107 return fmt.Errorf("bad yaml in config.yaml: %s", err)
108 }
109 return nil
110}
111
112func createFile(path string, content string) error {
113 f, err := os.Create(path)
114 if err != nil {
115 return err
116 }
117 defer f.Close()
118 _, err = f.WriteString(content)
119 if err != nil {
120 return err
121 }
122 return f.Sync()
123}
124
125func (cfg *Config) createDefaultConfigRepo(yaml string) error {
126 cn := "config"
127 rs := cfg.Source
128 err := rs.LoadRepos()
129 if err != nil {
130 return err
131 }
132 _, err = rs.GetRepo(cn)
133 if err == git.ErrMissingRepo {
134 cr, err := rs.InitRepo(cn, true)
135 if err != nil {
136 return err
137 }
138 wt, err := cr.Repository.Worktree()
139 if err != nil {
140 return err
141 }
142 rm, err := wt.Filesystem.Create("README.md")
143 if err != nil {
144 return err
145 }
146 _, err = rm.Write([]byte(defaultReadme))
147 if err != nil {
148 return err
149 }
150 cf, err := wt.Filesystem.Create("config.yaml")
151 if err != nil {
152 return err
153 }
154 _, err = cf.Write([]byte(yaml))
155 if err != nil {
156 return err
157 }
158 _, err = wt.Add("README.md")
159 if err != nil {
160 return err
161 }
162 _, err = wt.Add("config.yaml")
163 if err != nil {
164 return err
165 }
166 _, err = wt.Commit("Default init", &gg.CommitOptions{
167 All: true,
168 Author: &object.Signature{
169 Name: "Soft Serve Server",
170 Email: "vt100@charm.sh",
171 },
172 })
173 if err != nil {
174 return err
175 }
176 err = cr.Repository.Push(&gg.PushOptions{})
177 if err != nil {
178 return err
179 }
180 cmd := exec.Command("git", "update-server-info")
181 cmd.Dir = filepath.Join(rs.Path, cn)
182 err = cmd.Run()
183 if err != nil {
184 return err
185 }
186 } else if err != nil {
187 return err
188 }
189 return cfg.Reload()
190}
191
192func (cfg *Config) isPrivate(repo string) bool {
193 for _, r := range cfg.Repos {
194 if r.Repo == repo {
195 return r.Private
196 }
197 }
198 return false
199}