serve.go

  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}