074eada2c8630bfd321216a676c1702f6919a103
- Author
- Toby Padilla <toby@charm.sh>
- Committer
- Toby Padilla <toby@charm.sh>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/config/auth.go b/config/auth.go
2new file mode 100644
3index 0000000000000000000000000000000000000000..999262c5a07821e8e08a0b3417814c007977ab41
4--- /dev/null
5+++ b/config/auth.go
6@@ -0,0 +1,20 @@
7+package config
8+
9+import (
10+ gm "github.com/charmbracelet/wish/git"
11+ "github.com/gliderlabs/ssh"
12+)
13+
14+func (cfg *Config) AuthRepo(repo string, pk ssh.PublicKey) gm.AccessLevel {
15+ // TODO: check yaml for access rules
16+ return gm.ReadWriteAccess
17+}
18+
19+func (cfg *Config) PasswordHandler(ctx ssh.Context, password string) bool {
20+ return cfg.AnonReadOnly && cfg.AllowNoKeys
21+}
22+
23+func (cfg *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
24+ // TODO: check yaml for access rules
25+ return true
26+}
27diff --git a/config/config.go b/config/config.go
28new file mode 100644
29index 0000000000000000000000000000000000000000..b64974d8be1017ab8c78129d1c27dd9fa0275dfe
30--- /dev/null
31+++ b/config/config.go
32@@ -0,0 +1,164 @@
33+package config
34+
35+import (
36+ "log"
37+
38+ "gopkg.in/yaml.v2"
39+
40+ "fmt"
41+ "os"
42+ "path/filepath"
43+ "soft-serve/git"
44+
45+ "github.com/gliderlabs/ssh"
46+ gg "github.com/go-git/go-git/v5"
47+ "github.com/go-git/go-git/v5/plumbing/object"
48+)
49+
50+type Config struct {
51+ Name string `yaml:"name"`
52+ Host string `yaml:"host"`
53+ Port int `yaml:"port"`
54+ AnonReadOnly bool `yaml:"anon-access"`
55+ AllowNoKeys bool `yaml:"allow-no-keys"`
56+ Users []User `yaml:"users"`
57+ Repos []Repo `yaml:"repos"`
58+ Source *git.RepoSource
59+}
60+
61+type User struct {
62+ Name string `yaml:"name"`
63+ Admin bool `yaml:"admin"`
64+ PublicKey string `yaml:"pk"`
65+ CollabRepos []string `yaml:"collab_repos"`
66+}
67+
68+type Repo struct {
69+ Name string `yaml:"name"`
70+ Repo string `yaml:"repo"`
71+ Note string `yaml:"note"`
72+}
73+
74+func NewConfig(host string, port int, anon bool, pk string, rs *git.RepoSource) (*Config, error) {
75+ cfg := &Config{}
76+ cfg.Host = host
77+ cfg.Port = port
78+ cfg.AnonReadOnly = anon
79+ cfg.Source = rs
80+
81+ var yamlUsers string
82+ var h string
83+ if host == "" {
84+ h = "localhost"
85+ } else {
86+ h = host
87+ }
88+ yamlConfig := fmt.Sprintf(defaultConfig, h, port, anon)
89+ if pk != "" {
90+ yamlUsers = fmt.Sprintf(hasKeyUserConfig, pk)
91+ } else {
92+ yamlUsers = defaultUserConfig
93+ }
94+ yaml := fmt.Sprintf("%s%s%s", yamlConfig, yamlUsers, exampleUserConfig)
95+ err := cfg.createDefaultConfigRepo(yaml)
96+ if err != nil {
97+ return nil, err
98+ }
99+ return cfg, nil
100+}
101+
102+func (cfg *Config) Pushed(repo string, pk ssh.PublicKey) {
103+ err := cfg.Reload()
104+ if err != nil {
105+ log.Printf("error reloading after push: %s", err)
106+ }
107+}
108+
109+func (cfg *Config) Reload() error {
110+ err := cfg.Source.LoadRepos()
111+ if err != nil {
112+ return err
113+ }
114+ cr, err := cfg.Source.GetRepo("config")
115+ if err != nil {
116+ return err
117+ }
118+ cs, err := cr.LatestFile("config.yaml")
119+ if err != nil {
120+ return err
121+ }
122+ err = yaml.Unmarshal([]byte(cs), cfg)
123+ if err != nil {
124+ return fmt.Errorf("bad yaml in config.yaml: %s", err)
125+ }
126+ return nil
127+}
128+
129+func createFile(path string, content string) error {
130+ f, err := os.Create(path)
131+ if err != nil {
132diff --git a/config/defaults.go b/config/defaults.go
133new file mode 100644
134index 0000000000000000000000000000000000000000..1a7cbb3866df8db37deef9ac6e2bff02f0bc45e0
135--- /dev/null
136+++ b/config/defaults.go
137@@ -0,0 +1,42 @@
138+package config
139+
140diff --git a/go.mod b/go.mod
141index 0afb48b770c62697c2bc0f388c6418aca7031f46..c7408bd7120adf343dfb9e4084c5edbbe8d21f2e 100644
142--- a/go.mod
143+++ b/go.mod
144@@ -19,6 +19,7 @@ require (
145 github.com/muesli/reflow v0.3.0
146 github.com/muesli/termenv v0.9.0
147 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
148+ gopkg.in/yaml.v2 v2.3.0
149 )
150
151 require (
152diff --git a/main.go b/main.go
153index 71288764ed2e2fd5da0ff9e8b50e20499848de3f..92c1eaaccce8147781f2eca97b717ca0eac3cbe7 100644
154--- a/main.go
155+++ b/main.go
156@@ -3,45 +3,62 @@ package main
157 import (
158 "fmt"
159 "log"
160+ "soft-serve/config"
161+ "soft-serve/git"
162 "soft-serve/tui"
163- "time"
164
165 "github.com/charmbracelet/wish"
166 bm "github.com/charmbracelet/wish/bubbletea"
167 gm "github.com/charmbracelet/wish/git"
168 lm "github.com/charmbracelet/wish/logging"
169+ "github.com/gliderlabs/ssh"
170
171 "github.com/meowgorithm/babyenv"
172 )
173
174-type Config struct {
175- Port int `env:"SOFT_SERVE_PORT" default:"23231"`
176- Host string `env:"SOFT_SERVE_HOST" default:""`
177- KeyPath string `env:"SOFT_SERVE_KEY_PATH" default:".ssh/soft_serve_server_ed25519"`
178- RepoAuth string `env:"SOFT_SERVE_REPO_KEYS" default:""`
179- RepoAuthFile string `env:"SOFT_SERVE_REPO_KEYS_PATH" default:".ssh/soft_serve_git_authorized_keys"`
180- RepoPath string `env:"SOFT_SERVE_REPO_PATH" default:".repos"`
181+type serverConfig struct {
182+ Port int `env:"SOFT_SERVE_PORT" default:"23231"`
183+ Host string `env:"SOFT_SERVE_HOST" default:""`
184+ InitKey string `env:"SOFT_SERVE_REPO_KEY" default:""`
185+ KeyPath string `env:"SOFT_SERVE_KEY_PATH" default:".ssh/soft_serve_server_ed25519"`
186+ RepoPath string `env:"SOFT_SERVE_REPO_PATH" default:".repos"`
187 }
188
189 func main() {
190- var cfg Config
191- err := babyenv.Parse(&cfg)
192+ var scfg serverConfig
193+ var cfg *config.Config
194+ var err error
195+ err = babyenv.Parse(&scfg)
196 if err != nil {
197 log.Fatalln(err)
198 }
199+ rs := git.NewRepoSource(scfg.RepoPath)
200+ if scfg.InitKey == "" {
201+ cfg, err = config.NewConfig(scfg.Host, scfg.Port, true, "", rs)
202+ if err != nil {
203+ log.Fatalln(err)
204+ }
205+ } else {
206+ cfg, err = config.NewConfig(scfg.Host, scfg.Port, false, scfg.InitKey, rs)
207+ if err != nil {
208+ log.Fatalln(err)
209+ }
210+ }
211 s, err := wish.NewServer(
212- wish.WithAddress(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)),
213- wish.WithHostKeyPath(cfg.KeyPath),
214+ ssh.PublicKeyAuth(cfg.PublicKeyHandler),
215+ ssh.PasswordAuth(cfg.PasswordHandler),
216+ wish.WithAddress(fmt.Sprintf("%s:%d", scfg.Host, scfg.Port)),
217+ wish.WithHostKeyPath(scfg.KeyPath),
218 wish.WithMiddlewares(
219- bm.Middleware(tui.SessionHandler(cfg.RepoPath, time.Second*5)),
220- gm.Middleware(cfg.RepoPath, cfg.RepoAuth, cfg.RepoAuthFile),
221+ bm.Middleware(tui.SessionHandler(cfg)),
222+ gm.MiddlewareWithPushCallback(scfg.RepoPath, cfg, cfg.Pushed),
223 lm.Middleware(),
224 ),
225 )
226 if err != nil {
227 log.Fatalln(err)
228 }
229- log.Printf("Starting SSH server on %s:%d\n", cfg.Host, cfg.Port)
230+ log.Printf("Starting SSH server on %s:%d\n", scfg.Host, scfg.Port)
231 err = s.ListenAndServe()
232 if err != nil {
233 log.Fatalln(err)
234diff --git a/tui/bubble.go b/tui/bubble.go
235index e0ebb9ea62b40f0f5ea0d1f8503b6f4e83304e49..400a249ee7c813f3b3b9119c978e5586442e2ecf 100644
236--- a/tui/bubble.go
237+++ b/tui/bubble.go
238@@ -2,6 +2,7 @@ package tui
239
240 import (
241 "fmt"
242+ "soft-serve/config"
243 "soft-serve/git"
244 "soft-serve/tui/bubbles/repo"
245 "soft-serve/tui/bubbles/selection"
246@@ -22,13 +23,10 @@ const (
247 quitState
248 )
249
250-type Config struct {
251- Name string `json:"name"`
252- Host string `json:"host"`
253- Port int64 `json:"port"`
254- ShowAllRepos bool `json:"show_all_repos"`
255- Menu []MenuEntry `json:"menu"`
256- RepoSource *git.RepoSource
257+type SessionConfig struct {
258+ Width int
259+ Height int
260+ InitialRepo string
261 }
262
263 type MenuEntry struct {
264@@ -38,20 +36,13 @@ type MenuEntry struct {
265 bubble *repo.Bubble
266 }
267
268-type SessionConfig struct {
269- Width int
270- Height int
271- InitialRepo string
272-}
273-
274 type Bubble struct {
275- config *Config
276+ config *config.Config
277 styles *style.Styles
278 state sessionState
279 error string
280 width int
281 height int
282- repoSource *git.RepoSource
283 initialRepo string
284 repoMenu []MenuEntry
285 repos []*git.Repo
286@@ -60,17 +51,12 @@ type Bubble struct {
287 repoSelect *selection.Bubble
288 }
289
290-func NewBubble(cfg *Config, sCfg *SessionConfig) *Bubble {
291- var repoSource *git.RepoSource = nil
292- if cfg != nil {
293- repoSource = cfg.RepoSource
294- }
295+func NewBubble(cfg *config.Config, sCfg *SessionConfig) *Bubble {
296 b := &Bubble{
297 config: cfg,
298 styles: style.DefaultStyles(),
299 width: sCfg.Width,
300 height: sCfg.Height,
301- repoSource: repoSource,
302 repoMenu: make([]MenuEntry, 0),
303 boxes: make([]tea.Model, 2),
304 initialRepo: sCfg.InitialRepo,
305diff --git a/tui/bubbles/repo/bubble.go b/tui/bubbles/repo/bubble.go
306index aad6a734ea943ac014f7bf8005a9e30b98f1adae..bf2ac0c591c724b58b391fbb32975980163ce3b1 100644
307--- a/tui/bubbles/repo/bubble.go
308+++ b/tui/bubbles/repo/bubble.go
309@@ -46,7 +46,7 @@ type Bubble struct {
310 // solution would be to (rename and) move this Bubble into the parent
311 // package.
312 Host string
313- Port int64
314+ Port int
315 }
316
317 func NewBubble(rs *git.RepoSource, name string, styles *style.Styles, width, wm, height, hm int, tmp interface{}) *Bubble {
318diff --git a/tui/commands.go b/tui/commands.go
319index 4f7c6d45259e13c77632a2f4fb6f43b1c4ccb99d..7f777cc2e40db1418837fd27abfeb1fd4be2a8ee 100644
320--- a/tui/commands.go
321+++ b/tui/commands.go
322@@ -3,6 +3,7 @@ package tui
323 import (
324 "fmt"
325 "log"
326+ "soft-serve/config"
327 "soft-serve/tui/bubbles/repo"
328 "soft-serve/tui/bubbles/selection"
329 "time"
330@@ -19,29 +20,27 @@ func (e errMsg) Error() string {
331 }
332
333 func (b *Bubble) setupCmd() tea.Msg {
334- if b.config == nil || b.config.RepoSource == nil {
335+ if b.config == nil || b.config.Source == nil {
336 return errMsg{err: fmt.Errorf("config not set")}
337 }
338 ct := time.Now()
339 lipgloss.SetColorProfile(termenv.ANSI256)
340- b.repos = b.repoSource.AllRepos()
341- mes := append([]MenuEntry{}, b.config.Menu...)
342+ b.repos = b.config.Source.AllRepos()
343+ mes := append([]MenuEntry{}, b.repoMenu...)
344 rs := make([]string, 0)
345- if b.config.ShowAllRepos {
346- OUTER:
347- for _, r := range b.repos {
348- for _, me := range mes {
349- if r.Name == me.Repo {
350- continue OUTER
351- }
352+OUTER:
353+ for _, r := range b.repos {
354+ for _, me := range mes {
355+ if r.Name == me.Repo {
356+ continue OUTER
357 }
358- mes = append(mes, MenuEntry{Name: r.Name, Repo: r.Name})
359 }
360+ mes = append(mes, MenuEntry{Name: r.Name, Repo: r.Name})
361 }
362 if len(mes) == 0 {
363 return errMsg{fmt.Errorf("no repos found")}
364 }
365- var tmplConfig *Config
366+ var tmplConfig *config.Config
367 for _, me := range mes {
368 if me.Repo == "config" {
369 tmplConfig = b.config
370@@ -53,7 +52,7 @@ func (b *Bubble) setupCmd() tea.Msg {
371 lipgloss.Height(b.footerView()) +
372 b.styles.RepoBody.GetVerticalFrameSize() +
373 b.styles.App.GetVerticalMargins()
374- rb := repo.NewBubble(b.repoSource, me.Repo, b.styles, width, boxLeftWidth, b.height, heightMargin, tmplConfig)
375+ rb := repo.NewBubble(b.config.Source, me.Repo, b.styles, width, boxLeftWidth, b.height, heightMargin, tmplConfig)
376 rb.Host = b.config.Host
377 rb.Port = b.config.Port
378 initCmd := rb.Init()
379diff --git a/tui/session.go b/tui/session.go
380index 78ed852cddbea2cceee98d2015aac07b18c58e52..51eedc361eb876aef7534ae193d0b049cf73767a 100644
381--- a/tui/session.go
382+++ b/tui/session.go
383@@ -1,52 +1,22 @@
384 package tui
385
386 import (
387- "encoding/json"
388 "fmt"
389- "log"
390- "soft-serve/git"
391- "time"
392+ "soft-serve/config"
393
394 tea "github.com/charmbracelet/bubbletea"
395 "github.com/gliderlabs/ssh"
396 )
397
398-func SessionHandler(reposPath string, repoPoll time.Duration) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
399- rs := git.NewRepoSource(reposPath)
400- // createDefaultConfigRepo runs rs.LoadRepos()
401- err := createDefaultConfigRepo(rs)
402- if err != nil {
403- if err != nil {
404- log.Fatalf("cannot create config repo: %s", err)
405- }
406- }
407- appCfg, err := loadConfig(rs)
408- if err != nil {
409- log.Printf("cannot load config: %s", err)
410- }
411-
412+func SessionHandler(cfg *config.Config) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
413 return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
414 cmd := s.Command()
415- // reload repos and config on git push
416- if len(cmd) > 0 && cmd[0] == "git-receive-pack" {
417- ct := time.Now()
418- err := rs.LoadRepos()
419- if err != nil {
420- log.Printf("cannot load repos: %s", err)
421- }
422- cfg, err := loadConfig(rs)
423- if err != nil {
424- log.Printf("cannot load config: %s", err)
425- }
426- appCfg = cfg
427- log.Printf("Repo bubble loaded in %s", time.Since(ct))
428- }
429- cfg := &SessionConfig{}
430+ scfg := &SessionConfig{}
431 switch len(cmd) {
432 case 0:
433- cfg.InitialRepo = ""
434+ scfg.InitialRepo = ""
435 case 1:
436- cfg.InitialRepo = cmd[0]
437+ scfg.InitialRepo = cmd[0]
438 default:
439 return nil, nil
440 }
441@@ -55,26 +25,8 @@ func SessionHandler(reposPath string, repoPoll time.Duration) func(ssh.Session)
442 fmt.Println("not active")
443 return nil, nil
444 }
445- cfg.Width = pty.Window.Width
446- cfg.Height = pty.Window.Height
447- return NewBubble(appCfg, cfg), []tea.ProgramOption{tea.WithAltScreen()}
448- }
449-}
450-
451-func loadConfig(rs *git.RepoSource) (*Config, error) {
452- cfg := &Config{}
453- cfg.RepoSource = rs
454- cr, err := rs.GetRepo("config")
455- if err != nil {
456- return nil, err
457- }
458- cs, err := cr.LatestFile("config.json")
459- if err != nil {
460- return nil, err
461- }
462- err = json.Unmarshal([]byte(cs), cfg)
463- if err != nil {
464- return nil, fmt.Errorf("bad json in config.json: %s", err)
465+ scfg.Width = pty.Window.Width
466+ scfg.Height = pty.Window.Height
467+ return NewBubble(cfg, scfg), []tea.ProgramOption{tea.WithAltScreen()}
468 }
469- return cfg, nil
470 }