diff --git a/buildinfo/buildinfo.go b/buildinfo/buildinfo.go index d55385eae7..bafd3a916b 100644 --- a/buildinfo/buildinfo.go +++ b/buildinfo/buildinfo.go @@ -21,8 +21,12 @@ var ( version string readVersion sync.Once - // Injected with ldflags at build! - tag string + // Updated by buildinfo_slim.go on start. + slim bool + + // Injected with ldflags at build, see scripts/build_go.sh + tag string + agpl string // either "true" or "false", ldflags does not support bools ) const ( @@ -73,6 +77,16 @@ func IsDev() bool { return strings.HasPrefix(Version(), develPrefix) } +// IsSlim returns true if this is a slim build. +func IsSlim() bool { + return slim +} + +// IsAGPL returns true if this is an AGPL build. +func IsAGPL() bool { + return strings.Contains(agpl, "t") +} + // ExternalURL returns a URL referencing the current Coder version. // For production builds, this will link directly to a release. // For development builds, this will link to a commit. diff --git a/buildinfo/buildinfo_slim.go b/buildinfo/buildinfo_slim.go new file mode 100644 index 0000000000..651527455b --- /dev/null +++ b/buildinfo/buildinfo_slim.go @@ -0,0 +1,7 @@ +//go:build slim + +package buildinfo + +func init() { + slim = true +} diff --git a/cli/agent.go b/cli/agent.go index baf55eccea..95744aa340 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -181,3 +181,26 @@ func workspaceAgent() *cobra.Command { cliflag.StringVarP(cmd.Flags(), &pprofAddress, "pprof-address", "", "CODER_AGENT_PPROF_ADDRESS", "127.0.0.1:6060", "The address to serve pprof.") return cmd } + +func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler, addr, name string) (closeFunc func()) { + logger.Debug(ctx, "http server listening", slog.F("addr", addr), slog.F("name", name)) + + // ReadHeaderTimeout is purposefully not enabled. It caused some issues with + // websockets over the dev tunnel. + // See: https://github.com/coder/coder/pull/3730 + //nolint:gosec + srv := &http.Server{ + Addr: addr, + Handler: handler, + } + go func() { + err := srv.ListenAndServe() + if err != nil && !xerrors.Is(err, http.ErrServerClosed) { + logger.Error(ctx, "http server listen", slog.F("name", name), slog.Error(err)) + } + }() + + return func() { + _ = srv.Close() + } +} diff --git a/cli/root.go b/cli/root.go index e7fdab6b91..3d0b237e89 100644 --- a/cli/root.go +++ b/cli/root.go @@ -213,12 +213,22 @@ func versionCmd() *cobra.Command { Short: "Show coder version", RunE: func(cmd *cobra.Command, args []string) error { var str strings.Builder - _, _ = str.WriteString(fmt.Sprintf("Coder %s", buildinfo.Version())) + _, _ = str.WriteString("Coder ") + if buildinfo.IsAGPL() { + _, _ = str.WriteString("(AGPL) ") + } + _, _ = str.WriteString(buildinfo.Version()) buildTime, valid := buildinfo.Time() if valid { _, _ = str.WriteString(" " + buildTime.Format(time.UnixDate)) } - _, _ = str.WriteString("\r\n" + buildinfo.ExternalURL() + "\r\n") + _, _ = str.WriteString("\r\n" + buildinfo.ExternalURL() + "\r\n\r\n") + + if buildinfo.IsSlim() { + _, _ = str.WriteString(fmt.Sprintf("Slim build of Coder, does not support the %s subcommand.\n", cliui.Styles.Code.Render("server"))) + } else { + _, _ = str.WriteString(fmt.Sprintf("Full build of Coder, supports the %s subcommand.\n", cliui.Styles.Code.Render("server"))) + } _, _ = fmt.Fprint(cmd.OutOrStdout(), str.String()) return nil diff --git a/cli/server.go b/cli/server.go index 5165478f21..66970f0031 100644 --- a/cli/server.go +++ b/cli/server.go @@ -1,3 +1,5 @@ +//go:build !slim + package cli import ( @@ -1370,29 +1372,6 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al }, nil } -func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler, addr, name string) (closeFunc func()) { - logger.Debug(ctx, "http server listening", slog.F("addr", addr), slog.F("name", name)) - - // ReadHeaderTimeout is purposefully not enabled. It caused some issues with - // websockets over the dev tunnel. - // See: https://github.com/coder/coder/pull/3730 - //nolint:gosec - srv := &http.Server{ - Addr: addr, - Handler: handler, - } - go func() { - err := srv.ListenAndServe() - if err != nil && !xerrors.Is(err, http.ErrServerClosed) { - logger.Error(ctx, "http server listen", slog.F("name", name), slog.Error(err)) - } - }() - - return func() { - _ = srv.Close() - } -} - // embeddedPostgresURL returns the URL for the embedded PostgreSQL deployment. func embeddedPostgresURL(cfg config.Root) (string, error) { pgPassword, err := cfg.PostgresPassword().Read() diff --git a/cli/server_slim.go b/cli/server_slim.go new file mode 100644 index 0000000000..ae6f9cbaea --- /dev/null +++ b/cli/server_slim.go @@ -0,0 +1,68 @@ +//go:build slim + +package cli + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/cli/deployment" + "github.com/coder/coder/coderd" +) + +func Server(vip *viper.Viper, _ func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { + root := &cobra.Command{ + Use: "server", + Short: "Start a Coder server", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + serverUnsupported(cmd.ErrOrStderr()) + return nil + }, + } + + var pgRawURL bool + postgresBuiltinURLCmd := &cobra.Command{ + Use: "postgres-builtin-url", + Short: "Output the connection URL for the built-in PostgreSQL deployment.", + Hidden: true, + RunE: func(cmd *cobra.Command, _ []string) error { + serverUnsupported(cmd.ErrOrStderr()) + return nil + }, + } + postgresBuiltinServeCmd := &cobra.Command{ + Use: "postgres-builtin-serve", + Short: "Run the built-in PostgreSQL deployment.", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + serverUnsupported(cmd.ErrOrStderr()) + return nil + }, + } + + // We still have to attach the flags to the commands so users don't get + // an error when they try to use them. + postgresBuiltinURLCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") + postgresBuiltinServeCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") + + root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd) + + deployment.AttachFlags(root.Flags(), vip, false) + + return root +} + +func serverUnsupported(w io.Writer) { + _, _ = fmt.Fprintf(w, "You are using a 'slim' build of Coder, which does not support the %s subcommand.\n", cliui.Styles.Code.Render("server")) + _, _ = fmt.Fprintln(w, "") + _, _ = fmt.Fprintln(w, "Please use a build of Coder from GitHub releases:") + _, _ = fmt.Fprintln(w, " https://github.com/coder/coder/releases") + os.Exit(1) +} diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index 847e7a9345..31fbd55565 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -1,3 +1,5 @@ +//go:build !slim + package cli import ( diff --git a/enterprise/cli/server_slim.go b/enterprise/cli/server_slim.go new file mode 100644 index 0000000000..8aabd7c6b0 --- /dev/null +++ b/enterprise/cli/server_slim.go @@ -0,0 +1,27 @@ +//go:build slim + +package cli + +import ( + "context" + "io" + + "github.com/spf13/cobra" + "golang.org/x/xerrors" + + "github.com/coder/coder/cli/deployment" + + agpl "github.com/coder/coder/cli" + agplcoderd "github.com/coder/coder/coderd" +) + +func server() *cobra.Command { + vip := deployment.NewViper() + cmd := agpl.Server(vip, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { + return nil, nil, xerrors.Errorf("slim build does not support `coder server`") + }) + + deployment.AttachFlags(cmd.Flags(), vip, true) + + return cmd +} diff --git a/scripts/build_go.sh b/scripts/build_go.sh index 3a86498901..9057387861 100755 --- a/scripts/build_go.sh +++ b/scripts/build_go.sh @@ -93,12 +93,23 @@ if [[ "$sign_darwin" == 1 ]]; then requiredenvs AC_CERTIFICATE_FILE AC_CERTIFICATE_PASSWORD_FILE fi -build_args=( - -ldflags "-s -w -X 'github.com/coder/coder/buildinfo.tag=$version'" +ldflags=( + -s + -w + -X "'github.com/coder/coder/buildinfo.tag=$version'" ) + if [[ "$slim" == 0 ]]; then build_args+=(-tags embed) +else + build_args+=(-tags slim) fi +if [[ "$agpl" == 1 ]]; then + # We don't use a tag to control AGPL because we don't want code to depend on + # a flag to control AGPL vs. enterprise behavior. + ldflags+=(-X "'github.com/coder/coder/buildinfo.agpl=true'") +fi +build_args+=(-ldflags "${ldflags[*]}") # Compute default output path. if [[ "$output_path" == "" ]]; then