diff --git a/cmd/soft/serve/server.go b/cmd/soft/serve/server.go index 33679091..c1379340 100644 --- a/cmd/soft/serve/server.go +++ b/cmd/soft/serve/server.go @@ -93,34 +93,51 @@ func NewServer(ctx context.Context) (*Server, error) { // 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 + } + 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 + } + 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 + } + 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 + } + return nil + }) + } + errg.Go(func() error { s.Cron.Start() return nil diff --git a/pkg/config/config.go b/pkg/config/config.go index 2b650984..9d8395d4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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"` @@ -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"` @@ -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"` @@ -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"` } @@ -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), @@ -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"), @@ -326,6 +343,7 @@ func DefaultConfig() *Config { IdleTimeout: 10 * 60, // 10 minutes }, Git: GitConfig{ + Enabled: true, ListenAddr: ":9418", PublicURL: "git://localhost", MaxTimeout: 0, @@ -333,10 +351,12 @@ func DefaultConfig() *Config { MaxConnections: 32, }, HTTP: HTTPConfig{ + Enabled: true, ListenAddr: ":23232", PublicURL: "http://localhost:23232", }, Stats: StatsConfig{ + Enabled: true, ListenAddr: "localhost:23233", }, Log: LogConfig{ diff --git a/testscript/script_test.go b/testscript/script_test.go index 4277122f..ce45dec3 100644 --- a/testscript/script_test.go +++ b/testscript/script_test.go @@ -88,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, - "ensureserverrunning": cmdEnsureServerRunning, - "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 @@ -121,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()) @@ -505,6 +508,32 @@ func cmdEnsureServerRunning(ts *testscript.TestScript, neg bool, args []string) } } +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"))) diff --git a/testscript/testdata/config-servers-git_disabled.txtar b/testscript/testdata/config-servers-git_disabled.txtar new file mode 100644 index 00000000..7fe34eda --- /dev/null +++ b/testscript/testdata/config-servers-git_disabled.txtar @@ -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 diff --git a/testscript/testdata/config-servers-http_disabled.txtar b/testscript/testdata/config-servers-http_disabled.txtar new file mode 100644 index 00000000..7eee3ed5 --- /dev/null +++ b/testscript/testdata/config-servers-http_disabled.txtar @@ -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 + diff --git a/testscript/testdata/config-servers-ssh_disabled.txtar b/testscript/testdata/config-servers-ssh_disabled.txtar new file mode 100644 index 00000000..30b7492d --- /dev/null +++ b/testscript/testdata/config-servers-ssh_disabled.txtar @@ -0,0 +1,18 @@ +# vi: set ft=conf + +# disable ssh listening +env SOFT_SERVE_SSH_ENABLED=false +env SOFT_SERVE_GIT_ENABLED=true +env SOFT_SERVE_HTTP_ENABLED=true +env SOFT_SERVE_STATS_ENABLED=true + +# start soft serve +exec soft serve --sync-hooks & + +# wait for the git + other servers to come up +ensureserverrunning GIT_PORT +ensureserverrunning HTTP_PORT +ensureserverrunning STATS_PORT + +# ensure that the disabled server is not running +ensureservernotrunning SSH_PORT diff --git a/testscript/testdata/config-servers-stats_disabled.txtar b/testscript/testdata/config-servers-stats_disabled.txtar new file mode 100644 index 00000000..2b803175 --- /dev/null +++ b/testscript/testdata/config-servers-stats_disabled.txtar @@ -0,0 +1,18 @@ +# vi: set ft=conf + +# disable stats listening +env SOFT_SERVE_SSH_ENABLED=true +env SOFT_SERVE_GIT_ENABLED=true +env SOFT_SERVE_HTTP_ENABLED=true +env SOFT_SERVE_STATS_ENABLED=false + +# start soft serve +exec soft serve --sync-hooks & + +# wait for the ssh + other servers to come up +ensureserverrunning SSH_PORT +ensureserverrunning GIT_PORT +ensureserverrunning HTTP_PORT + +# ensure that the disabled server is not running +ensureservernotrunning STATS_PORT