1package main
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "log"
8 "os"
9 "os/signal"
10 "strings"
11 "syscall"
12 "text/template"
13 "time"
14
15 "github.com/charmbracelet/keygen"
16 "github.com/charmbracelet/soft-serve/config"
17 "github.com/charmbracelet/soft-serve/server"
18 "github.com/spf13/cobra"
19)
20
21var (
22 hookTmpl *template.Template
23 initHooks bool
24)
25
26var (
27 serveCmd = &cobra.Command{
28 Use: "serve",
29 Short: "Start the server",
30 Long: "Start the server",
31 Args: cobra.NoArgs,
32 RunE: func(cmd *cobra.Command, args []string) error {
33 cfg := config.DefaultConfig()
34 // Internal API keypair
35 _, err := keygen.NewWithWrite(
36 strings.TrimSuffix(cfg.InternalKeyPath, "_ed25519"),
37 nil,
38 keygen.Ed25519,
39 )
40 if err != nil {
41 return err
42 }
43 // Create git server hooks
44 if initHooks {
45 ex, err := os.Executable()
46 if err != nil {
47 return err
48 }
49 repos, err := os.ReadDir(cfg.RepoPath)
50 if err != nil {
51 return err
52 }
53 for _, repo := range repos {
54 for _, hook := range []string{"pre-receive", "update", "post-receive"} {
55 var data bytes.Buffer
56 var args string
57 hp := fmt.Sprintf("%s/%s/hooks/%s", cfg.RepoPath, repo.Name(), hook)
58 if hook == "update" {
59 args = "$1 $2 $3"
60 }
61 err = hookTmpl.Execute(&data, hookScript{
62 Executable: ex,
63 Hook: hook,
64 Args: args,
65 })
66 if err != nil {
67 return err
68 }
69 err = os.WriteFile(hp, data.Bytes(), 0755) //nolint:gosec
70 if err != nil {
71 return err
72 }
73 }
74 }
75 }
76 s := server.NewServer(cfg)
77
78 done := make(chan os.Signal, 1)
79 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
80
81 log.Printf("Starting SSH server on %s:%d", cfg.BindAddr, cfg.Port)
82 go func() {
83 if err := s.Start(); err != nil {
84 log.Fatalln(err)
85 }
86 }()
87
88 <-done
89
90 log.Printf("Stopping SSH server on %s:%d", cfg.BindAddr, cfg.Port)
91 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
92 defer func() { cancel() }()
93 return s.Shutdown(ctx)
94 },
95 }
96)
97
98type hookScript struct {
99 Executable string
100 Hook string
101 Args string
102}
103
104func init() {
105 hookTmpl = template.New("hook")
106 hookTmpl, _ = hookTmpl.Parse(`#!/usr/bin/env bash
107# AUTO GENERATED BY SOFT SERVE, DO NOT MODIFY
108{{ .Executable }} internal hook {{ .Hook }} {{ .Args }}
109`)
110 serveCmd.Flags().BoolVarP(&initHooks, "init-hooks", "i", false, "Initialize git hooks")
111}