Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: config supports toggling HTTP and Stats servers on/off (#594) #600

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 45 additions & 28 deletions cmd/soft/serve/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,34 +93,51 @@
// Start starts the SSH server.
func (s *Server) Start() error {
errg, _ := errgroup.WithContext(s.ctx)
errg.Go(func() error {
s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
if err := s.GitDaemon.Start(); !errors.Is(err, daemon.ErrServerClosed) {
return err
}
return nil
})
errg.Go(func() error {
s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
if err := s.HTTPServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
})
errg.Go(func() error {
s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {
return err
}
return nil
})
errg.Go(func() error {
s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
if err := s.StatsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
})

// optionally start the SSH server
if s.Config.SSH.Enabled {
errg.Go(func() error {
s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
if err := s.SSHServer.ListenAndServe(); !errors.Is(err, ssh.ErrServerClosed) {
return err

Check failure on line 102 in cmd/soft/serve/server.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/ssh.SSHServer).ListenAndServe() error (wrapcheck)
}
return nil
})
}

// optionally start the git daemon
if s.Config.Git.Enabled {
errg.Go(func() error {
s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
if err := s.GitDaemon.Start(); !errors.Is(err, daemon.ErrServerClosed) {
return err

Check failure on line 113 in cmd/soft/serve/server.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/daemon.GitDaemon).Start() error (wrapcheck)
}
return nil
})
}

// optionally start the HTTP server
if s.Config.HTTP.Enabled {
errg.Go(func() error {
s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
if err := s.HTTPServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return err

Check failure on line 124 in cmd/soft/serve/server.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/web.HTTPServer).ListenAndServe() error (wrapcheck)
}
return nil
})
}

// optionally start the Stats server
if s.Config.Stats.Enabled {
errg.Go(func() error {
s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr)
if err := s.StatsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return err

Check failure on line 135 in cmd/soft/serve/server.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func (*github.com/charmbracelet/soft-serve/pkg/stats.StatsServer).ListenAndServe() error (wrapcheck)
}
return nil
})
}

