2023-01-17 16:58:00 +00:00
//go:build !slim
2022-03-22 19:17:50 +00:00
package cli
import (
"context"
2023-02-02 17:08:35 +00:00
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
2022-03-24 19:21:05 +00:00
"crypto/tls"
"crypto/x509"
2022-03-24 15:07:33 +00:00
"database/sql"
2023-03-07 19:38:11 +00:00
"encoding/hex"
2022-04-11 23:54:30 +00:00
"errors"
2023-03-07 21:10:01 +00:00
"flag"
2022-03-22 19:17:50 +00:00
"fmt"
2022-05-02 01:31:12 +00:00
"io"
"log"
2023-02-02 17:08:35 +00:00
"math/big"
2022-03-22 19:17:50 +00:00
"net"
"net/http"
2022-05-03 12:48:02 +00:00
"net/http/pprof"
2022-03-22 19:17:50 +00:00
"net/url"
"os"
2022-03-24 15:07:33 +00:00
"os/signal"
2022-06-15 21:02:18 +00:00
"os/user"
2022-03-28 19:57:19 +00:00
"path/filepath"
2022-10-14 18:25:11 +00:00
"regexp"
2023-03-07 21:10:01 +00:00
"sort"
2022-06-15 21:02:18 +00:00
"strconv"
"strings"
2022-07-27 15:21:21 +00:00
"sync"
2022-03-22 19:17:50 +00:00
"time"
2022-08-01 04:05:35 +00:00
"github.com/coreos/go-oidc/v3/oidc"
2022-03-29 00:19:28 +00:00
"github.com/coreos/go-systemd/daemon"
2022-06-15 21:02:18 +00:00
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
2022-04-23 22:58:57 +00:00
"github.com/google/go-github/v43/github"
2022-06-17 05:26:40 +00:00
"github.com/google/uuid"
2022-08-08 15:09:46 +00:00
"github.com/prometheus/client_golang/prometheus"
2022-11-05 00:03:01 +00:00
"github.com/prometheus/client_golang/prometheus/collectors"
2022-05-03 12:48:02 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2022-06-16 15:09:22 +00:00
"github.com/spf13/afero"
2022-09-16 16:43:22 +00:00
"go.opentelemetry.io/otel/trace"
2022-10-18 01:02:25 +00:00
"golang.org/x/mod/semver"
2022-04-23 22:58:57 +00:00
"golang.org/x/oauth2"
xgithub "golang.org/x/oauth2/github"
2022-06-10 18:38:11 +00:00
"golang.org/x/sync/errgroup"
2022-03-29 00:19:28 +00:00
"golang.org/x/xerrors"
"google.golang.org/api/idtoken"
"google.golang.org/api/option"
2023-03-07 21:10:01 +00:00
"gopkg.in/yaml.v3"
2022-09-01 01:09:44 +00:00
"tailscale.com/tailcfg"
2022-05-19 22:43:07 +00:00
2022-03-22 19:17:50 +00:00
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
2023-01-13 02:08:23 +00:00
"cdr.dev/slog/sloggers/slogjson"
"cdr.dev/slog/sloggers/slogstackdriver"
2022-07-14 21:51:44 +00:00
"github.com/coder/coder/buildinfo"
2023-03-07 21:10:01 +00:00
"github.com/coder/coder/cli/clibase"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/cli/cliui"
2022-03-24 15:07:33 +00:00
"github.com/coder/coder/cli/config"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/coderd"
2022-05-11 22:03:02 +00:00
"github.com/coder/coder/coderd/autobuild/executor"
2022-03-25 21:07:45 +00:00
"github.com/coder/coder/coderd/database"
2023-02-03 01:28:55 +00:00
"github.com/coder/coder/coderd/database/dbfake"
2023-03-23 19:09:13 +00:00
"github.com/coder/coder/coderd/database/dbpurge"
2022-09-19 17:39:02 +00:00
"github.com/coder/coder/coderd/database/migrations"
2022-04-14 15:29:40 +00:00
"github.com/coder/coder/coderd/devtunnel"
2022-10-25 00:46:24 +00:00
"github.com/coder/coder/coderd/gitauth"
2022-04-06 00:18:26 +00:00
"github.com/coder/coder/coderd/gitsshkey"
2022-10-14 18:25:11 +00:00
"github.com/coder/coder/coderd/httpapi"
2022-10-23 18:21:49 +00:00
"github.com/coder/coder/coderd/httpmw"
2022-08-08 15:09:46 +00:00
"github.com/coder/coder/coderd/prometheusmetrics"
2022-06-17 05:26:40 +00:00
"github.com/coder/coder/coderd/telemetry"
2022-05-19 22:43:07 +00:00
"github.com/coder/coder/coderd/tracing"
2022-12-01 17:43:28 +00:00
"github.com/coder/coder/coderd/updatecheck"
2023-03-10 05:31:38 +00:00
"github.com/coder/coder/coderd/util/slice"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/codersdk"
2022-07-14 21:51:44 +00:00
"github.com/coder/coder/cryptorand"
"github.com/coder/coder/provisioner/echo"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/provisioner/terraform"
"github.com/coder/coder/provisionerd"
2022-11-10 22:37:33 +00:00
"github.com/coder/coder/provisionerd/proto"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/provisionersdk"
2022-11-10 22:37:33 +00:00
sdkproto "github.com/coder/coder/provisionersdk/proto"
2022-09-01 01:09:44 +00:00
"github.com/coder/coder/tailnet"
2023-03-22 13:13:48 +00:00
"github.com/coder/wgtunnel/tunnelsdk"
2022-03-22 19:17:50 +00:00
)
2023-03-07 21:10:01 +00:00
// ReadGitAuthProvidersFromEnv is provided for compatibility purposes with the
// viper CLI.
// DEPRECATED
func ReadGitAuthProvidersFromEnv ( environ [ ] string ) ( [ ] codersdk . GitAuthConfig , error ) {
// The index numbers must be in-order.
sort . Strings ( environ )
var providers [ ] codersdk . GitAuthConfig
2023-03-23 22:42:20 +00:00
for _ , v := range clibase . ParseEnviron ( environ , "CODER_GITAUTH_" ) {
2023-03-07 21:10:01 +00:00
tokens := strings . SplitN ( v . Name , "_" , 2 )
if len ( tokens ) != 2 {
return nil , xerrors . Errorf ( "invalid env var: %s" , v . Name )
}
providerNum , err := strconv . Atoi ( tokens [ 0 ] )
if err != nil {
return nil , xerrors . Errorf ( "parse number: %s" , v . Name )
}
var provider codersdk . GitAuthConfig
switch {
case len ( providers ) < providerNum :
return nil , xerrors . Errorf (
"provider num %v skipped: %s" ,
len ( providers ) ,
v . Name ,
)
case len ( providers ) == providerNum :
// At the next next provider.
providers = append ( providers , provider )
case len ( providers ) == providerNum + 1 :
// At the current provider.
provider = providers [ providerNum ]
}
key := tokens [ 1 ]
switch key {
case "ID" :
provider . ID = v . Value
case "TYPE" :
provider . Type = v . Value
case "CLIENT_ID" :
provider . ClientID = v . Value
case "CLIENT_SECRET" :
provider . ClientSecret = v . Value
case "AUTH_URL" :
provider . AuthURL = v . Value
case "TOKEN_URL" :
provider . TokenURL = v . Value
case "VALIDATE_URL" :
provider . ValidateURL = v . Value
case "REGEX" :
provider . Regex = v . Value
case "NO_REFRESH" :
b , err := strconv . ParseBool ( key )
if err != nil {
return nil , xerrors . Errorf ( "parse bool: %s" , v . Value )
}
provider . NoRefresh = b
case "SCOPES" :
provider . Scopes = strings . Split ( v . Value , " " )
}
providers [ providerNum ] = provider
}
return providers , nil
}
2022-04-25 04:52:07 +00:00
// nolint:gocyclo
2023-03-23 22:42:20 +00:00
func ( r * RootCmd ) Server ( newAPI func ( context . Context , * coderd . Options ) ( * coderd . API , io . Closer , error ) ) * clibase . Cmd {
var (
cfg = new ( codersdk . DeploymentValues )
opts = cfg . Options ( )
)
serverCmd := & clibase . Cmd {
Use : "server" ,
Short : "Start a Coder server" ,
Options : opts ,
Middleware : clibase . RequireNArgs ( 0 ) ,
Handler : func ( inv * clibase . Invocation ) error {
2023-01-11 16:22:20 +00:00
// Main command context for managing cancellation of running
// services.
2023-03-23 22:42:20 +00:00
ctx , cancel := context . WithCancel ( inv . Context ( ) )
2023-01-11 16:22:20 +00:00
defer cancel ( )
2023-03-07 21:10:01 +00:00
if cfg . WriteConfig {
// TODO: this should output to a file.
2023-03-23 22:42:20 +00:00
n , err := opts . ToYAML ( )
2023-03-07 21:10:01 +00:00
if err != nil {
return xerrors . Errorf ( "generate yaml: %w" , err )
}
2023-03-23 22:42:20 +00:00
enc := yaml . NewEncoder ( inv . Stderr )
2023-03-07 21:10:01 +00:00
err = enc . Encode ( n )
if err != nil {
return xerrors . Errorf ( "encode yaml: %w" , err )
}
err = enc . Close ( )
if err != nil {
return xerrors . Errorf ( "close yaml encoder: %w" , err )
}
return nil
2022-10-21 22:08:23 +00:00
}
2022-12-15 20:09:19 +00:00
2023-03-07 21:10:01 +00:00
// Print deprecation warnings.
2023-03-23 22:42:20 +00:00
for _ , opt := range opts {
2023-03-07 21:10:01 +00:00
if opt . UseInstead == nil {
continue
}
2023-03-09 19:22:21 +00:00
if opt . Value . String ( ) == opt . Default {
continue
}
2023-03-07 21:10:01 +00:00
warnStr := opt . Name + " is deprecated, please use "
for i , use := range opt . UseInstead {
warnStr += use . Name + " "
if i != len ( opt . UseInstead ) - 1 {
warnStr += "and "
}
}
warnStr += "instead.\n"
2023-03-23 22:42:20 +00:00
cliui . Warn ( inv . Stderr ,
warnStr ,
2023-03-07 21:10:01 +00:00
)
}
go dumpHandler ( ctx )
2022-12-15 20:09:19 +00:00
// Validate bind addresses.
2023-03-07 21:10:01 +00:00
if cfg . Address . String ( ) != "" {
if cfg . TLS . Enable {
cfg . HTTPAddress = ""
cfg . TLS . Address = cfg . Address
2022-12-15 20:09:19 +00:00
} else {
2023-03-07 21:10:01 +00:00
_ = cfg . HTTPAddress . Set ( cfg . Address . String ( ) )
cfg . TLS . Address . Host = ""
cfg . TLS . Address . Port = ""
2022-12-15 20:09:19 +00:00
}
}
2023-03-07 21:10:01 +00:00
if cfg . TLS . Enable && cfg . TLS . Address . String ( ) == "" {
2022-12-15 20:09:19 +00:00
return xerrors . Errorf ( "TLS address must be set if TLS is enabled" )
}
2023-03-07 21:10:01 +00:00
if ! cfg . TLS . Enable && cfg . HTTPAddress . String ( ) == "" {
2023-01-18 23:23:55 +00:00
return xerrors . Errorf ( "TLS is disabled. Enable with --tls-enable or specify a HTTP address" )
2022-12-15 20:09:19 +00:00
}
2023-03-10 03:37:19 +00:00
if cfg . AccessURL . String ( ) != "" &&
! ( cfg . AccessURL . Scheme == "http" || cfg . AccessURL . Scheme == "https" ) {
2023-03-07 21:10:01 +00:00
return xerrors . Errorf ( "access-url must include a scheme (e.g. 'http://' or 'https://)" )
}
2023-01-05 18:05:20 +00:00
// Disable rate limits if the `--dangerous-disable-rate-limits` flag
// was specified.
loginRateLimit := 60
filesRateLimit := 12
2023-03-07 21:10:01 +00:00
if cfg . RateLimit . DisableAll {
cfg . RateLimit . API = - 1
2023-01-05 18:05:20 +00:00
loginRateLimit = - 1
filesRateLimit = - 1
}
2023-03-23 22:42:20 +00:00
printLogo ( inv )
logger , logCloser , err := buildLogger ( inv , cfg )
2023-01-13 02:08:23 +00:00
if err != nil {
return xerrors . Errorf ( "make logger: %w" , err )
2022-11-09 18:58:23 +00:00
}
2023-01-13 02:08:23 +00:00
defer logCloser ( )
2022-05-03 21:13:30 +00:00
2023-03-07 21:10:01 +00:00
// This line is helpful in tests.
logger . Debug ( ctx , "started debug logging" )
logger . Sync ( )
2022-08-18 13:25:32 +00:00
// Register signals early on so that graceful shutdown can't
// be interrupted by additional signals. Note that we avoid
// shadowing cancel() (from above) here because notifyStop()
// restores default behavior for the signals. This protects
2022-10-25 00:46:24 +00:00
// the shutdown sequence from abruptly terminating things
2022-08-18 13:25:32 +00:00
// like: database migrations, provisioner work, workspace
// cleanup in dev-mode, etc.
//
// To get out of a graceful shutdown, the user can send
// SIGQUIT with ctrl+\ or SIGKILL with `kill -9`.
2022-11-16 22:34:06 +00:00
notifyCtx , notifyStop := signal . NotifyContext ( ctx , InterruptSignals ... )
2022-08-18 13:25:32 +00:00
defer notifyStop ( )
2022-12-06 17:05:14 +00:00
// Ensure we have a unique cache directory for this process.
2023-03-07 21:10:01 +00:00
cacheDir := filepath . Join ( cfg . CacheDir . String ( ) , uuid . NewString ( ) )
2022-12-06 17:05:14 +00:00
err = os . MkdirAll ( cacheDir , 0 o700 )
if err != nil {
return xerrors . Errorf ( "create cache directory: %w" , err )
}
defer os . RemoveAll ( cacheDir )
2022-07-27 15:21:21 +00:00
// Clean up idle connections at the end, e.g.
// embedded-postgres can leave an idle connection
// which is caught by goleaks.
defer http . DefaultClient . CloseIdleConnections ( )
2022-05-20 15:51:06 +00:00
var (
2022-09-16 16:43:22 +00:00
tracerProvider trace . TracerProvider
2022-05-20 15:51:06 +00:00
sqlDriver = "postgres"
)
2022-09-16 16:43:22 +00:00
2022-09-22 16:53:08 +00:00
// Coder tracing should be disabled if telemetry is disabled unless
// --telemetry-trace was explicitly provided.
2023-03-07 21:10:01 +00:00
shouldCoderTrace := cfg . Telemetry . Enable . Value ( ) && ! isTest ( )
2022-09-22 16:53:08 +00:00
// Only override if telemetryTraceEnable was specifically set.
// By default we want it to be controlled by telemetryEnable.
2023-03-23 22:42:20 +00:00
if inv . ParsedFlags ( ) . Changed ( "telemetry-trace" ) {
2023-03-07 21:10:01 +00:00
shouldCoderTrace = cfg . Telemetry . Trace . Value ( )
2022-09-22 16:53:08 +00:00
}
2023-03-07 21:10:01 +00:00
if cfg . Trace . Enable . Value ( ) || shouldCoderTrace || cfg . Trace . HoneycombAPIKey != "" {
2022-09-20 04:35:18 +00:00
sdkTracerProvider , closeTracing , err := tracing . TracerProvider ( ctx , "coderd" , tracing . TracerOpts {
2023-03-07 21:10:01 +00:00
Default : cfg . Trace . Enable . Value ( ) ,
2022-11-08 22:10:48 +00:00
Coder : shouldCoderTrace ,
2023-03-07 21:10:01 +00:00
Honeycomb : cfg . Trace . HoneycombAPIKey . String ( ) ,
2022-09-16 16:43:22 +00:00
} )
2022-05-19 22:43:07 +00:00
if err != nil {
2022-09-16 16:43:22 +00:00
logger . Warn ( ctx , "start telemetry exporter" , slog . Error ( err ) )
2022-05-19 22:43:07 +00:00
} else {
2022-07-27 15:21:21 +00:00
// allow time for traces to flush even if command context is canceled
2022-05-19 22:43:07 +00:00
defer func ( ) {
2022-09-20 04:35:18 +00:00
_ = shutdownWithTimeout ( closeTracing , 5 * time . Second )
2022-05-19 22:43:07 +00:00
} ( )
2022-05-20 15:51:06 +00:00
2022-09-16 16:43:22 +00:00
d , err := tracing . PostgresDriver ( sdkTracerProvider , "coderd.database" )
2022-05-20 15:51:06 +00:00
if err != nil {
2022-09-16 16:43:22 +00:00
logger . Warn ( ctx , "start postgres tracing driver" , slog . Error ( err ) )
2022-05-20 15:51:06 +00:00
} else {
sqlDriver = d
}
2022-09-16 16:43:22 +00:00
tracerProvider = sdkTracerProvider
2022-05-19 22:43:07 +00:00
}
2022-03-28 22:11:52 +00:00
}
2022-03-24 15:07:33 +00:00
2023-03-23 22:42:20 +00:00
config := r . createConfig ( )
2022-06-17 05:26:40 +00:00
builtinPostgres := false
2022-06-15 21:02:18 +00:00
// Only use built-in if PostgreSQL URL isn't specified!
2023-03-07 21:10:01 +00:00
if ! cfg . InMemoryDatabase && cfg . PostgresURL == "" {
2022-06-15 21:02:18 +00:00
var closeFunc func ( ) error
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "Using built-in PostgreSQL (%s)" , config . PostgresPath ( ) )
2023-03-07 21:10:01 +00:00
pgURL , closeFunc , err := startBuiltinPostgres ( ctx , config , logger )
if err != nil {
return err
}
err = cfg . PostgresURL . Set ( pgURL )
2022-06-15 21:02:18 +00:00
if err != nil {
return err
}
2022-06-17 05:26:40 +00:00
builtinPostgres = true
2022-06-15 21:02:18 +00:00
defer func ( ) {
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "Stopping built-in PostgreSQL..." )
2022-06-15 21:02:18 +00:00
// Gracefully shut PostgreSQL down!
2022-11-29 13:45:14 +00:00
if err := closeFunc ( ) ; err != nil {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "Failed to stop built-in PostgreSQL: %v" , err )
2022-11-29 13:45:14 +00:00
} else {
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "Stopped built-in PostgreSQL" )
2022-11-29 13:45:14 +00:00
}
2022-06-15 21:02:18 +00:00
} ( )
}
2022-12-15 20:09:19 +00:00
var (
httpListener net . Listener
httpURL * url . URL
)
2023-03-07 21:10:01 +00:00
if cfg . HTTPAddress . String ( ) != "" {
httpListener , err = net . Listen ( "tcp" , cfg . HTTPAddress . String ( ) )
2022-12-15 20:09:19 +00:00
if err != nil {
2023-03-23 22:42:20 +00:00
return err
2022-12-15 20:09:19 +00:00
}
defer httpListener . Close ( )
2023-01-09 19:59:23 +00:00
listenAddrStr := httpListener . Addr ( ) . String ( )
// For some reason if 0.0.0.0:x is provided as the http address,
// httpListener.Addr().String() likes to return it as an ipv6
// address (i.e. [::]:x). If the input ip is 0.0.0.0, try to
// coerce the output back to ipv4 to make it less confusing.
2023-03-07 21:10:01 +00:00
if strings . Contains ( cfg . HTTPAddress . String ( ) , "0.0.0.0" ) {
2023-01-09 19:59:23 +00:00
listenAddrStr = strings . ReplaceAll ( listenAddrStr , "[::]" , "0.0.0.0" )
}
// We want to print out the address the user supplied, not the
// loopback device.
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "Started HTTP listener at %s\n" , ( & url . URL { Scheme : "http" , Host : listenAddrStr } ) . String ( ) )
2023-01-09 19:59:23 +00:00
// Set the http URL we want to use when connecting to ourselves.
2022-12-15 20:09:19 +00:00
tcpAddr , tcpAddrValid := httpListener . Addr ( ) . ( * net . TCPAddr )
if ! tcpAddrValid {
return xerrors . Errorf ( "invalid TCP address type %T" , httpListener . Addr ( ) )
}
if tcpAddr . IP . IsUnspecified ( ) {
tcpAddr . IP = net . IPv4 ( 127 , 0 , 0 , 1 )
}
httpURL = & url . URL {
Scheme : "http" ,
Host : tcpAddr . String ( ) ,
}
2022-03-22 19:17:50 +00:00
}
2022-03-24 19:21:05 +00:00
2022-12-15 20:09:19 +00:00
var (
tlsConfig * tls . Config
httpsListener net . Listener
httpsURL * url . URL
)
2023-03-07 21:10:01 +00:00
if cfg . TLS . Enable {
if cfg . TLS . Address . String ( ) == "" {
2022-12-15 20:09:19 +00:00
return xerrors . New ( "tls address must be set if tls is enabled" )
}
2023-02-02 17:08:35 +00:00
// DEPRECATED: This redirect used to default to true.
// It made more sense to have the redirect be opt-in.
2023-03-23 22:42:20 +00:00
if inv . Environ . Get ( "CODER_TLS_REDIRECT_HTTP" ) == "true" || inv . ParsedFlags ( ) . Changed ( "tls-redirect-http-to-https" ) {
cliui . Warn ( inv . Stderr , "--tls-redirect-http-to-https is deprecated, please use --redirect-to-access-url instead" )
2023-03-07 21:10:01 +00:00
cfg . RedirectToAccessURL = cfg . TLS . RedirectHTTP
2023-02-02 17:08:35 +00:00
}
2022-10-17 13:43:30 +00:00
tlsConfig , err = configureTLS (
2023-03-07 21:10:01 +00:00
cfg . TLS . MinVersion . String ( ) ,
cfg . TLS . ClientAuth . String ( ) ,
cfg . TLS . CertFiles ,
cfg . TLS . KeyFiles ,
cfg . TLS . ClientCAFile . String ( ) ,
2022-10-10 19:04:15 +00:00
)
2022-03-24 19:21:05 +00:00
if err != nil {
return xerrors . Errorf ( "configure tls: %w" , err )
}
2023-03-07 21:10:01 +00:00
httpsListenerInner , err := net . Listen ( "tcp" , cfg . TLS . Address . String ( ) )
2022-12-15 20:09:19 +00:00
if err != nil {
2023-03-23 22:42:20 +00:00
return err
2022-12-15 20:09:19 +00:00
}
defer httpsListenerInner . Close ( )
httpsListener = tls . NewListener ( httpsListenerInner , tlsConfig )
defer httpsListener . Close ( )
2023-01-09 19:59:23 +00:00
listenAddrStr := httpsListener . Addr ( ) . String ( )
// For some reason if 0.0.0.0:x is provided as the https
// address, httpsListener.Addr().String() likes to return it as
// an ipv6 address (i.e. [::]:x). If the input ip is 0.0.0.0,
// try to coerce the output back to ipv4 to make it less
// confusing.
2023-03-07 21:10:01 +00:00
if strings . Contains ( cfg . HTTPAddress . String ( ) , "0.0.0.0" ) {
2023-01-09 19:59:23 +00:00
listenAddrStr = strings . ReplaceAll ( listenAddrStr , "[::]" , "0.0.0.0" )
}
// We want to print out the address the user supplied, not the
// loopback device.
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "Started TLS/HTTPS listener at %s\n" , ( & url . URL { Scheme : "https" , Host : listenAddrStr } ) . String ( ) )
2023-01-09 19:59:23 +00:00
// Set the https URL we want to use when connecting to
// ourselves.
2022-12-15 20:09:19 +00:00
tcpAddr , tcpAddrValid := httpsListener . Addr ( ) . ( * net . TCPAddr )
if ! tcpAddrValid {
return xerrors . Errorf ( "invalid TCP address type %T" , httpsListener . Addr ( ) )
}
if tcpAddr . IP . IsUnspecified ( ) {
tcpAddr . IP = net . IPv4 ( 127 , 0 , 0 , 1 )
}
httpsURL = & url . URL {
Scheme : "https" ,
Host : tcpAddr . String ( ) ,
}
}
// Sanity check that at least one listener was started.
if httpListener == nil && httpsListener == nil {
return xerrors . New ( "must listen on at least one address" )
}
// Prefer HTTP because it's less prone to TLS errors over localhost.
localURL := httpsURL
if httpURL != nil {
localURL = httpURL
2022-03-24 19:21:05 +00:00
}
2022-12-14 14:44:29 +00:00
ctx , httpClient , err := configureHTTPClient (
ctx ,
2023-03-07 21:10:01 +00:00
cfg . TLS . ClientCertFile . String ( ) ,
cfg . TLS . ClientKeyFile . String ( ) ,
cfg . TLS . ClientCAFile . String ( ) ,
2022-12-14 14:44:29 +00:00
)
if err != nil {
return xerrors . Errorf ( "configure http client: %w" , err )
}
2022-10-20 22:09:44 +00:00
// If the access URL is empty, we attempt to run a reverse-proxy
// tunnel to make the initial setup really simple.
2023-03-22 13:13:48 +00:00
var (
tunnel * tunnelsdk . Tunnel
tunnelDone <- chan struct { } = make ( chan struct { } , 1 )
)
2023-03-07 21:10:01 +00:00
if cfg . AccessURL . String ( ) == "" {
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stderr , "Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL\n" )
2023-03-22 13:13:48 +00:00
tunnel , err = devtunnel . New ( ctx , logger . Named ( "devtunnel" ) , cfg . WgtunnelHost . String ( ) )
2022-06-15 21:02:18 +00:00
if err != nil {
return xerrors . Errorf ( "create tunnel: %w" , err )
2022-03-24 15:07:33 +00:00
}
2023-03-22 13:13:48 +00:00
defer tunnel . Close ( )
tunnelDone = tunnel . Wait ( )
cfg . AccessURL = clibase . URL ( * tunnel . URL )
2022-10-20 22:09:44 +00:00
2023-03-07 21:10:01 +00:00
if cfg . WildcardAccessURL . String ( ) == "" {
2022-10-20 22:09:44 +00:00
// Suffixed wildcard access URL.
2023-03-22 13:13:48 +00:00
u , err := url . Parse ( fmt . Sprintf ( "*--%s" , tunnel . URL . Hostname ( ) ) )
2023-03-07 21:10:01 +00:00
if err != nil {
return xerrors . Errorf ( "parse wildcard url: %w" , err )
}
cfg . WildcardAccessURL = clibase . URL ( * u )
2022-10-20 22:09:44 +00:00
}
2022-03-22 19:17:50 +00:00
}
2022-04-14 15:29:40 +00:00
2023-03-07 21:10:01 +00:00
_ , accessURLPortRaw , _ := net . SplitHostPort ( cfg . AccessURL . Host )
2022-09-01 01:09:44 +00:00
if accessURLPortRaw == "" {
accessURLPortRaw = "80"
2023-03-07 21:10:01 +00:00
if cfg . AccessURL . Scheme == "https" {
2022-09-01 01:09:44 +00:00
accessURLPortRaw = "443"
}
}
2023-03-07 21:10:01 +00:00
2022-09-01 01:09:44 +00:00
accessURLPort , err := strconv . Atoi ( accessURLPortRaw )
if err != nil {
return xerrors . Errorf ( "parse access URL port: %w" , err )
}
2022-07-31 22:49:25 +00:00
2022-06-10 18:35:51 +00:00
// Warn the user if the access URL appears to be a loopback address.
2023-03-07 21:10:01 +00:00
isLocal , err := isLocalURL ( ctx , cfg . AccessURL . Value ( ) )
2022-06-10 18:35:51 +00:00
if isLocal || err != nil {
2022-06-15 21:02:18 +00:00
reason := "could not be resolved"
2022-06-10 18:35:51 +00:00
if isLocal {
2022-06-15 21:02:18 +00:00
reason = "isn't externally reachable"
2022-06-10 18:35:51 +00:00
}
2023-03-23 22:42:20 +00:00
cliui . Warnf (
inv . Stderr ,
"The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL by not specifying an access URL.\n" ,
cliui . Styles . Field . Render ( cfg . AccessURL . String ( ) ) , reason ,
2023-03-07 21:10:01 +00:00
)
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
2022-10-07 13:05:56 +00:00
// A newline is added before for visibility in terminal output.
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "\nView the Web UI: %s\n" , cfg . AccessURL . String ( ) )
2022-04-06 00:18:26 +00:00
2022-06-15 21:02:18 +00:00
// Used for zero-trust instance identity with Google Cloud.
2022-07-27 15:21:21 +00:00
googleTokenValidator , err := idtoken . NewValidator ( ctx , option . WithoutAuthentication ( ) )
2022-06-15 21:02:18 +00:00
if err != nil {
return err
}
2023-03-07 21:10:01 +00:00
sshKeygenAlgorithm , err := gitsshkey . ParseAlgorithm ( cfg . SSHKeygenAlgorithm . String ( ) )
2022-04-06 00:18:26 +00:00
if err != nil {
2023-03-07 21:10:01 +00:00
return xerrors . Errorf ( "parse ssh keygen algorithm %s: %w" , cfg . SSHKeygenAlgorithm , err )
2022-04-06 00:18:26 +00:00
}
2022-09-02 23:47:25 +00:00
defaultRegion := & tailcfg . DERPRegion {
2022-09-26 17:56:04 +00:00
EmbeddedRelay : true ,
2023-03-07 21:10:01 +00:00
RegionID : int ( cfg . DERP . Server . RegionID . Value ( ) ) ,
RegionCode : cfg . DERP . Server . RegionCode . String ( ) ,
RegionName : cfg . DERP . Server . RegionName . String ( ) ,
2022-09-01 01:09:44 +00:00
Nodes : [ ] * tailcfg . DERPNode { {
2023-03-07 21:10:01 +00:00
Name : fmt . Sprintf ( "%db" , cfg . DERP . Server . RegionID ) ,
RegionID : int ( cfg . DERP . Server . RegionID . Value ( ) ) ,
2023-03-10 15:58:31 +00:00
HostName : cfg . AccessURL . Value ( ) . Hostname ( ) ,
2022-09-01 01:09:44 +00:00
DERPPort : accessURLPort ,
STUNPort : - 1 ,
2023-03-07 21:10:01 +00:00
ForceHTTP : cfg . AccessURL . Scheme == "http" ,
2022-09-01 01:09:44 +00:00
} } ,
2022-09-02 23:47:25 +00:00
}
2023-03-07 21:10:01 +00:00
if ! cfg . DERP . Server . Enable {
2022-09-02 23:47:25 +00:00
defaultRegion = nil
}
2023-03-07 21:10:01 +00:00
derpMap , err := tailnet . NewDERPMap (
ctx , defaultRegion , cfg . DERP . Server . STUNAddresses ,
cfg . DERP . Config . URL . String ( ) , cfg . DERP . Config . Path . String ( ) ,
)
2022-09-01 01:09:44 +00:00
if err != nil {
return xerrors . Errorf ( "create derp map: %w" , err )
}
2023-03-07 21:10:01 +00:00
appHostname := cfg . WildcardAccessURL . String ( )
2022-10-14 18:25:11 +00:00
var appHostnameRegex * regexp . Regexp
if appHostname != "" {
appHostnameRegex , err = httpapi . CompileHostnamePattern ( appHostname )
if err != nil {
return xerrors . Errorf ( "parse wildcard access URL %q: %w" , appHostname , err )
}
}
2022-09-22 22:30:32 +00:00
2023-03-07 21:10:01 +00:00
gitAuthEnv , err := ReadGitAuthProvidersFromEnv ( os . Environ ( ) )
if err != nil {
return xerrors . Errorf ( "read git auth providers from env: %w" , err )
}
2023-03-09 20:27:54 +00:00
cfg . GitAuthProviders . Value = append ( cfg . GitAuthProviders . Value , gitAuthEnv ... )
2023-03-07 21:10:01 +00:00
gitAuthConfigs , err := gitauth . ConvertConfig (
2023-03-09 20:27:54 +00:00
cfg . GitAuthProviders . Value ,
2023-03-07 21:10:01 +00:00
cfg . AccessURL . Value ( ) ,
)
2022-10-25 00:46:24 +00:00
if err != nil {
2023-03-07 21:10:01 +00:00
return xerrors . Errorf ( "convert git auth config: %w" , err )
}
for _ , c := range gitAuthConfigs {
logger . Debug (
ctx , "loaded git auth config" ,
slog . F ( "id" , c . ID ) ,
)
2022-10-25 00:46:24 +00:00
}
2023-03-07 21:10:01 +00:00
realIPConfig , err := httpmw . ParseRealIPConfig ( cfg . ProxyTrustedHeaders , cfg . ProxyTrustedOrigins )
2022-10-23 18:21:49 +00:00
if err != nil {
return xerrors . Errorf ( "parse real ip config: %w" , err )
}
2023-03-16 18:03:37 +00:00
configSSHOptions , err := cfg . SSHConfig . ParseOptions ( )
if err != nil {
return xerrors . Errorf ( "parse ssh config options %q: %w" , cfg . SSHConfig . SSHConfigOptions . String ( ) , err )
}
2022-03-24 15:07:33 +00:00
options := & coderd . Options {
2023-03-07 21:10:01 +00:00
AccessURL : cfg . AccessURL . Value ( ) ,
2022-09-22 22:30:32 +00:00
AppHostname : appHostname ,
2022-10-14 18:25:11 +00:00
AppHostnameRegex : appHostnameRegex ,
2022-09-01 19:58:23 +00:00
Logger : logger . Named ( "coderd" ) ,
2023-02-03 01:28:55 +00:00
Database : dbfake . New ( ) ,
2022-09-01 19:58:23 +00:00
DERPMap : derpMap ,
Pubsub : database . NewPubsubInMemory ( ) ,
2022-12-06 17:05:14 +00:00
CacheDir : cacheDir ,
2022-09-01 19:58:23 +00:00
GoogleTokenValidator : googleTokenValidator ,
2022-10-25 00:46:24 +00:00
GitAuthConfigs : gitAuthConfigs ,
2022-10-23 18:21:49 +00:00
RealIPConfig : realIPConfig ,
2023-03-07 21:10:01 +00:00
SecureAuthCookie : cfg . SecureAuthCookie . Value ( ) ,
2022-09-01 19:58:23 +00:00
SSHKeygenAlgorithm : sshKeygenAlgorithm ,
TracerProvider : tracerProvider ,
Telemetry : telemetry . NewNoop ( ) ,
2023-03-07 21:10:01 +00:00
MetricsCacheRefreshInterval : cfg . MetricsCacheRefreshInterval . Value ( ) ,
AgentStatsRefreshInterval : cfg . AgentStatRefreshInterval . Value ( ) ,
DeploymentValues : cfg ,
2022-11-05 00:03:01 +00:00
PrometheusRegistry : prometheus . NewRegistry ( ) ,
2023-03-07 21:10:01 +00:00
APIRateLimit : int ( cfg . RateLimit . API . Value ( ) ) ,
2023-01-05 18:05:20 +00:00
LoginRateLimit : loginRateLimit ,
FilesRateLimit : filesRateLimit ,
2022-12-14 14:44:29 +00:00
HTTPClient : httpClient ,
2023-03-16 18:03:37 +00:00
SSHConfig : codersdk . SSHConfigResponse {
HostnamePrefix : cfg . SSHConfig . DeploymentName . String ( ) ,
SSHConfigOptions : configSSHOptions ,
} ,
2022-10-10 19:04:15 +00:00
}
2022-10-17 13:43:30 +00:00
if tlsConfig != nil {
options . TLSCertificates = tlsConfig . Certificates
}
2022-10-10 19:04:15 +00:00
2023-03-07 21:10:01 +00:00
if cfg . StrictTransportSecurity > 0 {
options . StrictTransportSecurityCfg , err = httpmw . HSTSConfigOptions (
int ( cfg . StrictTransportSecurity . Value ( ) ) , cfg . StrictTransportSecurityOptions ,
)
2023-02-10 16:52:49 +00:00
if err != nil {
2023-03-07 21:10:01 +00:00
return xerrors . Errorf ( "coderd: setting hsts header failed (options: %v): %w" , cfg . StrictTransportSecurityOptions , err )
2023-02-10 16:52:49 +00:00
}
}
2023-03-07 21:10:01 +00:00
if cfg . UpdateCheck {
2022-12-01 17:43:28 +00:00
options . UpdateCheckOptions = & updatecheck . Options {
// Avoid spamming GitHub API checking for updates.
Interval : 24 * time . Hour ,
// Inform server admins of new versions.
Notify : func ( r updatecheck . Result ) {
if semver . Compare ( r . Version , buildinfo . Version ( ) ) > 0 {
options . Logger . Info (
context . Background ( ) ,
"new version of coder available" ,
slog . F ( "new_version" , r . Version ) ,
slog . F ( "url" , r . URL ) ,
slog . F ( "upgrade_instructions" , "https://coder.com/docs/coder-oss/latest/admin/upgrade" ) ,
)
}
} ,
}
}
2023-03-07 21:10:01 +00:00
if cfg . OAuth2 . Github . ClientSecret != "" {
options . GithubOAuth2Config , err = configureGithubOAuth2 ( cfg . AccessURL . Value ( ) ,
cfg . OAuth2 . Github . ClientID . String ( ) ,
cfg . OAuth2 . Github . ClientSecret . String ( ) ,
cfg . OAuth2 . Github . AllowSignups . Value ( ) ,
cfg . OAuth2 . Github . AllowEveryone . Value ( ) ,
cfg . OAuth2 . Github . AllowedOrgs ,
cfg . OAuth2 . Github . AllowedTeams ,
cfg . OAuth2 . Github . EnterpriseBaseURL . String ( ) ,
2022-10-10 19:04:15 +00:00
)
2022-04-23 22:58:57 +00:00
if err != nil {
return xerrors . Errorf ( "configure github oauth2: %w" , err )
}
}
2023-03-07 21:10:01 +00:00
if cfg . OIDC . ClientSecret != "" {
if cfg . OIDC . ClientID == "" {
2022-08-01 04:05:35 +00:00
return xerrors . Errorf ( "OIDC client ID be set!" )
}
2023-03-07 21:10:01 +00:00
if cfg . OIDC . IssuerURL == "" {
2022-08-01 04:05:35 +00:00
return xerrors . Errorf ( "OIDC issuer URL must be set!" )
}
2023-03-07 21:10:01 +00:00
if cfg . OIDC . IgnoreEmailVerified {
2022-11-25 10:10:09 +00:00
logger . Warn ( ctx , "coder will not check email_verified for OIDC logins" )
}
2023-03-07 21:10:01 +00:00
oidcProvider , err := oidc . NewProvider (
ctx , cfg . OIDC . IssuerURL . String ( ) ,
)
2022-08-01 04:05:35 +00:00
if err != nil {
return xerrors . Errorf ( "configure oidc provider: %w" , err )
}
2023-03-07 21:10:01 +00:00
redirectURL , err := cfg . AccessURL . Value ( ) . Parse ( "/api/v2/users/oidc/callback" )
2022-08-01 04:05:35 +00:00
if err != nil {
return xerrors . Errorf ( "parse oidc oauth callback url: %w" , err )
}
2023-03-10 05:31:38 +00:00
// If the scopes contain 'groups', we enable group support.
// Do not override any custom value set by the user.
if slice . Contains ( cfg . OIDC . Scopes , "groups" ) && cfg . OIDC . GroupField == "" {
cfg . OIDC . GroupField = "groups"
}
2022-08-01 04:05:35 +00:00
options . OIDCConfig = & coderd . OIDCConfig {
OAuth2Config : & oauth2 . Config {
2023-03-07 21:10:01 +00:00
ClientID : cfg . OIDC . ClientID . String ( ) ,
ClientSecret : cfg . OIDC . ClientSecret . String ( ) ,
2022-08-01 04:05:35 +00:00
RedirectURL : redirectURL . String ( ) ,
Endpoint : oidcProvider . Endpoint ( ) ,
2023-03-07 21:10:01 +00:00
Scopes : cfg . OIDC . Scopes ,
2022-08-01 04:05:35 +00:00
} ,
2023-01-16 22:06:39 +00:00
Provider : oidcProvider ,
2022-08-01 04:05:35 +00:00
Verifier : oidcProvider . Verifier ( & oidc . Config {
2023-03-07 21:10:01 +00:00
ClientID : cfg . OIDC . ClientID . String ( ) ,
2022-08-01 04:05:35 +00:00
} ) ,
2023-03-07 21:10:01 +00:00
EmailDomain : cfg . OIDC . EmailDomain ,
AllowSignups : cfg . OIDC . AllowSignups . Value ( ) ,
UsernameField : cfg . OIDC . UsernameField . String ( ) ,
2023-03-10 05:31:38 +00:00
GroupField : cfg . OIDC . GroupField . String ( ) ,
2023-03-21 19:25:45 +00:00
GroupMapping : cfg . OIDC . GroupMapping . Value ,
2023-03-07 21:10:01 +00:00
SignInText : cfg . OIDC . SignInText . String ( ) ,
IconURL : cfg . OIDC . IconURL . String ( ) ,
IgnoreEmailVerified : cfg . OIDC . IgnoreEmailVerified . Value ( ) ,
2022-08-01 04:05:35 +00:00
}
}
2023-03-07 21:10:01 +00:00
if cfg . InMemoryDatabase {
2023-02-03 01:28:55 +00:00
options . Database = dbfake . New ( )
2022-06-15 21:02:18 +00:00
options . Pubsub = database . NewPubsubInMemory ( )
} else {
2023-03-07 21:10:01 +00:00
sqlDB , err := connectToPostgres ( ctx , logger , sqlDriver , cfg . PostgresURL . String ( ) )
2022-03-24 15:07:33 +00:00
if err != nil {
2023-02-06 14:58:21 +00:00
return xerrors . Errorf ( "connect to postgres: %w" , err )
2022-03-24 15:07:33 +00:00
}
2023-02-06 14:58:21 +00:00
defer func ( ) {
_ = sqlDB . Close ( )
} ( )
2022-11-09 16:25:25 +00:00
2022-03-24 15:07:33 +00:00
options . Database = database . New ( sqlDB )
2023-03-07 21:10:01 +00:00
options . Pubsub , err = database . NewPubsub ( ctx , sqlDB , cfg . PostgresURL . String ( ) )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "create pubsub: %w" , err )
}
2022-07-27 15:21:21 +00:00
defer options . Pubsub . Close ( )
2022-03-24 15:07:33 +00:00
}
2023-03-07 19:38:11 +00:00
var deploymentID string
err = options . Database . InTx ( func ( tx database . Store ) error {
// This will block until the lock is acquired, and will be
// automatically released when the transaction ends.
err := tx . AcquireLock ( ctx , database . LockIDDeploymentSetup )
2022-06-17 05:26:40 +00:00
if err != nil {
2023-03-07 19:38:11 +00:00
return xerrors . Errorf ( "acquire lock: %w" , err )
}
deploymentID , err = tx . GetDeploymentID ( ctx )
if err != nil && ! xerrors . Is ( err , sql . ErrNoRows ) {
return xerrors . Errorf ( "get deployment id: %w" , err )
}
if deploymentID == "" {
deploymentID = uuid . NewString ( )
err = tx . InsertDeploymentID ( ctx , deploymentID )
if err != nil {
return xerrors . Errorf ( "set deployment id: %w" , err )
}
}
// Read the app signing key from the DB. We store it hex
// encoded since the config table uses strings for the value and
// we don't want to deal with automatic encoding issues.
appSigningKeyStr , err := tx . GetAppSigningKey ( ctx )
if err != nil && ! xerrors . Is ( err , sql . ErrNoRows ) {
return xerrors . Errorf ( "get app signing key: %w" , err )
2022-06-17 05:26:40 +00:00
}
2023-03-07 19:38:11 +00:00
if appSigningKeyStr == "" {
// Generate 64 byte secure random string.
b := make ( [ ] byte , 64 )
_ , err := rand . Read ( b )
if err != nil {
return xerrors . Errorf ( "generate fresh app signing key: %w" , err )
}
appSigningKeyStr = hex . EncodeToString ( b )
err = tx . InsertAppSigningKey ( ctx , appSigningKeyStr )
if err != nil {
return xerrors . Errorf ( "insert freshly generated app signing key to database: %w" , err )
}
}
appSigningKey , err := hex . DecodeString ( appSigningKeyStr )
if err != nil {
return xerrors . Errorf ( "decode app signing key from database as hex: %w" , err )
}
if len ( appSigningKey ) != 64 {
return xerrors . Errorf ( "app signing key must be 64 bytes, key in database is %d bytes" , len ( appSigningKey ) )
}
options . AppSigningKey = appSigningKey
return nil
} , nil )
if err != nil {
return err
2022-06-17 05:26:40 +00:00
}
2023-03-07 21:10:01 +00:00
if cfg . Telemetry . Enable {
2022-11-14 16:11:08 +00:00
gitAuth := make ( [ ] telemetry . GitAuth , 0 )
2023-03-07 21:10:01 +00:00
// TODO:
var gitAuthConfigs [ ] codersdk . GitAuthConfig
2022-11-14 16:11:08 +00:00
for _ , cfg := range gitAuthConfigs {
gitAuth = append ( gitAuth , telemetry . GitAuth {
2023-03-07 21:10:01 +00:00
Type : cfg . Type ,
2022-11-14 16:11:08 +00:00
} )
}
2022-06-17 05:26:40 +00:00
options . Telemetry , err = telemetry . New ( telemetry . Options {
2022-11-14 16:11:08 +00:00
BuiltinPostgres : builtinPostgres ,
DeploymentID : deploymentID ,
Database : options . Database ,
Logger : logger . Named ( "telemetry" ) ,
2023-03-07 21:10:01 +00:00
URL : cfg . Telemetry . URL . Value ( ) ,
Wildcard : cfg . WildcardAccessURL . String ( ) != "" ,
DERPServerRelayURL : cfg . DERP . Server . RelayURL . String ( ) ,
2022-11-14 16:11:08 +00:00
GitAuth : gitAuth ,
2023-03-07 21:10:01 +00:00
GitHubOAuth : cfg . OAuth2 . Github . ClientID != "" ,
OIDCAuth : cfg . OIDC . ClientID != "" ,
OIDCIssuerURL : cfg . OIDC . IssuerURL . String ( ) ,
Prometheus : cfg . Prometheus . Enable . Value ( ) ,
STUN : len ( cfg . DERP . Server . STUNAddresses ) != 0 ,
2022-11-14 16:11:08 +00:00
Tunnel : tunnel != nil ,
2022-06-17 05:26:40 +00:00
} )
if err != nil {
return xerrors . Errorf ( "create telemetry reporter: %w" , err )
}
defer options . Telemetry . Close ( )
}
2022-08-08 15:09:46 +00:00
// This prevents the pprof import from being accidentally deleted.
_ = pprof . Handler
2023-03-07 21:10:01 +00:00
if cfg . Pprof . Enable {
2022-08-08 15:09:46 +00:00
//nolint:revive
2023-03-07 21:10:01 +00:00
defer serveHandler ( ctx , logger , nil , cfg . Pprof . Address . String ( ) , "pprof" ) ( )
2022-08-08 15:09:46 +00:00
}
2023-03-07 21:10:01 +00:00
if cfg . Prometheus . Enable {
2022-11-05 00:03:01 +00:00
options . PrometheusRegistry . MustRegister ( collectors . NewGoCollector ( ) )
options . PrometheusRegistry . MustRegister ( collectors . NewProcessCollector ( collectors . ProcessCollectorOpts { } ) )
closeUsersFunc , err := prometheusmetrics . ActiveUsers ( ctx , options . PrometheusRegistry , options . Database , 0 )
2022-08-08 15:09:46 +00:00
if err != nil {
return xerrors . Errorf ( "register active users prometheus metric: %w" , err )
}
2022-08-09 01:08:42 +00:00
defer closeUsersFunc ( )
2022-11-05 00:03:01 +00:00
closeWorkspacesFunc , err := prometheusmetrics . Workspaces ( ctx , options . PrometheusRegistry , options . Database , 0 )
2022-08-09 01:08:42 +00:00
if err != nil {
return xerrors . Errorf ( "register workspaces prometheus metric: %w" , err )
}
defer closeWorkspacesFunc ( )
2022-08-08 15:09:46 +00:00
//nolint:revive
2022-11-05 00:03:01 +00:00
defer serveHandler ( ctx , logger , promhttp . InstrumentMetricHandler (
options . PrometheusRegistry , promhttp . HandlerFor ( options . PrometheusRegistry , promhttp . HandlerOpts { } ) ,
2023-03-07 21:10:01 +00:00
) , cfg . Prometheus . Address . String ( ) , "prometheus" ) ( )
2022-08-08 15:09:46 +00:00
}
2023-03-07 21:10:01 +00:00
if cfg . Swagger . Enable {
options . SwaggerEndpoint = cfg . Swagger . Enable . Value ( )
2022-12-19 17:43:46 +00:00
}
2022-10-17 23:36:23 +00:00
// We use a separate coderAPICloser so the Enterprise API
2022-10-17 13:43:30 +00:00
// can have it's own close functions. This is cleaner
// than abstracting the Coder API itself.
2022-10-17 23:36:23 +00:00
coderAPI , coderAPICloser , err := newAPI ( ctx , options )
2022-09-20 04:11:01 +00:00
if err != nil {
2023-03-23 22:42:20 +00:00
return xerrors . Errorf ( "create coder API: %w" , err )
2022-09-20 04:11:01 +00:00
}
2022-07-27 15:21:21 +00:00
2022-03-22 19:17:50 +00:00
client := codersdk . New ( localURL )
2022-12-19 19:25:59 +00:00
if localURL . Scheme == "https" && isLocalhost ( localURL . Hostname ( ) ) {
// The certificate will likely be self-signed or for a different
// hostname, so we need to skip verification.
2022-03-24 19:21:05 +00:00
client . HTTPClient . Transport = & http . Transport {
2022-03-25 00:20:13 +00:00
TLSClientConfig : & tls . Config {
//nolint:gosec
InsecureSkipVerify : true ,
} ,
2022-03-24 19:21:05 +00:00
}
}
2022-12-19 19:25:59 +00:00
defer client . HTTPClient . CloseIdleConnections ( )
2022-03-22 19:17:50 +00:00
2022-12-15 20:09:19 +00:00
// This is helpful for tests, but can be silently ignored.
// Coder may be ran as users that don't have permission to write in the homedir,
// such as via the systemd service.
2023-03-07 21:10:01 +00:00
err = config . URL ( ) . Write ( client . URL . String ( ) )
if err != nil && flag . Lookup ( "test.v" ) != nil {
return xerrors . Errorf ( "write config url: %w" , err )
}
2022-12-15 20:09:19 +00:00
2022-07-27 15:21:21 +00:00
// Since errCh only has one buffered slot, all routines
// sending on it must be wrapped in a select/default to
// avoid leaving dangling goroutines waiting for the
// channel to be consumed.
2022-04-18 20:11:59 +00:00
errCh := make ( chan error , 1 )
2022-03-24 15:07:33 +00:00
provisionerDaemons := make ( [ ] * provisionerd . Server , 0 )
2022-07-27 15:21:21 +00:00
defer func ( ) {
// We have no graceful shutdown of provisionerDaemons
// here because that's handled at the end of main, this
// is here in case the program exits early.
for _ , daemon := range provisionerDaemons {
_ = daemon . Close ( )
}
} ( )
2023-03-23 22:42:20 +00:00
var provisionerdWaitGroup sync . WaitGroup
defer provisionerdWaitGroup . Wait ( )
2022-11-05 00:03:01 +00:00
provisionerdMetrics := provisionerd . NewMetrics ( options . PrometheusRegistry )
2023-03-07 21:10:01 +00:00
for i := int64 ( 0 ) ; i < cfg . Provisioner . Daemons . Value ( ) ; i ++ {
2022-12-06 17:05:14 +00:00
daemonCacheDir := filepath . Join ( cacheDir , fmt . Sprintf ( "provisioner-%d" , i ) )
2023-03-23 22:42:20 +00:00
daemon , err := newProvisionerDaemon (
ctx , coderAPI , provisionerdMetrics , logger , cfg , daemonCacheDir , errCh , false , & provisionerdWaitGroup ,
)
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "create provisioner daemon: %w" , err )
}
2022-07-27 15:21:21 +00:00
provisionerDaemons = append ( provisionerDaemons , daemon )
}
shutdownConnsCtx , shutdownConns := context . WithCancel ( ctx )
defer shutdownConns ( )
2022-08-29 17:07:49 +00:00
2023-03-23 19:09:13 +00:00
// Ensures that old database entries are cleaned up over time!
purger := dbpurge . New ( ctx , logger , options . Database )
defer purger . Close ( )
2022-12-15 20:09:19 +00:00
// Wrap the server in middleware that redirects to the access URL if
// the request is not to a local IP.
var handler http . Handler = coderAPI . RootHandler
2023-03-07 21:10:01 +00:00
if cfg . RedirectToAccessURL {
handler = redirectToAccessURL ( handler , cfg . AccessURL . Value ( ) , tunnel != nil , appHostnameRegex )
2022-12-15 20:09:19 +00:00
}
// ReadHeaderTimeout is purposefully not enabled. It caused some
// issues with websockets over the dev tunnel.
2022-08-29 17:07:49 +00:00
// See: https://github.com/coder/coder/pull/3730
//nolint:gosec
2022-12-15 20:09:19 +00:00
httpServer := & http . Server {
// These errors are typically noise like "TLS: EOF". Vault does
// similar:
2022-07-27 15:21:21 +00:00
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
2022-08-29 17:07:49 +00:00
ErrorLog : log . New ( io . Discard , "" , 0 ) ,
2022-12-15 20:09:19 +00:00
Handler : handler ,
2022-07-27 15:21:21 +00:00
BaseContext : func ( _ net . Listener ) context . Context {
return shutdownConnsCtx
} ,
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
defer func ( ) {
2022-12-15 20:09:19 +00:00
_ = shutdownWithTimeout ( httpServer . Shutdown , 5 * time . Second )
2022-03-24 15:07:33 +00:00
} ( )
2022-03-22 19:17:50 +00:00
2022-12-15 20:09:19 +00:00
// We call this in the routine so we can kill the other listeners if
// one of them fails.
closeListenersNow := func ( ) {
if httpListener != nil {
_ = httpListener . Close ( )
}
if httpsListener != nil {
_ = httpsListener . Close ( )
}
2022-10-07 13:05:56 +00:00
if tunnel != nil {
2022-12-15 20:09:19 +00:00
_ = tunnel . Listener . Close ( )
2022-03-24 19:21:05 +00:00
}
2022-12-15 20:09:19 +00:00
}
2022-06-10 18:38:11 +00:00
2022-12-15 20:09:19 +00:00
eg := errgroup . Group { }
if httpListener != nil {
eg . Go ( func ( ) error {
defer closeListenersNow ( )
return httpServer . Serve ( httpListener )
} )
}
if httpsListener != nil {
eg . Go ( func ( ) error {
defer closeListenersNow ( )
return httpServer . Serve ( httpsListener )
} )
}
2022-10-07 13:05:56 +00:00
if tunnel != nil {
2022-07-27 15:21:21 +00:00
eg . Go ( func ( ) error {
2022-12-15 20:09:19 +00:00
defer closeListenersNow ( )
return httpServer . Serve ( tunnel . Listener )
2022-06-10 18:38:11 +00:00
} )
2022-07-27 15:21:21 +00:00
}
2022-12-15 20:09:19 +00:00
2022-07-27 15:21:21 +00:00
go func ( ) {
select {
case errCh <- eg . Wait ( ) :
default :
2022-06-10 18:38:11 +00:00
}
2022-03-22 19:17:50 +00:00
} ( )
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "\n==> Logs will stream in below (press ctrl+c to gracefully exit):" )
2022-06-15 21:02:18 +00:00
2022-03-24 15:07:33 +00:00
// Updates the systemd status from activating to activated.
_ , err = daemon . SdNotify ( false , daemon . SdNotifyReady )
if err != nil {
return xerrors . Errorf ( "notify systemd: %w" , err )
}
2023-03-07 21:10:01 +00:00
autobuildPoller := time . NewTicker ( cfg . AutobuildPollInterval . Value ( ) )
2022-05-20 10:57:02 +00:00
defer autobuildPoller . Stop ( )
2022-07-27 15:21:21 +00:00
autobuildExecutor := executor . New ( ctx , options . Database , logger , autobuildPoller . C )
2022-05-20 10:57:02 +00:00
autobuildExecutor . Run ( )
2022-05-11 22:03:02 +00:00
2022-07-27 15:21:21 +00:00
// Currently there is no way to ask the server to shut
// itself down, so any exit signal will result in a non-zero
// exit of the server.
var exitErr error
2022-03-22 19:17:50 +00:00
select {
2022-08-18 13:25:32 +00:00
case <- notifyCtx . Done ( ) :
exitErr = notifyCtx . Err ( )
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintln ( inv . Stdout , cliui . Styles . Bold . Render (
2022-07-27 15:21:21 +00:00
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ,
) )
2023-03-22 13:13:48 +00:00
case <- tunnelDone :
exitErr = xerrors . New ( "dev tunnel closed unexpectedly" )
2022-07-27 15:21:21 +00:00
case exitErr = <- errCh :
2022-03-24 15:07:33 +00:00
}
2022-07-27 15:21:21 +00:00
if exitErr != nil && ! xerrors . Is ( exitErr , context . Canceled ) {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "Unexpected error, shutting down server: %s\n" , exitErr )
2022-07-27 15:21:21 +00:00
}
// Begin clean shut down stage, we try to shut down services
// gracefully in an order that gives the best experience.
// This procedure should not differ greatly from the order
// of `defer`s in this function, but allows us to inform
// the user about what's going on and handle errors more
// explicitly.
2022-03-24 15:07:33 +00:00
_ , err = daemon . SdNotify ( false , daemon . SdNotifyStopping )
if err != nil {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "Notify systemd failed: %s" , err )
2022-03-24 15:07:33 +00:00
}
2022-07-27 15:21:21 +00:00
// Stop accepting new connections without interrupting
// in-flight requests, give in-flight requests 5 seconds to
// complete.
2023-03-23 22:42:20 +00:00
cliui . Info ( inv . Stdout , "Shutting down API server..." + "\n" )
2022-12-15 20:09:19 +00:00
err = shutdownWithTimeout ( httpServer . Shutdown , 3 * time . Second )
2022-07-27 15:21:21 +00:00
if err != nil {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "API server shutdown took longer than 3s: %s\n" , err )
2022-07-27 15:21:21 +00:00
} else {
2023-03-23 22:42:20 +00:00
cliui . Info ( inv . Stdout , "Gracefully shut down API server\n" )
2022-03-22 19:17:50 +00:00
}
2022-07-27 15:21:21 +00:00
// Cancel any remaining in-flight requests.
shutdownConns ( )
2022-03-24 15:07:33 +00:00
2022-07-27 15:21:21 +00:00
// Shut down provisioners before waiting for WebSockets
// connections to close.
var wg sync . WaitGroup
for i , provisionerDaemon := range provisionerDaemons {
id := i + 1
provisionerDaemon := provisionerDaemon
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
2023-03-23 22:42:20 +00:00
if ok , _ := inv . ParsedFlags ( ) . GetBool ( varVerbose ) ; ok {
cliui . Infof ( inv . Stdout , "Shutting down provisioner daemon %d...\n" , id )
2022-07-27 15:21:21 +00:00
}
2022-09-20 04:35:18 +00:00
err := shutdownWithTimeout ( provisionerDaemon . Shutdown , 5 * time . Second )
2022-07-27 15:21:21 +00:00
if err != nil {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "Failed to shutdown provisioner daemon %d: %s\n" , id , err )
2022-07-27 15:21:21 +00:00
return
}
err = provisionerDaemon . Close ( )
if err != nil {
2023-03-23 22:42:20 +00:00
cliui . Errorf ( inv . Stderr , "Close provisioner daemon %d: %s\n" , id , err )
2022-07-27 15:21:21 +00:00
return
}
2023-03-23 22:42:20 +00:00
if ok , _ := inv . ParsedFlags ( ) . GetBool ( varVerbose ) ; ok {
cliui . Infof ( inv . Stdout , "Gracefully shut down provisioner daemon %d\n" , id )
2022-07-27 15:21:21 +00:00
}
} ( )
}
wg . Wait ( )
2023-03-23 22:42:20 +00:00
cliui . Info ( inv . Stdout , "Waiting for WebSocket connections to close..." + "\n" )
2022-10-17 23:36:23 +00:00
_ = coderAPICloser . Close ( )
2023-03-23 22:42:20 +00:00
cliui . Info ( inv . Stdout , "Done waiting for WebSocket connections" + "\n" )
2022-07-27 15:21:21 +00:00
// Close tunnel after we no longer have in-flight connections.
2022-10-07 13:05:56 +00:00
if tunnel != nil {
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "Waiting for tunnel to close..." )
2023-03-22 13:13:48 +00:00
_ = tunnel . Close ( )
<- tunnel . Wait ( )
2023-03-23 22:42:20 +00:00
cliui . Infof ( inv . Stdout , "Done waiting for tunnel" )
2022-04-14 15:29:40 +00:00
}
2022-06-17 05:26:40 +00:00
// Ensures a last report can be sent before exit!
options . Telemetry . Close ( )
2022-07-27 15:21:21 +00:00
// Trigger context cancellation for any remaining services.
cancel ( )
2023-03-23 22:42:20 +00:00
switch {
case xerrors . Is ( exitErr , context . DeadlineExceeded ) :
cliui . Warnf ( inv . Stderr , "Graceful shutdown timed out" )
// Errors here cause a significant number of benign CI failures.
return nil
case xerrors . Is ( exitErr , context . Canceled ) :
return nil
case exitErr != nil :
return xerrors . Errorf ( "graceful shutdown: %w" , exitErr )
default :
2022-09-20 04:56:51 +00:00
return nil
}
2022-03-22 19:17:50 +00:00
} ,
}
2022-03-28 19:26:41 +00:00
2022-12-20 18:51:17 +00:00
var pgRawURL bool
2023-03-23 22:42:20 +00:00
postgresBuiltinURLCmd := & clibase . Cmd {
2022-06-15 21:02:18 +00:00
Use : "postgres-builtin-url" ,
Short : "Output the connection URL for the built-in PostgreSQL deployment." ,
2023-03-23 22:42:20 +00:00
Handler : func ( inv * clibase . Invocation ) error {
url , err := embeddedPostgresURL ( r . createConfig ( ) )
2022-06-15 21:02:18 +00:00
if err != nil {
return err
}
2022-12-20 18:51:17 +00:00
if pgRawURL {
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "%s\n" , url )
2022-12-20 18:51:17 +00:00
} else {
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "%s\n" , cliui . Styles . Code . Render ( fmt . Sprintf ( "psql %q" , url ) ) )
2022-12-20 18:51:17 +00:00
}
2022-06-15 21:02:18 +00:00
return nil
} ,
2022-12-20 18:51:17 +00:00
}
2023-03-23 22:42:20 +00:00
postgresBuiltinServeCmd := & clibase . Cmd {
2022-07-14 21:51:44 +00:00
Use : "postgres-builtin-serve" ,
Short : "Run the built-in PostgreSQL deployment." ,
2023-03-23 22:42:20 +00:00
Handler : func ( inv * clibase . Invocation ) error {
ctx := inv . Context ( )
2022-12-20 18:51:17 +00:00
2023-03-23 22:42:20 +00:00
cfg := r . createConfig ( )
logger := slog . Make ( sloghuman . Sink ( inv . Stderr ) )
if ok , _ := inv . ParsedFlags ( ) . GetBool ( varVerbose ) ; ok {
2022-07-14 21:51:44 +00:00
logger = logger . Leveled ( slog . LevelDebug )
}
2022-12-20 18:51:17 +00:00
ctx , cancel := signal . NotifyContext ( ctx , InterruptSignals ... )
defer cancel ( )
url , closePg , err := startBuiltinPostgres ( ctx , cfg , logger )
2022-07-14 21:51:44 +00:00
if err != nil {
return err
}
defer func ( ) { _ = closePg ( ) } ( )
2022-12-20 18:51:17 +00:00
if pgRawURL {
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "%s\n" , url )
2022-12-20 18:51:17 +00:00
} else {
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "%s\n" , cliui . Styles . Code . Render ( fmt . Sprintf ( "psql %q" , url ) ) )
2022-12-20 18:51:17 +00:00
}
2022-07-14 21:51:44 +00:00
2022-12-20 18:51:17 +00:00
<- ctx . Done ( )
2022-07-14 21:51:44 +00:00
return nil
} ,
2022-12-20 18:51:17 +00:00
}
2023-03-23 22:42:20 +00:00
createAdminUserCmd := r . newCreateAdminUserCommand ( )
rawURLOpt := clibase . Option {
Flag : "raw-url" ,
Value : clibase . BoolOf ( & pgRawURL ) ,
Description : "Output the raw connection URL instead of a psql command." ,
}
createAdminUserCmd . Options . Add ( rawURLOpt )
postgresBuiltinURLCmd . Options . Add ( rawURLOpt )
postgresBuiltinServeCmd . Options . Add ( rawURLOpt )
serverCmd . Children = append (
serverCmd . Children ,
createAdminUserCmd , postgresBuiltinURLCmd , postgresBuiltinServeCmd ,
)
2022-07-14 21:51:44 +00:00
2023-03-23 22:42:20 +00:00
return serverCmd
2022-03-22 19:17:50 +00:00
}
2022-07-31 22:49:25 +00:00
// isLocalURL returns true if the hostname of the provided URL appears to
// resolve to a loopback address.
func isLocalURL ( ctx context . Context , u * url . URL ) ( bool , error ) {
resolver := & net . Resolver { }
ips , err := resolver . LookupIPAddr ( ctx , u . Hostname ( ) )
if err != nil {
return false , err
}
for _ , ip := range ips {
if ip . IP . IsLoopback ( ) {
return true , nil
}
}
return false , nil
}
2022-09-20 04:35:18 +00:00
func shutdownWithTimeout ( shutdown func ( context . Context ) error , timeout time . Duration ) error {
2022-07-27 15:21:21 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , timeout )
defer cancel ( )
2022-09-20 04:35:18 +00:00
return shutdown ( ctx )
2022-07-27 15:21:21 +00:00
}
2022-05-19 22:47:45 +00:00
// nolint:revive
2022-09-16 16:43:22 +00:00
func newProvisionerDaemon (
ctx context . Context ,
coderAPI * coderd . API ,
2022-11-05 00:03:01 +00:00
metrics provisionerd . Metrics ,
2022-09-16 16:43:22 +00:00
logger slog . Logger ,
2023-03-07 21:10:01 +00:00
cfg * codersdk . DeploymentValues ,
2022-12-06 17:05:14 +00:00
cacheDir string ,
2022-09-16 16:43:22 +00:00
errCh chan error ,
dev bool ,
2023-03-23 22:42:20 +00:00
wg * sync . WaitGroup ,
2022-07-27 15:21:21 +00:00
) ( srv * provisionerd . Server , err error ) {
ctx , cancel := context . WithCancel ( ctx )
defer func ( ) {
if err != nil {
cancel ( )
}
} ( )
2022-12-06 17:05:14 +00:00
err = os . MkdirAll ( cacheDir , 0 o700 )
2022-03-30 22:59:54 +00:00
if err != nil {
2022-12-06 17:05:14 +00:00
return nil , xerrors . Errorf ( "mkdir %q: %w" , cacheDir , err )
2022-03-30 22:59:54 +00:00
}
2022-11-22 18:19:32 +00:00
terraformClient , terraformServer := provisionersdk . MemTransportPipe ( )
2023-03-23 22:42:20 +00:00
wg . Add ( 1 )
2022-03-22 19:17:50 +00:00
go func ( ) {
2023-03-23 22:42:20 +00:00
defer wg . Done ( )
2022-07-27 15:21:21 +00:00
<- ctx . Done ( )
_ = terraformClient . Close ( )
_ = terraformServer . Close ( )
} ( )
2023-03-23 22:42:20 +00:00
wg . Add ( 1 )
2022-07-27 15:21:21 +00:00
go func ( ) {
2023-03-23 22:42:20 +00:00
defer wg . Done ( )
2022-07-27 15:21:21 +00:00
defer cancel ( )
2022-03-22 19:17:50 +00:00
err := terraform . Serve ( ctx , & terraform . ServeOptions {
ServeOptions : & provisionersdk . ServeOptions {
Listener : terraformServer ,
} ,
2022-12-06 17:05:14 +00:00
CachePath : cacheDir ,
2022-03-28 19:57:19 +00:00
Logger : logger ,
2022-03-22 19:17:50 +00:00
} )
2022-06-22 23:11:52 +00:00
if err != nil && ! xerrors . Is ( err , context . Canceled ) {
2022-07-27 15:21:21 +00:00
select {
case errCh <- err :
default :
}
2022-03-22 19:17:50 +00:00
}
} ( )
2022-03-29 19:59:32 +00:00
tempDir , err := os . MkdirTemp ( "" , "provisionerd" )
2022-03-22 19:17:50 +00:00
if err != nil {
return nil , err
}
2022-03-29 19:59:32 +00:00
2022-05-19 22:47:45 +00:00
provisioners := provisionerd . Provisioners {
2022-11-22 18:19:32 +00:00
string ( database . ProvisionerTypeTerraform ) : sdkproto . NewDRPCProvisionerClient ( terraformClient ) ,
2022-05-19 22:47:45 +00:00
}
// include echo provisioner when in dev mode
if dev {
2022-11-22 18:19:32 +00:00
echoClient , echoServer := provisionersdk . MemTransportPipe ( )
2023-03-23 22:42:20 +00:00
wg . Add ( 1 )
2022-05-19 22:47:45 +00:00
go func ( ) {
2023-03-23 22:42:20 +00:00
defer wg . Done ( )
2022-07-27 15:21:21 +00:00
<- ctx . Done ( )
_ = echoClient . Close ( )
_ = echoServer . Close ( )
} ( )
2023-03-23 22:42:20 +00:00
wg . Add ( 1 )
2022-07-27 15:21:21 +00:00
go func ( ) {
2023-03-23 22:42:20 +00:00
defer wg . Done ( )
2022-07-27 15:21:21 +00:00
defer cancel ( )
2022-06-16 15:09:22 +00:00
err := echo . Serve ( ctx , afero . NewOsFs ( ) , & provisionersdk . ServeOptions { Listener : echoServer } )
2022-05-19 22:47:45 +00:00
if err != nil {
2022-07-27 15:21:21 +00:00
select {
case errCh <- err :
default :
}
2022-05-19 22:47:45 +00:00
}
} ( )
2022-11-22 18:19:32 +00:00
provisioners [ string ( database . ProvisionerTypeEcho ) ] = sdkproto . NewDRPCProvisionerClient ( echoClient )
2022-05-19 22:47:45 +00:00
}
2022-12-01 22:54:53 +00:00
debounce := time . Second
2022-11-10 22:37:33 +00:00
return provisionerd . New ( func ( ctx context . Context ) ( proto . DRPCProvisionerDaemonClient , error ) {
// This debounces calls to listen every second. Read the comment
// in provisionerdserver.go to learn more!
2022-12-01 22:54:53 +00:00
return coderAPI . CreateInMemoryProvisionerDaemon ( ctx , debounce )
2022-11-10 22:37:33 +00:00
} , & provisionerd . Options {
2022-11-08 13:19:40 +00:00
Logger : logger ,
2023-03-07 21:10:01 +00:00
JobPollInterval : cfg . Provisioner . DaemonPollInterval . Value ( ) ,
JobPollJitter : cfg . Provisioner . DaemonPollJitter . Value ( ) ,
2022-12-01 22:54:53 +00:00
JobPollDebounce : debounce ,
2022-11-08 13:19:40 +00:00
UpdateInterval : 500 * time . Millisecond ,
2023-03-07 21:10:01 +00:00
ForceCancelInterval : cfg . Provisioner . ForceCancelInterval . Value ( ) ,
2022-11-08 13:19:40 +00:00
Provisioners : provisioners ,
WorkDirectory : tempDir ,
TracerProvider : coderAPI . TracerProvider ,
Metrics : & metrics ,
2022-03-22 19:17:50 +00:00
} ) , nil
}
2022-03-24 19:21:05 +00:00
2022-04-19 16:40:01 +00:00
// nolint: revive
2023-03-23 22:42:20 +00:00
func printLogo ( inv * clibase . Invocation ) {
2023-01-13 02:08:23 +00:00
// Only print the logo in TTYs.
2023-03-23 22:42:20 +00:00
if ! isTTYOut ( inv ) {
2023-01-13 02:08:23 +00:00
return
}
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stdout , "%s - Your Self-Hosted Remote Development Platform\n" , cliui . Styles . Bold . Render ( "Coder " + buildinfo . Version ( ) ) )
2022-03-24 19:21:05 +00:00
}
2022-10-04 11:45:21 +00:00
func loadCertificates ( tlsCertFiles , tlsKeyFiles [ ] string ) ( [ ] tls . Certificate , error ) {
if len ( tlsCertFiles ) != len ( tlsKeyFiles ) {
return nil , xerrors . New ( "--tls-cert-file and --tls-key-file must be used the same amount of times" )
}
certs := make ( [ ] tls . Certificate , len ( tlsCertFiles ) )
for i := range tlsCertFiles {
certFile , keyFile := tlsCertFiles [ i ] , tlsKeyFiles [ i ]
cert , err := tls . LoadX509KeyPair ( certFile , keyFile )
if err != nil {
2023-03-07 21:10:01 +00:00
return nil , xerrors . Errorf (
"load TLS key pair %d (%q, %q): %w\ncertFiles: %+v\nkeyFiles: %+v" ,
i , certFile , keyFile , err ,
tlsCertFiles , tlsKeyFiles ,
)
2022-10-04 11:45:21 +00:00
}
certs [ i ] = cert
}
return certs , nil
}
2023-02-02 17:08:35 +00:00
// generateSelfSignedCertificate creates an unsafe self-signed certificate
// at random that allows users to proceed with setup in the event they
// haven't configured any TLS certificates.
func generateSelfSignedCertificate ( ) ( * tls . Certificate , error ) {
privateKey , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
return nil , err
}
template := x509 . Certificate {
SerialNumber : big . NewInt ( 1 ) ,
NotBefore : time . Now ( ) ,
NotAfter : time . Now ( ) . Add ( time . Hour * 24 * 180 ) ,
KeyUsage : x509 . KeyUsageKeyEncipherment | x509 . KeyUsageDigitalSignature ,
ExtKeyUsage : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageServerAuth } ,
BasicConstraintsValid : true ,
IPAddresses : [ ] net . IP { net . ParseIP ( "127.0.0.1" ) } ,
}
derBytes , err := x509 . CreateCertificate ( rand . Reader , & template , & template , & privateKey . PublicKey , privateKey )
if err != nil {
return nil , err
}
var cert tls . Certificate
cert . Certificate = append ( cert . Certificate , derBytes )
cert . PrivateKey = privateKey
return & cert , nil
}
2022-10-17 13:43:30 +00:00
func configureTLS ( tlsMinVersion , tlsClientAuth string , tlsCertFiles , tlsKeyFiles [ ] string , tlsClientCAFile string ) ( * tls . Config , error ) {
2022-03-25 00:20:13 +00:00
tlsConfig := & tls . Config {
MinVersion : tls . VersionTLS12 ,
}
2022-03-24 19:21:05 +00:00
switch tlsMinVersion {
case "tls10" :
tlsConfig . MinVersion = tls . VersionTLS10
case "tls11" :
tlsConfig . MinVersion = tls . VersionTLS11
case "tls12" :
tlsConfig . MinVersion = tls . VersionTLS12
case "tls13" :
tlsConfig . MinVersion = tls . VersionTLS13
default :
return nil , xerrors . Errorf ( "unrecognized tls version: %q" , tlsMinVersion )
}
switch tlsClientAuth {
case "none" :
tlsConfig . ClientAuth = tls . NoClientCert
case "request" :
tlsConfig . ClientAuth = tls . RequestClientCert
case "require-any" :
tlsConfig . ClientAuth = tls . RequireAnyClientCert
case "verify-if-given" :
tlsConfig . ClientAuth = tls . VerifyClientCertIfGiven
case "require-and-verify" :
tlsConfig . ClientAuth = tls . RequireAndVerifyClientCert
default :
return nil , xerrors . Errorf ( "unrecognized tls client auth: %q" , tlsClientAuth )
}
2022-10-04 11:45:21 +00:00
certs , err := loadCertificates ( tlsCertFiles , tlsKeyFiles )
2022-03-24 19:21:05 +00:00
if err != nil {
2022-10-04 11:45:21 +00:00
return nil , xerrors . Errorf ( "load certificates: %w" , err )
2022-03-24 19:21:05 +00:00
}
2023-02-02 17:08:35 +00:00
if len ( certs ) == 0 {
selfSignedCertificate , err := generateSelfSignedCertificate ( )
if err != nil {
return nil , xerrors . Errorf ( "generate self signed certificate: %w" , err )
}
certs = append ( certs , * selfSignedCertificate )
}
2022-10-17 13:43:30 +00:00
tlsConfig . Certificates = certs
2022-10-04 11:45:21 +00:00
tlsConfig . GetCertificate = func ( hi * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
// If there's only one certificate, return it.
if len ( certs ) == 1 {
return & certs [ 0 ] , nil
}
// Expensively check which certificate matches the client hello.
for _ , cert := range certs {
cert := cert
if err := hi . SupportsCertificate ( & cert ) ; err == nil {
return & cert , nil
}
}
2022-03-24 19:21:05 +00:00
2022-10-04 11:45:21 +00:00
// Return the first certificate if we have one, or return nil so the
// server doesn't fail.
if len ( certs ) > 0 {
return & certs [ 0 ] , nil
}
return nil , nil //nolint:nilnil
}
2022-03-24 19:21:05 +00:00
2022-12-14 14:44:29 +00:00
err = configureCAPool ( tlsClientCAFile , tlsConfig )
if err != nil {
return nil , err
}
return tlsConfig , nil
}
func configureCAPool ( tlsClientCAFile string , tlsConfig * tls . Config ) error {
2022-03-24 19:21:05 +00:00
if tlsClientCAFile != "" {
caPool := x509 . NewCertPool ( )
2022-03-29 19:59:32 +00:00
data , err := os . ReadFile ( tlsClientCAFile )
2022-03-24 19:21:05 +00:00
if err != nil {
2022-12-14 14:44:29 +00:00
return xerrors . Errorf ( "read %q: %w" , tlsClientCAFile , err )
2022-03-24 19:21:05 +00:00
}
if ! caPool . AppendCertsFromPEM ( data ) {
2022-12-14 14:44:29 +00:00
return xerrors . Errorf ( "failed to parse CA certificate in tls-client-ca-file" )
2022-03-24 19:21:05 +00:00
}
tlsConfig . ClientCAs = caPool
}
2022-12-14 14:44:29 +00:00
return nil
2022-03-24 19:21:05 +00:00
}
2022-04-18 17:37:01 +00:00
2022-11-15 16:56:46 +00:00
//nolint:revive // Ignore flag-parameter: parameter 'allowEveryone' seems to be a control flag, avoid control coupling (revive)
func configureGithubOAuth2 ( accessURL * url . URL , clientID , clientSecret string , allowSignups , allowEveryone bool , allowOrgs [ ] string , rawTeams [ ] string , enterpriseBaseURL string ) ( * coderd . GithubOAuth2Config , error ) {
2022-04-23 22:58:57 +00:00
redirectURL , err := accessURL . Parse ( "/api/v2/users/oauth2/github/callback" )
if err != nil {
return nil , xerrors . Errorf ( "parse github oauth callback url: %w" , err )
}
2022-11-15 16:56:46 +00:00
if allowEveryone && len ( allowOrgs ) > 0 {
return nil , xerrors . New ( "allow everyone and allowed orgs cannot be used together" )
}
if allowEveryone && len ( rawTeams ) > 0 {
return nil , xerrors . New ( "allow everyone and allowed teams cannot be used together" )
}
if ! allowEveryone && len ( allowOrgs ) == 0 {
return nil , xerrors . New ( "allowed orgs is empty: must specify at least one org or allow everyone" )
}
2022-07-09 02:37:18 +00:00
allowTeams := make ( [ ] coderd . GithubOAuth2Team , 0 , len ( rawTeams ) )
for _ , rawTeam := range rawTeams {
parts := strings . SplitN ( rawTeam , "/" , 2 )
if len ( parts ) != 2 {
return nil , xerrors . Errorf ( "github team allowlist is formatted incorrectly. got %s; wanted <organization>/<team>" , rawTeam )
}
allowTeams = append ( allowTeams , coderd . GithubOAuth2Team {
Organization : parts [ 0 ] ,
Slug : parts [ 1 ] ,
} )
}
2022-08-09 01:49:51 +00:00
createClient := func ( client * http . Client ) ( * github . Client , error ) {
if enterpriseBaseURL != "" {
return github . NewEnterpriseClient ( enterpriseBaseURL , "" , client )
}
return github . NewClient ( client ) , nil
}
endpoint := xgithub . Endpoint
if enterpriseBaseURL != "" {
enterpriseURL , err := url . Parse ( enterpriseBaseURL )
if err != nil {
return nil , xerrors . Errorf ( "parse enterprise base url: %w" , err )
}
authURL , err := enterpriseURL . Parse ( "/login/oauth/authorize" )
if err != nil {
return nil , xerrors . Errorf ( "parse enterprise auth url: %w" , err )
}
tokenURL , err := enterpriseURL . Parse ( "/login/oauth/access_token" )
if err != nil {
return nil , xerrors . Errorf ( "parse enterprise token url: %w" , err )
}
endpoint = oauth2 . Endpoint {
AuthURL : authURL . String ( ) ,
TokenURL : tokenURL . String ( ) ,
}
}
2022-04-23 22:58:57 +00:00
return & coderd . GithubOAuth2Config {
OAuth2Config : & oauth2 . Config {
ClientID : clientID ,
ClientSecret : clientSecret ,
2022-08-09 01:49:51 +00:00
Endpoint : endpoint ,
2022-04-23 22:58:57 +00:00
RedirectURL : redirectURL . String ( ) ,
Scopes : [ ] string {
"read:user" ,
"read:org" ,
"user:email" ,
} ,
} ,
AllowSignups : allowSignups ,
2022-11-15 16:56:46 +00:00
AllowEveryone : allowEveryone ,
2022-04-23 22:58:57 +00:00
AllowOrganizations : allowOrgs ,
2022-07-09 02:37:18 +00:00
AllowTeams : allowTeams ,
2022-04-23 22:58:57 +00:00
AuthenticatedUser : func ( ctx context . Context , client * http . Client ) ( * github . User , error ) {
2022-08-09 01:49:51 +00:00
api , err := createClient ( client )
if err != nil {
return nil , err
}
user , _ , err := api . Users . Get ( ctx , "" )
2022-04-23 22:58:57 +00:00
return user , err
} ,
ListEmails : func ( ctx context . Context , client * http . Client ) ( [ ] * github . UserEmail , error ) {
2022-08-09 01:49:51 +00:00
api , err := createClient ( client )
if err != nil {
return nil , err
}
emails , _ , err := api . Users . ListEmails ( ctx , & github . ListOptions { } )
2022-04-23 22:58:57 +00:00
return emails , err
} ,
ListOrganizationMemberships : func ( ctx context . Context , client * http . Client ) ( [ ] * github . Membership , error ) {
2022-08-09 01:49:51 +00:00
api , err := createClient ( client )
if err != nil {
return nil , err
}
memberships , _ , err := api . Organizations . ListOrgMemberships ( ctx , & github . ListOrgMembershipsOptions {
2022-04-23 22:58:57 +00:00
State : "active" ,
2022-07-09 02:37:18 +00:00
ListOptions : github . ListOptions {
PerPage : 100 ,
} ,
2022-04-23 22:58:57 +00:00
} )
return memberships , err
} ,
2022-07-22 18:54:08 +00:00
TeamMembership : func ( ctx context . Context , client * http . Client , org , teamSlug , username string ) ( * github . Membership , error ) {
2022-08-09 01:49:51 +00:00
api , err := createClient ( client )
if err != nil {
return nil , err
}
team , _ , err := api . Teams . GetTeamMembershipBySlug ( ctx , org , teamSlug , username )
2022-07-13 00:45:43 +00:00
return team , err
2022-07-09 02:37:18 +00:00
} ,
2022-04-23 22:58:57 +00:00
} , nil
}
2022-06-15 21:02:18 +00:00
// embeddedPostgresURL returns the URL for the embedded PostgreSQL deployment.
func embeddedPostgresURL ( cfg config . Root ) ( string , error ) {
pgPassword , err := cfg . PostgresPassword ( ) . Read ( )
if errors . Is ( err , os . ErrNotExist ) {
pgPassword , err = cryptorand . String ( 16 )
if err != nil {
return "" , xerrors . Errorf ( "generate password: %w" , err )
}
err = cfg . PostgresPassword ( ) . Write ( pgPassword )
if err != nil {
return "" , xerrors . Errorf ( "write password: %w" , err )
}
}
if err != nil && ! errors . Is ( err , os . ErrNotExist ) {
return "" , err
}
pgPort , err := cfg . PostgresPort ( ) . Read ( )
if errors . Is ( err , os . ErrNotExist ) {
listener , err := net . Listen ( "tcp4" , "127.0.0.1:0" )
if err != nil {
return "" , xerrors . Errorf ( "listen for random port: %w" , err )
}
_ = listener . Close ( )
tcpAddr , valid := listener . Addr ( ) . ( * net . TCPAddr )
if ! valid {
return "" , xerrors . Errorf ( "listener returned non TCP addr: %T" , tcpAddr )
}
pgPort = strconv . Itoa ( tcpAddr . Port )
err = cfg . PostgresPort ( ) . Write ( pgPort )
if err != nil {
return "" , xerrors . Errorf ( "write postgres port: %w" , err )
}
}
return fmt . Sprintf ( "postgres://coder@localhost:%s/coder?sslmode=disable&password=%s" , pgPort , pgPassword ) , nil
}
func startBuiltinPostgres ( ctx context . Context , cfg config . Root , logger slog . Logger ) ( string , func ( ) error , error ) {
usr , err := user . Current ( )
if err != nil {
return "" , nil , err
}
if usr . Uid == "0" {
return "" , nil , xerrors . New ( "The built-in PostgreSQL cannot run as the root user. Create a non-root user and run again!" )
}
// Ensure a password and port have been generated!
connectionURL , err := embeddedPostgresURL ( cfg )
if err != nil {
return "" , nil , err
}
pgPassword , err := cfg . PostgresPassword ( ) . Read ( )
if err != nil {
return "" , nil , xerrors . Errorf ( "read postgres password: %w" , err )
}
pgPortRaw , err := cfg . PostgresPort ( ) . Read ( )
if err != nil {
return "" , nil , xerrors . Errorf ( "read postgres port: %w" , err )
}
2022-12-19 19:25:59 +00:00
pgPort , err := strconv . ParseUint ( pgPortRaw , 10 , 16 )
2022-06-15 21:02:18 +00:00
if err != nil {
return "" , nil , xerrors . Errorf ( "parse postgres port: %w" , err )
}
stdlibLogger := slog . Stdlib ( ctx , logger . Named ( "postgres" ) , slog . LevelDebug )
ep := embeddedpostgres . NewDatabase (
embeddedpostgres . DefaultConfig ( ) .
2022-08-10 15:08:24 +00:00
Version ( embeddedpostgres . V13 ) .
2022-06-15 21:02:18 +00:00
BinariesPath ( filepath . Join ( cfg . PostgresPath ( ) , "bin" ) ) .
DataPath ( filepath . Join ( cfg . PostgresPath ( ) , "data" ) ) .
RuntimePath ( filepath . Join ( cfg . PostgresPath ( ) , "runtime" ) ) .
CachePath ( filepath . Join ( cfg . PostgresPath ( ) , "cache" ) ) .
Username ( "coder" ) .
Password ( pgPassword ) .
Database ( "coder" ) .
Port ( uint32 ( pgPort ) ) .
Logger ( stdlibLogger . Writer ( ) ) ,
)
err = ep . Start ( )
if err != nil {
return "" , nil , xerrors . Errorf ( "Failed to start built-in PostgreSQL. Optionally, specify an external deployment with `--postgres-url`: %w" , err )
}
return connectionURL , ep . Stop , nil
}
2022-11-13 20:15:06 +00:00
2022-12-14 14:44:29 +00:00
func configureHTTPClient ( ctx context . Context , clientCertFile , clientKeyFile string , tlsClientCAFile string ) ( context . Context , * http . Client , error ) {
if clientCertFile != "" && clientKeyFile != "" {
certificates , err := loadCertificates ( [ ] string { clientCertFile } , [ ] string { clientKeyFile } )
2022-11-13 20:15:06 +00:00
if err != nil {
2022-12-14 14:44:29 +00:00
return ctx , nil , err
2022-11-13 20:15:06 +00:00
}
2022-12-14 14:44:29 +00:00
tlsClientConfig := & tls . Config { //nolint:gosec
Certificates : certificates ,
}
err = configureCAPool ( tlsClientCAFile , tlsClientConfig )
if err != nil {
return nil , nil , err
}
httpClient := & http . Client {
2022-11-13 20:15:06 +00:00
Transport : & http . Transport {
2022-12-14 14:44:29 +00:00
TLSClientConfig : tlsClientConfig ,
2022-11-13 20:15:06 +00:00
} ,
2022-12-14 14:44:29 +00:00
}
return context . WithValue ( ctx , oauth2 . HTTPClient , httpClient ) , httpClient , nil
2022-11-13 20:15:06 +00:00
}
2022-12-14 14:44:29 +00:00
return ctx , & http . Client { } , nil
2022-11-13 20:15:06 +00:00
}
2022-12-15 20:09:19 +00:00
2023-02-02 17:08:35 +00:00
// nolint:revive
2023-02-08 19:48:17 +00:00
func redirectToAccessURL ( handler http . Handler , accessURL * url . URL , tunnel bool , appHostnameRegex * regexp . Regexp ) http . Handler {
2022-12-15 20:09:19 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2023-02-02 17:08:35 +00:00
redirect := func ( ) {
2022-12-15 20:09:19 +00:00
http . Redirect ( w , r , accessURL . String ( ) , http . StatusTemporaryRedirect )
2023-02-02 17:08:35 +00:00
}
// Only do this if we aren't tunneling.
// If we are tunneling, we want to allow the request to go through
// because the tunnel doesn't proxy with TLS.
if ! tunnel && accessURL . Scheme == "https" && r . TLS == nil {
redirect ( )
return
}
2023-02-08 19:48:17 +00:00
if r . Host == accessURL . Host {
handler . ServeHTTP ( w , r )
return
}
if appHostnameRegex != nil && appHostnameRegex . MatchString ( r . Host ) {
handler . ServeHTTP ( w , r )
2022-12-15 20:09:19 +00:00
return
}
2023-02-08 19:48:17 +00:00
redirect ( )
2022-12-15 20:09:19 +00:00
} )
}
2022-12-19 19:25:59 +00:00
// isLocalhost returns true if the host points to the local machine. Intended to
// be called with `u.Hostname()`.
func isLocalhost ( host string ) bool {
return host == "localhost" || host == "127.0.0.1" || host == "::1"
}
2023-01-13 02:08:23 +00:00
2023-03-23 22:42:20 +00:00
func buildLogger ( inv * clibase . Invocation , cfg * codersdk . DeploymentValues ) ( slog . Logger , func ( ) , error ) {
2023-01-13 02:08:23 +00:00
var (
sinks = [ ] slog . Sink { }
closers = [ ] func ( ) error { }
)
addSinkIfProvided := func ( sinkFn func ( io . Writer ) slog . Sink , loc string ) error {
switch loc {
case "" :
case "/dev/stdout" :
2023-03-23 22:42:20 +00:00
sinks = append ( sinks , sinkFn ( inv . Stdout ) )
2023-01-13 02:08:23 +00:00
case "/dev/stderr" :
2023-03-23 22:42:20 +00:00
sinks = append ( sinks , sinkFn ( inv . Stderr ) )
2023-01-13 02:08:23 +00:00
default :
2023-02-19 00:32:09 +00:00
fi , err := os . OpenFile ( loc , os . O_WRONLY | os . O_CREATE | os . O_APPEND , 0 o644 )
2023-01-13 02:08:23 +00:00
if err != nil {
return xerrors . Errorf ( "open log file %q: %w" , loc , err )
}
closers = append ( closers , fi . Close )
sinks = append ( sinks , sinkFn ( fi ) )
}
return nil
}
2023-03-07 21:10:01 +00:00
err := addSinkIfProvided ( sloghuman . Sink , cfg . Logging . Human . String ( ) )
2023-01-13 02:08:23 +00:00
if err != nil {
return slog . Logger { } , nil , xerrors . Errorf ( "add human sink: %w" , err )
}
2023-03-07 21:10:01 +00:00
err = addSinkIfProvided ( slogjson . Sink , cfg . Logging . JSON . String ( ) )
2023-01-13 02:08:23 +00:00
if err != nil {
return slog . Logger { } , nil , xerrors . Errorf ( "add json sink: %w" , err )
}
2023-03-07 21:10:01 +00:00
err = addSinkIfProvided ( slogstackdriver . Sink , cfg . Logging . Stackdriver . String ( ) )
2023-01-13 02:08:23 +00:00
if err != nil {
return slog . Logger { } , nil , xerrors . Errorf ( "add stackdriver sink: %w" , err )
}
2023-03-07 21:10:01 +00:00
if cfg . Trace . CaptureLogs {
2023-01-13 02:08:23 +00:00
sinks = append ( sinks , tracing . SlogSink { } )
}
level := slog . LevelInfo
2023-03-07 21:10:01 +00:00
if cfg . Verbose {
2023-01-13 02:08:23 +00:00
level = slog . LevelDebug
}
if len ( sinks ) == 0 {
return slog . Logger { } , nil , xerrors . New ( "no loggers provided" )
}
return slog . Make ( sinks ... ) . Leveled ( level ) , func ( ) {
for _ , closer := range closers {
_ = closer ( )
}
} , nil
}
2023-02-06 14:58:21 +00:00
func connectToPostgres ( ctx context . Context , logger slog . Logger , driver string , dbURL string ) ( * sql . DB , error ) {
logger . Debug ( ctx , "connecting to postgresql" )
sqlDB , err := sql . Open ( driver , dbURL )
if err != nil {
return nil , xerrors . Errorf ( "dial postgres: %w" , err )
}
ok := false
defer func ( ) {
if ! ok {
_ = sqlDB . Close ( )
}
} ( )
pingCtx , pingCancel := context . WithTimeout ( ctx , 15 * time . Second )
defer pingCancel ( )
err = sqlDB . PingContext ( pingCtx )
if err != nil {
return nil , xerrors . Errorf ( "ping postgres: %w" , err )
}
// Ensure the PostgreSQL version is >=13.0.0!
version , err := sqlDB . QueryContext ( ctx , "SHOW server_version;" )
if err != nil {
return nil , xerrors . Errorf ( "get postgres version: %w" , err )
}
if ! version . Next ( ) {
return nil , xerrors . Errorf ( "no rows returned for version select" )
}
var versionStr string
err = version . Scan ( & versionStr )
if err != nil {
return nil , xerrors . Errorf ( "scan version: %w" , err )
}
_ = version . Close ( )
versionStr = strings . Split ( versionStr , " " ) [ 0 ]
if semver . Compare ( "v" + versionStr , "v13" ) < 0 {
return nil , xerrors . New ( "PostgreSQL version must be v13.0.0 or higher!" )
}
logger . Debug ( ctx , "connected to postgresql" , slog . F ( "version" , versionStr ) )
err = migrations . Up ( sqlDB )
if err != nil {
return nil , xerrors . Errorf ( "migrate up: %w" , err )
}
// The default is 0 but the request will fail with a 500 if the DB
// cannot accept new connections, so we try to limit that here.
// Requests will wait for a new connection instead of a hard error
// if a limit is set.
sqlDB . SetMaxOpenConns ( 10 )
// Allow a max of 3 idle connections at a time. Lower values end up
// creating a lot of connection churn. Since each connection uses about
// 10MB of memory, we're allocating 30MB to Postgres connections per
// replica, but is better than causing Postgres to spawn a thread 15-20
// times/sec. PGBouncer's transaction pooling is not the greatest so
// it's not optimal for us to deploy.
//
// This was set to 10 before we started doing HA deployments, but 3 was
// later determined to be a better middle ground as to not use up all
// of PGs default connection limit while simultaneously avoiding a lot
// of connection churn.
sqlDB . SetMaxIdleConns ( 3 )
ok = true
return sqlDB , nil
}