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}