errg.Go(func() error {
s.Cron.Start()
return nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var binPath = "soft"

// SSHConfig is the configuration for the SSH server.
type SSHConfig struct {
// Enabled toggles the SSH server on/off
Enabled bool `env:"ENABLED" yaml:"enabled"`

// ListenAddr is the address on which the SSH server will listen.
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

Expand All @@ -39,6 +42,9 @@ type SSHConfig struct {

// GitConfig is the Git daemon configuration for the server.
type GitConfig struct {
// Enabled toggles the Git daemon on/off
Enabled bool `env:"ENABLED" yaml:"enabled"`

// ListenAddr is the address on which the Git daemon will listen.
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

Expand All @@ -57,6 +63,9 @@ type GitConfig struct {

// HTTPConfig is the HTTP configuration for the server.
type HTTPConfig struct {
// Enabled toggles the HTTP server on/off
Enabled bool `env:"ENABLED" yaml:"enabled"`

// ListenAddr is the address on which the HTTP server will listen.
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

Expand All @@ -72,6 +81,9 @@ type HTTPConfig struct {

// StatsConfig is the configuration for the stats server.
type StatsConfig struct {
// Enabled toggles the Stats server on/off
Enabled bool `env:"ENABLED" yaml:"enabled"`

// ListenAddr is the address on which the stats server will listen.
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
}
Expand Down Expand Up @@ -165,21 +177,25 @@ func (c *Config) Environ() []string {
fmt.Sprintf("SOFT_SERVE_DATA_PATH=%s", c.DataPath),
fmt.Sprintf("SOFT_SERVE_NAME=%s", c.Name),
fmt.Sprintf("SOFT_SERVE_INITIAL_ADMIN_KEYS=%s", strings.Join(c.InitialAdminKeys, "\n")),
fmt.Sprintf("SOFT_SERVE_SSH_ENABLED=%t", c.SSH.Enabled),
fmt.Sprintf("SOFT_SERVE_SSH_LISTEN_ADDR=%s", c.SSH.ListenAddr),
fmt.Sprintf("SOFT_SERVE_SSH_PUBLIC_URL=%s", c.SSH.PublicURL),
fmt.Sprintf("SOFT_SERVE_SSH_KEY_PATH=%s", c.SSH.KeyPath),
fmt.Sprintf("SOFT_SERVE_SSH_CLIENT_KEY_PATH=%s", c.SSH.ClientKeyPath),
fmt.Sprintf("SOFT_SERVE_SSH_MAX_TIMEOUT=%d", c.SSH.MaxTimeout),
fmt.Sprintf("SOFT_SERVE_SSH_IDLE_TIMEOUT=%d", c.SSH.IdleTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_ENABLED=%t", c.Git.Enabled),
fmt.Sprintf("SOFT_SERVE_GIT_LISTEN_ADDR=%s", c.Git.ListenAddr),
fmt.Sprintf("SOFT_SERVE_GIT_PUBLIC_URL=%s", c.Git.PublicURL),
fmt.Sprintf("SOFT_SERVE_GIT_MAX_TIMEOUT=%d", c.Git.MaxTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_IDLE_TIMEOUT=%d", c.Git.IdleTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_MAX_CONNECTIONS=%d", c.Git.MaxConnections),
fmt.Sprintf("SOFT_SERVE_HTTP_ENABLED=%t", c.HTTP.Enabled),
fmt.Sprintf("SOFT_SERVE_HTTP_LISTEN_ADDR=%s", c.HTTP.ListenAddr),
fmt.Sprintf("SOFT_SERVE_HTTP_TLS_KEY_PATH=%s", c.HTTP.TLSKeyPath),
fmt.Sprintf("SOFT_SERVE_HTTP_TLS_CERT_PATH=%s", c.HTTP.TLSCertPath),
fmt.Sprintf("SOFT_SERVE_HTTP_PUBLIC_URL=%s", c.HTTP.PublicURL),
fmt.Sprintf("SOFT_SERVE_STATS_ENABLED=%t", c.Stats.Enabled),
fmt.Sprintf("SOFT_SERVE_STATS_LISTEN_ADDR=%s", c.Stats.ListenAddr),
fmt.Sprintf("SOFT_SERVE_LOG_FORMAT=%s", c.Log.Format),
fmt.Sprintf("SOFT_SERVE_LOG_TIME_FORMAT=%s", c.Log.TimeFormat),
Expand Down Expand Up @@ -318,6 +334,7 @@ func DefaultConfig() *Config {
Name: "Soft Serve",
DataPath: DefaultDataPath(),
SSH: SSHConfig{
Enabled: true,
ListenAddr: ":23231",
PublicURL: "ssh://localhost:23231",
KeyPath: filepath.Join("ssh", "soft_serve_host_ed25519"),
Expand All @@ -326,17 +343,20 @@ func DefaultConfig() *Config {
IdleTimeout: 10 * 60, // 10 minutes
},
Git: GitConfig{
Enabled: true,
ListenAddr: ":9418",
PublicURL: "git://localhost",
MaxTimeout: 0,
IdleTimeout: 3,
MaxConnections: 32,
},
HTTP: HTTPConfig{
Enabled: true,
ListenAddr: ":23232",
PublicURL: "http://localhost:23232",
},
Stats: StatsConfig{
Enabled: true,
ListenAddr: "localhost:23233",
},
Log: LogConfig{
Expand Down
20 changes: 15 additions & 5 deletions pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,21 @@
conns: connections{m: make(map[net.Conn]struct{})},
logger: log.FromContext(ctx).WithPrefix("gitdaemon"),
}
listener, err := net.Listen("tcp", d.addr)
if err != nil {
return nil, err
}
d.listener = listener
return d, nil
}

// Start starts the Git TCP daemon.
func (d *GitDaemon) Start() error {
// listen on the socket
{
listener, err := net.Listen("tcp", d.addr)
if err != nil {
return err

Check failure on line 79 in pkg/daemon/daemon.go

View workflow job for this annotation

GitHub Actions / lint-soft

error returned from external package is unwrapped: sig: func net.Listen(network string, address string) (net.Listener, error) (wrapcheck)
}
d.listener = listener
}

// close eventual connections to the socket
defer d.listener.Close() // nolint: errcheck

d.wg.Add(1)
Expand Down Expand Up @@ -308,6 +313,11 @@

// Shutdown gracefully shuts down the daemon.
func (d *GitDaemon) Shutdown(ctx context.Context) error {
// in the case when git daemon was never started
if d.listener == nil {
return nil
}

d.once.Do(func() { close(d.finished) })
err := d.listener.Close()
finished := make(chan struct{}, 1)
Expand Down
84 changes: 66 additions & 18 deletions testscript/script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ var (
binPath string
)

func PrepareBuildCommand(binPath string) *exec.Cmd {
_, disableRaceSet := os.LookupEnv("DISABLE_RACE_CHECKS")
if disableRaceSet {
// don't add the -race flag
return exec.Command("go", "build", "-cover", "-o", binPath, filepath.Join("..", "cmd", "soft"))
}
return exec.Command("go", "build", "-race", "-cover", "-o", binPath, filepath.Join("..", "cmd", "soft"))
}

func TestMain(m *testing.M) {
tmp, err := os.MkdirTemp("", "soft-serve*")
if err != nil {
Expand All @@ -48,7 +57,7 @@ func TestMain(m *testing.M) {
}

// Build the soft binary with -cover flag.
cmd := exec.Command("go", "build", "-race", "-cover", "-o", binPath, filepath.Join("..", "cmd", "soft"))
cmd := PrepareBuildCommand(binPath)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "failed to build soft-serve binary: %s", err)
os.Exit(1)
Expand Down Expand Up @@ -79,20 +88,21 @@ func TestScript(t *testing.T) {
UpdateScripts: *update,
RequireExplicitExec: true,
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
"soft": cmdSoft("admin", admin1.Signer()),
"usoft": cmdSoft("user1", user1.Signer()),
"git": cmdGit(admin1Key),
"ugit": cmdGit(user1Key),
"curl": cmdCurl,
"mkfile": cmdMkfile,
"envfile": cmdEnvfile,
"readfile": cmdReadfile,
"dos2unix": cmdDos2Unix,
"new-webhook": cmdNewWebhook,
"waitforserver": cmdWaitforserver,
"stopserver": cmdStopserver,
"ui": cmdUI(admin1.Signer()),
"uui": cmdUI(user1.Signer()),
"soft": cmdSoft("admin", admin1.Signer()),
"usoft": cmdSoft("user1", user1.Signer()),
"git": cmdGit(admin1Key),
"ugit": cmdGit(user1Key),
"curl": cmdCurl,
"mkfile": cmdMkfile,
"envfile": cmdEnvfile,
"readfile": cmdReadfile,
"dos2unix": cmdDos2Unix,
"new-webhook": cmdNewWebhook,
"ensureserverrunning": cmdEnsureServerRunning,
"ensureservernotrunning": cmdEnsureServerNotRunning,
"stopserver": cmdStopserver,
"ui": cmdUI(admin1.Signer()),
"uui": cmdUI(user1.Signer()),
},
Setup: func(e *testscript.Env) error {
// Add binPath to PATH
Expand All @@ -112,6 +122,8 @@ func TestScript(t *testing.T) {
e.Setenv("DATA_PATH", data)
e.Setenv("SSH_PORT", fmt.Sprintf("%d", sshPort))
e.Setenv("HTTP_PORT", fmt.Sprintf("%d", httpPort))
e.Setenv("STATS_PORT", fmt.Sprintf("%d", statsPort))
e.Setenv("GIT_PORT", fmt.Sprintf("%d", gitPort))
e.Setenv("ADMIN1_AUTHORIZED_KEY", admin1.AuthorizedKey())
e.Setenv("ADMIN2_AUTHORIZED_KEY", admin2.AuthorizedKey())
e.Setenv("USER1_AUTHORIZED_KEY", user1.AuthorizedKey())
Expand Down Expand Up @@ -470,22 +482,58 @@ func cmdCurl(ts *testscript.TestScript, neg bool, args []string) {
check(ts, cmd.Execute(), neg)
}

func cmdWaitforserver(ts *testscript.TestScript, neg bool, args []string) {
// wait until the server is up
addr := net.JoinHostPort("localhost", ts.Getenv("SSH_PORT"))
func cmdEnsureServerRunning(ts *testscript.TestScript, neg bool, args []string) {
if len(args) < 1 {
ts.Fatalf("Must supply a TCP port of one of the services to connect to. " +
"These are set as env vars as they are randomized. " +
"Example usage: \"cmdensureserverrunning SSH_PORT\"\n" +
"Valid values for the env var: SSH_PORT|HTTP_PORT|GIT_PORT|STATS_PORT")
}

port := ts.Getenv(args[0])

// verify that the server is up
addr := net.JoinHostPort("localhost", port)
for {
conn, _ := net.DialTimeout(
"tcp",
addr,
time.Second,
)
if conn != nil {
ts.Logf("Server is running on port: %s", port)
conn.Close()
break
}
}
}

func cmdEnsureServerNotRunning(ts *testscript.TestScript, neg bool, args []string) {
if len(args) < 1 {
ts.Fatalf("Must supply a TCP port of one of the services to connect to. " +
"These are set as env vars as they are randomized. " +
"Example usage: \"cmdensureservernotrunning SSH_PORT\"\n" +
"Valid values for the env var: SSH_PORT|HTTP_PORT|GIT_PORT|STATS_PORT")
}

port := ts.Getenv(args[0])

// verify that the server is not up
addr := net.JoinHostPort("localhost", port)
for {
conn, _ := net.DialTimeout(
"tcp",
addr,
time.Second,
)
if conn != nil {
ts.Fatalf("server is running on port %s while it should not be running", port)
conn.Close()
}
break
}
}

func cmdStopserver(ts *testscript.TestScript, neg bool, args []string) {
// stop the server
resp, err := http.DefaultClient.Head(fmt.Sprintf("%s/__stop", ts.Getenv("SOFT_SERVE_HTTP_PUBLIC_URL")))
Expand Down
4 changes: 2 additions & 2 deletions testscript/testdata/anon-access.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

# start soft serve
exec soft serve &
# wait for server to start
waitforserver
# wait for SSH server to start
ensureserverrunning SSH_PORT

# set settings
soft settings allow-keyless true
Expand Down
18 changes: 18 additions & 0 deletions testscript/testdata/config-servers-git_disabled.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# vi: set ft=conf

# disable git listening
env SOFT_SERVE_SSH_ENABLED=true
env SOFT_SERVE_GIT_ENABLED=false
env SOFT_SERVE_HTTP_ENABLED=true
env SOFT_SERVE_STATS_ENABLED=true

# start soft serve
exec soft serve --sync-hooks &

# wait for the ssh + other servers to come up
ensureserverrunning SSH_PORT
ensureserverrunning HTTP_PORT
ensureserverrunning STATS_PORT

# ensure that the disabled server is not running
ensureservernotrunning GIT_PORT
19 changes: 19 additions & 0 deletions testscript/testdata/config-servers-http_disabled.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# vi: set ft=conf

# disable http listening
env SOFT_SERVE_SSH_ENABLED=true
env SOFT_SERVE_GIT_ENABLED=true
env SOFT_SERVE_HTTP_ENABLED=false
env SOFT_SERVE_STATS_ENABLED=true

# start soft serve
exec soft serve --sync-hooks &

# wait for the ssh + other servers to come up
ensureserverrunning SSH_PORT
ensureserverrunning GIT_PORT
ensureserverrunning STATS_PORT

# ensure that the disabled server is not running
ensureservernotrunning HTTP_PORT

Loading
Loading