2022-03-22 19:17:50 +00:00
package cli
import (
"context"
2022-03-24 19:21:05 +00:00
"crypto/tls"
"crypto/x509"
2022-03-24 15:07:33 +00:00
"database/sql"
2022-04-11 23:54:30 +00:00
"errors"
2022-03-22 19:17:50 +00:00
"fmt"
2022-05-02 01:31:12 +00:00
"io"
"log"
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-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-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-03-29 00:19:28 +00:00
"github.com/spf13/cobra"
2022-09-16 16:43:22 +00:00
"go.opentelemetry.io/otel/trace"
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"
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"
2022-07-14 21:51:44 +00:00
"github.com/coder/coder/buildinfo"
2022-03-28 19:26:41 +00:00
"github.com/coder/coder/cli/cliflag"
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"
"github.com/coder/coder/coderd/database/databasefake"
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-04-06 00:18:26 +00:00
"github.com/coder/coder/coderd/gitsshkey"
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-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"
"github.com/coder/coder/provisionersdk"
"github.com/coder/coder/provisionersdk/proto"
2022-09-01 01:09:44 +00:00
"github.com/coder/coder/tailnet"
2022-03-22 19:17:50 +00:00
)
2022-04-25 04:52:07 +00:00
// nolint:gocyclo
2022-09-20 04:11:01 +00:00
func Server ( newAPI func ( context . Context , * coderd . Options ) ( * coderd . API , error ) ) * cobra . Command {
2022-03-22 19:17:50 +00:00
var (
2022-05-20 10:57:02 +00:00
accessURL string
address string
2022-09-22 22:30:32 +00:00
wildcardAccessURL string
2022-05-20 10:57:02 +00:00
autobuildPollInterval time . Duration
2022-09-01 01:09:44 +00:00
derpServerEnabled bool
derpServerRegionID int
derpServerRegionCode string
derpServerRegionName string
derpServerSTUNAddrs [ ] string
derpConfigURL string
2022-09-11 21:45:49 +00:00
derpConfigPath string
2022-05-20 10:57:02 +00:00
promEnabled bool
promAddress string
pprofEnabled bool
pprofAddress string
cacheDir string
2022-06-15 21:02:18 +00:00
inMemoryDatabase bool
2022-03-28 19:26:41 +00:00
// provisionerDaemonCount is a uint8 to ensure a number > 0.
2022-04-23 22:58:57 +00:00
provisionerDaemonCount uint8
2022-06-15 21:02:18 +00:00
postgresURL string
2022-04-23 22:58:57 +00:00
oauth2GithubClientID string
oauth2GithubClientSecret string
oauth2GithubAllowedOrganizations [ ] string
2022-07-09 02:37:18 +00:00
oauth2GithubAllowedTeams [ ] string
2022-04-23 22:58:57 +00:00
oauth2GithubAllowSignups bool
2022-08-09 01:49:51 +00:00
oauth2GithubEnterpriseBaseURL string
2022-08-01 04:05:35 +00:00
oidcAllowSignups bool
oidcClientID string
oidcClientSecret string
oidcEmailDomain string
oidcIssuerURL string
oidcScopes [ ] string
2022-09-01 01:09:44 +00:00
tailscaleEnable bool
2022-06-17 14:02:44 +00:00
telemetryEnable bool
2022-09-22 16:53:08 +00:00
telemetryTraceEnable bool
2022-06-17 05:26:40 +00:00
telemetryURL string
2022-10-04 11:45:21 +00:00
tlsCertFiles [ ] string
2022-04-23 22:58:57 +00:00
tlsClientCAFile string
tlsClientAuth string
tlsEnable bool
2022-10-04 11:45:21 +00:00
tlsKeyFiles [ ] string
2022-04-23 22:58:57 +00:00
tlsMinVersion string
2022-09-16 16:43:22 +00:00
traceEnable bool
2022-04-23 22:58:57 +00:00
secureAuthCookie bool
sshKeygenAlgorithmRaw string
2022-08-25 19:32:35 +00:00
autoImportTemplates [ ] string
2022-04-23 22:58:57 +00:00
spooky bool
2022-05-03 21:13:30 +00:00
verbose bool
2022-09-01 19:58:23 +00:00
metricsCacheRefreshInterval time . Duration
agentStatRefreshInterval time . Duration
2022-03-22 19:17:50 +00:00
)
2022-04-18 22:40:25 +00:00
2022-03-22 19:17:50 +00:00
root := & cobra . Command {
2022-05-09 22:42:02 +00:00
Use : "server" ,
Short : "Start a Coder server" ,
2022-03-22 19:17:50 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-06-15 21:02:18 +00:00
printLogo ( cmd , spooky )
2022-07-26 14:23:32 +00:00
logger := slog . Make ( sloghuman . Sink ( cmd . ErrOrStderr ( ) ) )
2022-06-15 21:02:18 +00:00
if verbose {
2022-05-03 21:13:30 +00:00
logger = logger . Leveled ( slog . LevelDebug )
}
2022-07-27 15:21:21 +00:00
// Main command context for managing cancellation
// of running services.
ctx , cancel := context . WithCancel ( cmd . Context ( ) )
defer cancel ( )
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
// the shutdown sequence from abrubtly terminating things
// 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`.
notifyCtx , notifyStop := signal . NotifyContext ( ctx , interruptSignals ... )
defer notifyStop ( )
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
err error
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.
shouldCoderTrace := telemetryEnable && ! isTest ( )
// Only override if telemetryTraceEnable was specifically set.
// By default we want it to be controlled by telemetryEnable.
if cmd . Flags ( ) . Changed ( "telemetry-trace" ) {
shouldCoderTrace = telemetryTraceEnable
}
if traceEnable || shouldCoderTrace {
2022-09-20 04:35:18 +00:00
sdkTracerProvider , closeTracing , err := tracing . TracerProvider ( ctx , "coderd" , tracing . TracerOpts {
2022-09-16 16:43:22 +00:00
Default : traceEnable ,
2022-09-22 16:53:08 +00:00
Coder : shouldCoderTrace ,
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
2022-06-15 21:02:18 +00:00
config := createConfig ( cmd )
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!
if ! inMemoryDatabase && postgresURL == "" {
var closeFunc func ( ) error
cmd . Printf ( "Using built-in PostgreSQL (%s)\n" , config . PostgresPath ( ) )
2022-07-27 15:21:21 +00:00
postgresURL , closeFunc , err = startBuiltinPostgres ( ctx , config , logger )
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 ( ) {
2022-07-27 15:21:21 +00:00
cmd . Printf ( "Stopping built-in PostgreSQL...\n" )
2022-06-15 21:02:18 +00:00
// Gracefully shut PostgreSQL down!
_ = closeFunc ( )
2022-07-27 15:21:21 +00:00
cmd . Printf ( "Stopped built-in PostgreSQL\n" )
2022-06-15 21:02:18 +00:00
} ( )
}
2022-03-22 19:17:50 +00:00
listener , err := net . Listen ( "tcp" , address )
if err != nil {
return xerrors . Errorf ( "listen %q: %w" , address , err )
}
defer listener . Close ( )
2022-03-24 19:21:05 +00:00
if tlsEnable {
2022-10-04 11:45:21 +00:00
listener , err = configureServerTLS ( listener , tlsMinVersion , tlsClientAuth , tlsCertFiles , tlsKeyFiles , tlsClientCAFile )
2022-03-24 19:21:05 +00:00
if err != nil {
return xerrors . Errorf ( "configure tls: %w" , err )
}
}
2022-03-22 19:17:50 +00:00
tcpAddr , valid := listener . Addr ( ) . ( * net . TCPAddr )
if ! valid {
return xerrors . New ( "must be listening on tcp" )
}
2022-03-24 15:07:33 +00:00
// If just a port is specified, assume localhost.
2022-03-22 19:17:50 +00:00
if tcpAddr . IP . IsUnspecified ( ) {
tcpAddr . IP = net . IPv4 ( 127 , 0 , 0 , 1 )
}
2022-06-15 21:02:18 +00:00
// If no access URL is specified, fallback to the
// bounds URL.
2022-03-22 19:17:50 +00:00
localURL := & url . URL {
Scheme : "http" ,
Host : tcpAddr . String ( ) ,
}
2022-03-24 19:21:05 +00:00
if tlsEnable {
localURL . Scheme = "https"
}
2022-04-14 15:29:40 +00:00
var (
2022-07-27 15:21:21 +00:00
ctxTunnel , closeTunnel = context . WithCancel ( ctx )
2022-10-07 13:05:56 +00:00
tunnel * devtunnel . Tunnel
tunnelErr <- chan error
2022-04-14 15:29:40 +00:00
)
defer closeTunnel ( )
2022-10-07 13:05:56 +00:00
// If the access URL is empty, we attempt to run a reverse-proxy tunnel
// to make the initial setup really simple.
if accessURL == "" {
cmd . Printf ( "Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL\n" )
tunnel , tunnelErr , err = devtunnel . New ( ctxTunnel , logger . Named ( "devtunnel" ) )
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
}
2022-10-07 13:05:56 +00:00
accessURL = tunnel . URL
2022-03-22 19:17:50 +00:00
}
2022-04-14 15:29:40 +00:00
2022-07-31 22:49:25 +00:00
accessURLParsed , err := parseURL ( ctx , accessURL )
if err != nil {
return xerrors . Errorf ( "parse URL: %w" , err )
}
2022-09-01 01:09:44 +00:00
accessURLPortRaw := accessURLParsed . Port ( )
if accessURLPortRaw == "" {
accessURLPortRaw = "80"
if accessURLParsed . Scheme == "https" {
accessURLPortRaw = "443"
}
}
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.
2022-07-31 22:49:25 +00:00
isLocal , err := isLocalURL ( ctx , accessURLParsed )
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
}
2022-10-07 13:05:56 +00:00
cmd . Printf ( "%s 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 . Warn . Render ( "Warning:" ) , cliui . Styles . Field . Render ( accessURLParsed . String ( ) ) , reason )
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.
cmd . Printf ( "\nView the Web UI: %s\n" , accessURLParsed . 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
}
2022-04-06 00:18:26 +00:00
sshKeygenAlgorithm , err := gitsshkey . ParseAlgorithm ( sshKeygenAlgorithmRaw )
if err != nil {
return xerrors . Errorf ( "parse ssh keygen algorithm %s: %w" , sshKeygenAlgorithmRaw , err )
}
2022-08-25 19:32:35 +00:00
// Validate provided auto-import templates.
var (
validatedAutoImportTemplates = make ( [ ] coderd . AutoImportTemplate , len ( autoImportTemplates ) )
seenValidatedAutoImportTemplates = make ( map [ coderd . AutoImportTemplate ] struct { } , len ( autoImportTemplates ) )
)
for i , autoImportTemplate := range autoImportTemplates {
var v coderd . AutoImportTemplate
switch autoImportTemplate {
case "kubernetes" :
v = coderd . AutoImportTemplateKubernetes
default :
return xerrors . Errorf ( "auto import template %q is not supported" , autoImportTemplate )
}
if _ , ok := seenValidatedAutoImportTemplates [ v ] ; ok {
return xerrors . Errorf ( "auto import template %q is specified more than once" , v )
}
seenValidatedAutoImportTemplates [ v ] = struct { } { }
validatedAutoImportTemplates [ i ] = v
}
2022-09-02 23:47:25 +00:00
defaultRegion := & tailcfg . DERPRegion {
2022-09-26 17:56:04 +00:00
EmbeddedRelay : true ,
RegionID : derpServerRegionID ,
RegionCode : derpServerRegionCode ,
RegionName : derpServerRegionName ,
2022-09-01 01:09:44 +00:00
Nodes : [ ] * tailcfg . DERPNode { {
Name : fmt . Sprintf ( "%db" , derpServerRegionID ) ,
RegionID : derpServerRegionID ,
HostName : accessURLParsed . Hostname ( ) ,
DERPPort : accessURLPort ,
STUNPort : - 1 ,
ForceHTTP : accessURLParsed . Scheme == "http" ,
} } ,
2022-09-02 23:47:25 +00:00
}
if ! derpServerEnabled {
defaultRegion = nil
}
2022-09-11 21:45:49 +00:00
derpMap , err := tailnet . NewDERPMap ( ctx , defaultRegion , derpServerSTUNAddrs , derpConfigURL , derpConfigPath )
2022-09-01 01:09:44 +00:00
if err != nil {
return xerrors . Errorf ( "create derp map: %w" , err )
}
2022-09-22 22:30:32 +00:00
appHostname := strings . TrimPrefix ( wildcardAccessURL , "http://" )
appHostname = strings . TrimPrefix ( appHostname , "https://" )
appHostname = strings . TrimPrefix ( appHostname , "*." )
2022-03-24 15:07:33 +00:00
options := & coderd . Options {
2022-09-01 19:58:23 +00:00
AccessURL : accessURLParsed ,
2022-09-22 22:30:32 +00:00
AppHostname : appHostname ,
2022-09-01 19:58:23 +00:00
Logger : logger . Named ( "coderd" ) ,
Database : databasefake . New ( ) ,
DERPMap : derpMap ,
Pubsub : database . NewPubsubInMemory ( ) ,
CacheDir : cacheDir ,
GoogleTokenValidator : googleTokenValidator ,
SecureAuthCookie : secureAuthCookie ,
SSHKeygenAlgorithm : sshKeygenAlgorithm ,
TracerProvider : tracerProvider ,
Telemetry : telemetry . NewNoop ( ) ,
AutoImportTemplates : validatedAutoImportTemplates ,
MetricsCacheRefreshInterval : metricsCacheRefreshInterval ,
AgentStatsRefreshInterval : agentStatRefreshInterval ,
2022-10-04 19:45:00 +00:00
Experimental : ExperimentalEnabled ( cmd ) ,
2022-03-24 15:07:33 +00:00
}
2022-04-23 22:58:57 +00:00
if oauth2GithubClientSecret != "" {
2022-08-09 01:49:51 +00:00
options . GithubOAuth2Config , err = configureGithubOAuth2 ( accessURLParsed , oauth2GithubClientID , oauth2GithubClientSecret , oauth2GithubAllowSignups , oauth2GithubAllowedOrganizations , oauth2GithubAllowedTeams , oauth2GithubEnterpriseBaseURL )
2022-04-23 22:58:57 +00:00
if err != nil {
return xerrors . Errorf ( "configure github oauth2: %w" , err )
}
}
2022-08-01 04:05:35 +00:00
if oidcClientSecret != "" {
if oidcClientID == "" {
return xerrors . Errorf ( "OIDC client ID be set!" )
}
if oidcIssuerURL == "" {
return xerrors . Errorf ( "OIDC issuer URL must be set!" )
}
oidcProvider , err := oidc . NewProvider ( ctx , oidcIssuerURL )
if err != nil {
return xerrors . Errorf ( "configure oidc provider: %w" , err )
}
redirectURL , err := accessURLParsed . Parse ( "/api/v2/users/oidc/callback" )
if err != nil {
return xerrors . Errorf ( "parse oidc oauth callback url: %w" , err )
}
options . OIDCConfig = & coderd . OIDCConfig {
OAuth2Config : & oauth2 . Config {
ClientID : oidcClientID ,
ClientSecret : oidcClientSecret ,
RedirectURL : redirectURL . String ( ) ,
Endpoint : oidcProvider . Endpoint ( ) ,
Scopes : oidcScopes ,
} ,
Verifier : oidcProvider . Verifier ( & oidc . Config {
ClientID : oidcClientID ,
} ) ,
EmailDomain : oidcEmailDomain ,
AllowSignups : oidcAllowSignups ,
}
}
2022-06-15 21:02:18 +00:00
if inMemoryDatabase {
options . Database = databasefake . New ( )
options . Pubsub = database . NewPubsubInMemory ( )
} else {
2022-05-20 15:51:06 +00:00
sqlDB , err := sql . Open ( sqlDriver , postgresURL )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "dial postgres: %w" , err )
}
2022-07-27 15:21:21 +00:00
defer sqlDB . Close ( )
2022-03-24 15:07:33 +00:00
err = sqlDB . Ping ( )
if err != nil {
return xerrors . Errorf ( "ping postgres: %w" , err )
}
2022-09-19 17:39:02 +00:00
err = migrations . Up ( sqlDB )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "migrate up: %w" , err )
}
options . Database = database . New ( sqlDB )
2022-07-27 15:21:21 +00:00
options . Pubsub , err = database . NewPubsub ( ctx , sqlDB , postgresURL )
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
}
2022-07-27 15:21:21 +00:00
deploymentID , err := options . Database . GetDeploymentID ( ctx )
2022-06-17 05:26:40 +00:00
if errors . Is ( err , sql . ErrNoRows ) {
err = nil
}
if err != nil {
return xerrors . Errorf ( "get deployment id: %w" , err )
}
if deploymentID == "" {
deploymentID = uuid . NewString ( )
2022-07-27 15:21:21 +00:00
err = options . Database . InsertDeploymentID ( ctx , deploymentID )
2022-06-17 05:26:40 +00:00
if err != nil {
return xerrors . Errorf ( "set deployment id: %w" , err )
}
}
// Parse the raw telemetry URL!
2022-07-31 22:49:25 +00:00
telemetryURL , err := parseURL ( ctx , telemetryURL )
2022-06-17 05:26:40 +00:00
if err != nil {
return xerrors . Errorf ( "parse telemetry url: %w" , err )
}
2022-06-17 14:02:44 +00:00
// Disable telemetry if the in-memory database is used unless explicitly defined!
if inMemoryDatabase && ! cmd . Flags ( ) . Changed ( "telemetry" ) {
telemetryEnable = false
}
if telemetryEnable {
2022-06-17 05:26:40 +00:00
options . Telemetry , err = telemetry . New ( telemetry . Options {
BuiltinPostgres : builtinPostgres ,
DeploymentID : deploymentID ,
Database : options . Database ,
Logger : logger . Named ( "telemetry" ) ,
URL : telemetryURL ,
GitHubOAuth : oauth2GithubClientID != "" ,
2022-08-01 04:05:35 +00:00
OIDCAuth : oidcClientID != "" ,
OIDCIssuerURL : oidcIssuerURL ,
2022-06-17 05:26:40 +00:00
Prometheus : promEnabled ,
2022-09-20 00:46:29 +00:00
STUN : len ( derpServerSTUNAddrs ) != 0 ,
2022-10-07 13:05:56 +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
if pprofEnabled {
//nolint:revive
defer serveHandler ( ctx , logger , nil , pprofAddress , "pprof" ) ( )
}
if promEnabled {
options . PrometheusRegistry = prometheus . NewRegistry ( )
2022-08-09 01:08:42 +00:00
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 ( )
closeWorkspacesFunc , err := prometheusmetrics . Workspaces ( ctx , options . PrometheusRegistry , options . Database , 0 )
if err != nil {
return xerrors . Errorf ( "register workspaces prometheus metric: %w" , err )
}
defer closeWorkspacesFunc ( )
2022-08-08 15:09:46 +00:00
//nolint:revive
defer serveHandler ( ctx , logger , promhttp . InstrumentMetricHandler (
options . PrometheusRegistry , promhttp . HandlerFor ( options . PrometheusRegistry , promhttp . HandlerOpts { } ) ,
) , promAddress , "prometheus" ) ( )
}
2022-09-20 04:11:01 +00:00
coderAPI , err := newAPI ( ctx , options )
if err != nil {
return err
}
2022-07-27 15:21:21 +00:00
defer coderAPI . Close ( )
2022-03-22 19:17:50 +00:00
client := codersdk . New ( localURL )
2022-03-24 19:21:05 +00:00
if tlsEnable {
2022-03-25 00:20:13 +00:00
// Secure transport isn't needed for locally communicating!
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-03-22 19:17:50 +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 ( )
}
} ( )
2022-03-28 19:26:41 +00:00
for i := 0 ; uint8 ( i ) < provisionerDaemonCount ; i ++ {
2022-07-27 15:21:21 +00:00
daemon , err := newProvisionerDaemon ( ctx , coderAPI , logger , cacheDir , errCh , false )
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
// 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
2022-07-27 15:21:21 +00:00
server := & http . Server {
// These errors are typically noise like "TLS: EOF". Vault does similar:
// 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-09-20 04:11:01 +00:00
Handler : coderAPI . RootHandler ,
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-09-20 04:35:18 +00:00
_ = shutdownWithTimeout ( server . Shutdown , 5 * time . Second )
2022-03-24 15:07:33 +00:00
} ( )
2022-03-22 19:17:50 +00:00
2022-07-27 15:21:21 +00:00
eg := errgroup . Group { }
eg . Go ( func ( ) error {
// Make sure to close the tunnel listener if we exit so the
// errgroup doesn't wait forever!
2022-10-07 13:05:56 +00:00
if tunnel != nil {
defer tunnel . Listener . Close ( )
2022-03-24 19:21:05 +00:00
}
2022-06-10 18:38:11 +00:00
2022-07-27 15:21:21 +00:00
return server . Serve ( listener )
} )
2022-10-07 13:05:56 +00:00
if tunnel != nil {
2022-07-27 15:21:21 +00:00
eg . Go ( func ( ) error {
defer listener . Close ( )
2022-06-10 18:38:11 +00:00
2022-10-07 13:05:56 +00:00
return server . Serve ( tunnel . Listener )
2022-06-10 18:38:11 +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
} ( )
2022-07-27 15:21:21 +00:00
hasFirstUser , err := client . HasFirstUser ( ctx )
2022-06-15 21:02:18 +00:00
if ! hasFirstUser && err == nil {
cmd . Println ( )
cmd . Println ( "Get started by creating the first user (in a new terminal):" )
2022-07-31 22:49:25 +00:00
cmd . Println ( cliui . Styles . Code . Render ( "coder login " + accessURLParsed . String ( ) ) )
2022-03-22 19:17:50 +00:00
}
2022-06-15 21:02:18 +00:00
cmd . Println ( "\n==> Logs will stream in below (press ctrl+c to gracefully exit):" )
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 )
}
2022-05-20 10:57:02 +00:00
autobuildPoller := time . NewTicker ( autobuildPollInterval )
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
// 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.
_ = config . URL ( ) . Write ( client . URL . String ( ) )
// 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 ( )
2022-07-27 15:21:21 +00:00
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , cliui . Styles . Bold . Render (
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ,
) )
2022-10-07 13:05:56 +00:00
case exitErr = <- tunnelErr :
2022-07-27 15:21:21 +00:00
if exitErr == nil {
exitErr = xerrors . New ( "dev tunnel closed unexpectedly" )
2022-04-14 15:29:40 +00:00
}
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 ) {
cmd . Printf ( "Unexpected error, shutting down server: %s\n" , exitErr )
}
// 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 {
2022-07-27 15:21:21 +00:00
cmd . Printf ( "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.
cmd . Println ( "Shutting down API server..." )
2022-10-07 13:05:56 +00:00
err = shutdownWithTimeout ( server . Shutdown , 3 * time . Second )
2022-07-27 15:21:21 +00:00
if err != nil {
2022-10-07 13:05:56 +00:00
cmd . Printf ( "API server shutdown took longer than 3s: %s\n" , err )
2022-07-27 15:21:21 +00:00
} else {
cmd . Printf ( "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 ( )
if verbose {
cmd . Printf ( "Shutting down provisioner daemon %d...\n" , id )
}
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 {
cmd . PrintErrf ( "Failed to shutdown provisioner daemon %d: %s\n" , id , err )
return
}
err = provisionerDaemon . Close ( )
if err != nil {
cmd . PrintErrf ( "Close provisioner daemon %d: %s\n" , id , err )
return
}
if verbose {
cmd . Printf ( "Gracefully shut down provisioner daemon %d\n" , id )
}
} ( )
}
wg . Wait ( )
cmd . Println ( "Waiting for WebSocket connections to close..." )
_ = coderAPI . Close ( )
2022-09-13 17:31:33 +00:00
cmd . Println ( "Done waiting for WebSocket connections" )
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 {
2022-06-15 21:02:18 +00:00
cmd . Println ( "Waiting for tunnel to close..." )
2022-04-14 15:29:40 +00:00
closeTunnel ( )
2022-10-07 13:05:56 +00:00
<- tunnelErr
2022-07-27 15:21:21 +00:00
cmd . Println ( "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 ( )
2022-09-20 04:56:51 +00:00
if xerrors . Is ( exitErr , context . Canceled ) {
return nil
}
2022-07-27 15:21:21 +00:00
return exitErr
2022-03-22 19:17:50 +00:00
} ,
}
2022-03-28 19:26:41 +00:00
2022-06-15 21:02:18 +00:00
root . AddCommand ( & cobra . Command {
Use : "postgres-builtin-url" ,
Short : "Output the connection URL for the built-in PostgreSQL deployment." ,
2022-08-29 11:37:18 +00:00
RunE : func ( cmd * cobra . Command , _ [ ] string ) error {
2022-06-15 21:02:18 +00:00
cfg := createConfig ( cmd )
url , err := embeddedPostgresURL ( cfg )
if err != nil {
return err
}
2022-08-29 11:37:18 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , "psql %q\n" , url )
2022-06-15 21:02:18 +00:00
return nil
} ,
} )
2022-07-14 21:51:44 +00:00
root . AddCommand ( & cobra . Command {
Use : "postgres-builtin-serve" ,
Short : "Run the built-in PostgreSQL deployment." ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
cfg := createConfig ( cmd )
2022-07-26 14:23:32 +00:00
logger := slog . Make ( sloghuman . Sink ( cmd . ErrOrStderr ( ) ) )
2022-07-14 21:51:44 +00:00
if verbose {
logger = logger . Leveled ( slog . LevelDebug )
}
url , closePg , err := startBuiltinPostgres ( cmd . Context ( ) , cfg , logger )
if err != nil {
return err
}
defer func ( ) { _ = closePg ( ) } ( )
cmd . Println ( cliui . Styles . Code . Render ( "psql \"" + url + "\"" ) )
stopChan := make ( chan os . Signal , 1 )
defer signal . Stop ( stopChan )
signal . Notify ( stopChan , os . Interrupt )
<- stopChan
return nil
} ,
} )
2022-09-19 16:36:18 +00:00
cliflag . DurationVarP ( root . Flags ( ) , & autobuildPollInterval , "autobuild-poll-interval" , "" , "CODER_AUTOBUILD_POLL_INTERVAL" , time . Minute ,
"Interval to poll for scheduled workspace builds." )
_ = root . Flags ( ) . MarkHidden ( "autobuild-poll-interval" )
cliflag . StringVarP ( root . Flags ( ) , & accessURL , "access-url" , "" , "CODER_ACCESS_URL" , "" ,
"External URL to access your deployment. This must be accessible by all provisioned workspaces." )
cliflag . StringVarP ( root . Flags ( ) , & address , "address" , "a" , "CODER_ADDRESS" , "127.0.0.1:3000" ,
"Bind address of the server." )
2022-09-22 22:30:32 +00:00
cliflag . StringVarP ( root . Flags ( ) , & wildcardAccessURL , "wildcard-access-url" , "" , "CODER_WILDCARD_ACCESS_URL" , "" , ` Specifies the wildcard hostname to use for workspace applications in the form "*.example.com". ` )
2022-09-01 01:09:44 +00:00
cliflag . StringVarP ( root . Flags ( ) , & derpConfigURL , "derp-config-url" , "" , "CODER_DERP_CONFIG_URL" , "" ,
2022-09-19 16:36:18 +00:00
"URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/" )
2022-09-11 21:45:49 +00:00
cliflag . StringVarP ( root . Flags ( ) , & derpConfigPath , "derp-config-path" , "" , "CODER_DERP_CONFIG_PATH" , "" ,
2022-09-19 16:36:18 +00:00
"Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/" )
cliflag . BoolVarP ( root . Flags ( ) , & derpServerEnabled , "derp-server-enable" , "" , "CODER_DERP_SERVER_ENABLE" , true ,
"Whether to enable or disable the embedded DERP relay server." )
cliflag . IntVarP ( root . Flags ( ) , & derpServerRegionID , "derp-server-region-id" , "" , "CODER_DERP_SERVER_REGION_ID" , 999 ,
"Region ID to use for the embedded DERP server." )
cliflag . StringVarP ( root . Flags ( ) , & derpServerRegionCode , "derp-server-region-code" , "" , "CODER_DERP_SERVER_REGION_CODE" , "coder" ,
"Region code that for the embedded DERP server." )
cliflag . StringVarP ( root . Flags ( ) , & derpServerRegionName , "derp-server-region-name" , "" , "CODER_DERP_SERVER_REGION_NAME" , "Coder Embedded Relay" ,
"Region name that for the embedded DERP server." )
2022-09-01 01:09:44 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & derpServerSTUNAddrs , "derp-server-stun-addresses" , "" , "CODER_DERP_SERVER_STUN_ADDRESSES" , [ ] string {
"stun.l.google.com:19302" ,
2022-09-19 16:36:18 +00:00
} , "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections." )
cliflag . BoolVarP ( root . Flags ( ) , & promEnabled , "prometheus-enable" , "" , "CODER_PROMETHEUS_ENABLE" , false ,
"Serve prometheus metrics on the address defined by `prometheus-address`." )
cliflag . StringVarP ( root . Flags ( ) , & promAddress , "prometheus-address" , "" , "CODER_PROMETHEUS_ADDRESS" , "127.0.0.1:2112" ,
"The bind address to serve prometheus metrics." )
cliflag . BoolVarP ( root . Flags ( ) , & pprofEnabled , "pprof-enable" , "" , "CODER_PPROF_ENABLE" , false ,
"Serve pprof metrics on the address defined by `pprof-address`." )
cliflag . StringVarP ( root . Flags ( ) , & pprofAddress , "pprof-address" , "" , "CODER_PPROF_ADDRESS" , "127.0.0.1:6060" ,
"The bind address to serve pprof." )
2022-09-23 19:26:29 +00:00
defaultCacheDir , err := os . UserCacheDir ( )
if err != nil {
defaultCacheDir = os . TempDir ( )
}
2022-06-10 14:00:00 +00:00
if dir := os . Getenv ( "CACHE_DIRECTORY" ) ; dir != "" {
// For compatibility with systemd.
defaultCacheDir = dir
}
2022-09-23 19:26:29 +00:00
defaultCacheDir = filepath . Join ( defaultCacheDir , "coder" )
2022-09-19 16:36:18 +00:00
cliflag . StringVarP ( root . Flags ( ) , & cacheDir , "cache-dir" , "" , "CODER_CACHE_DIRECTORY" , defaultCacheDir ,
"The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd." )
2022-06-15 21:02:18 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & inMemoryDatabase , "in-memory" , "" , "CODER_INMEMORY" , false ,
2022-09-19 16:36:18 +00:00
"Controls whether data will be stored in an in-memory database." )
2022-06-15 21:02:18 +00:00
_ = root . Flags ( ) . MarkHidden ( "in-memory" )
2022-09-19 16:36:18 +00:00
cliflag . StringVarP ( root . Flags ( ) , & postgresURL , "postgres-url" , "" , "CODER_PG_CONNECTION_URL" , "" ,
"URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\"" )
cliflag . Uint8VarP ( root . Flags ( ) , & provisionerDaemonCount , "provisioner-daemons" , "" , "CODER_PROVISIONER_DAEMONS" , 3 ,
"Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this." )
2022-04-23 22:58:57 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oauth2GithubClientID , "oauth2-github-client-id" , "" , "CODER_OAUTH2_GITHUB_CLIENT_ID" , "" ,
2022-09-19 16:36:18 +00:00
"Client ID for Login with GitHub." )
2022-04-23 22:58:57 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oauth2GithubClientSecret , "oauth2-github-client-secret" , "" , "CODER_OAUTH2_GITHUB_CLIENT_SECRET" , "" ,
2022-09-19 16:36:18 +00:00
"Client secret for Login with GitHub." )
2022-04-23 22:58:57 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & oauth2GithubAllowedOrganizations , "oauth2-github-allowed-orgs" , "" , "CODER_OAUTH2_GITHUB_ALLOWED_ORGS" , nil ,
2022-09-19 16:36:18 +00:00
"Organizations the user must be a member of to Login with GitHub." )
2022-07-09 02:37:18 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & oauth2GithubAllowedTeams , "oauth2-github-allowed-teams" , "" , "CODER_OAUTH2_GITHUB_ALLOWED_TEAMS" , nil ,
2022-09-19 16:36:18 +00:00
"Teams inside organizations the user must be a member of to Login with GitHub. Structured as: <organization-name>/<team-slug>." )
2022-04-23 22:58:57 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & oauth2GithubAllowSignups , "oauth2-github-allow-signups" , "" , "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS" , false ,
2022-09-19 16:36:18 +00:00
"Whether new users can sign up with GitHub." )
2022-08-09 01:49:51 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oauth2GithubEnterpriseBaseURL , "oauth2-github-enterprise-base-url" , "" , "CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL" , "" ,
2022-09-19 16:36:18 +00:00
"Base URL of a GitHub Enterprise deployment to use for Login with GitHub." )
2022-08-01 04:05:35 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & oidcAllowSignups , "oidc-allow-signups" , "" , "CODER_OIDC_ALLOW_SIGNUPS" , true ,
2022-09-19 16:36:18 +00:00
"Whether new users can sign up with OIDC." )
2022-08-01 04:05:35 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oidcClientID , "oidc-client-id" , "" , "CODER_OIDC_CLIENT_ID" , "" ,
2022-09-19 16:36:18 +00:00
"Client ID to use for Login with OIDC." )
2022-08-01 04:05:35 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oidcClientSecret , "oidc-client-secret" , "" , "CODER_OIDC_CLIENT_SECRET" , "" ,
2022-09-19 16:36:18 +00:00
"Client secret to use for Login with OIDC." )
2022-08-01 04:05:35 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oidcEmailDomain , "oidc-email-domain" , "" , "CODER_OIDC_EMAIL_DOMAIN" , "" ,
2022-09-19 16:36:18 +00:00
"Email domain that clients logging in with OIDC must match." )
2022-08-01 04:05:35 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oidcIssuerURL , "oidc-issuer-url" , "" , "CODER_OIDC_ISSUER_URL" , "" ,
2022-09-19 16:36:18 +00:00
"Issuer URL to use for Login with OIDC." )
2022-08-01 04:05:35 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & oidcScopes , "oidc-scopes" , "" , "CODER_OIDC_SCOPES" , [ ] string { oidc . ScopeOpenID , "profile" , "email" } ,
2022-09-19 16:36:18 +00:00
"Scopes to grant when authenticating with OIDC." )
2022-09-13 20:55:56 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & tailscaleEnable , "tailscale" , "" , "CODER_TAILSCALE" , true ,
2022-09-01 01:09:44 +00:00
"Specifies whether Tailscale networking is used for web applications and terminals." )
_ = root . Flags ( ) . MarkHidden ( "tailscale" )
2022-07-26 13:27:48 +00:00
enableTelemetryByDefault := ! isTest ( )
2022-09-19 16:36:18 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & telemetryEnable , "telemetry" , "" , "CODER_TELEMETRY" , enableTelemetryByDefault ,
"Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product." )
2022-09-22 16:53:08 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & telemetryTraceEnable , "telemetry-trace" , "" , "CODER_TELEMETRY_TRACE" , enableTelemetryByDefault ,
"Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option." )
2022-09-19 16:36:18 +00:00
cliflag . StringVarP ( root . Flags ( ) , & telemetryURL , "telemetry-url" , "" , "CODER_TELEMETRY_URL" , "https://telemetry.coder.com" ,
"URL to send telemetry." )
2022-06-17 05:26:40 +00:00
_ = root . Flags ( ) . MarkHidden ( "telemetry-url" )
2022-09-19 16:36:18 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & tlsEnable , "tls-enable" , "" , "CODER_TLS_ENABLE" , false ,
"Whether TLS will be enabled." )
2022-10-04 11:45:21 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & tlsCertFiles , "tls-cert-file" , "" , "CODER_TLS_CERT_FILE" , [ ] string { } ,
"Path to each certificate for TLS. It requires a PEM-encoded file. " +
2022-03-24 19:21:05 +00:00
"To configure the listener to use a CA certificate, concatenate the primary certificate " +
2022-09-19 16:36:18 +00:00
"and the CA certificate together. The primary certificate should appear first in the combined file." )
2022-03-28 19:26:41 +00:00
cliflag . StringVarP ( root . Flags ( ) , & tlsClientCAFile , "tls-client-ca-file" , "" , "CODER_TLS_CLIENT_CA_FILE" , "" ,
"PEM-encoded Certificate Authority file used for checking the authenticity of client" )
cliflag . StringVarP ( root . Flags ( ) , & tlsClientAuth , "tls-client-auth" , "" , "CODER_TLS_CLIENT_AUTH" , "request" ,
2022-09-19 16:36:18 +00:00
` Policy the server will follow for TLS Client Authentication. ` +
2022-03-28 19:26:41 +00:00
` Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify" ` )
2022-10-04 11:45:21 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & tlsKeyFiles , "tls-key-file" , "" , "CODER_TLS_KEY_FILE" , [ ] string { } ,
"Paths to the private keys for each of the certificates. It requires a PEM-encoded file" )
2022-03-28 19:26:41 +00:00
cliflag . StringVarP ( root . Flags ( ) , & tlsMinVersion , "tls-min-version" , "" , "CODER_TLS_MIN_VERSION" , "tls12" ,
2022-09-19 16:36:18 +00:00
` Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13" ` )
cliflag . BoolVarP ( root . Flags ( ) , & traceEnable , "trace" , "" , "CODER_TRACE" , false ,
"Whether application tracing data is collected." )
cliflag . BoolVarP ( root . Flags ( ) , & secureAuthCookie , "secure-auth-cookie" , "" , "CODER_SECURE_AUTH_COOKIE" , false ,
"Controls if the 'Secure' property is set on browser session cookies" )
cliflag . StringVarP ( root . Flags ( ) , & sshKeygenAlgorithmRaw , "ssh-keygen-algorithm" , "" , "CODER_SSH_KEYGEN_ALGORITHM" , "ed25519" ,
"The algorithm to use for generating ssh keys. " +
` Accepted values are "ed25519", "ecdsa", or "rsa4096" ` )
cliflag . StringArrayVarP ( root . Flags ( ) , & autoImportTemplates , "auto-import-template" , "" , "CODER_TEMPLATE_AUTOIMPORT" , [ ] string { } ,
"Templates to auto-import. Available auto-importable templates are: kubernetes" )
_ = root . Flags ( ) . MarkHidden ( "auto-import-template" )
cliflag . BoolVarP ( root . Flags ( ) , & spooky , "spooky" , "" , "" , false , "Specifies spookiness level..." )
2022-04-19 16:40:01 +00:00
_ = root . Flags ( ) . MarkHidden ( "spooky" )
2022-09-19 16:36:18 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & verbose , "verbose" , "v" , "CODER_VERBOSE" , false ,
"Enables verbose logging." )
2022-09-01 19:58:23 +00:00
// These metrics flags are for manually testing the metric system.
// The defaults should be acceptable for any Coder deployment of any
// reasonable size.
cliflag . DurationVarP ( root . Flags ( ) , & metricsCacheRefreshInterval , "metrics-cache-refresh-interval" , "" , "CODER_METRICS_CACHE_REFRESH_INTERVAL" , time . Hour , "How frequently metrics are refreshed" )
_ = root . Flags ( ) . MarkHidden ( "metrics-cache-refresh-interval" )
cliflag . DurationVarP ( root . Flags ( ) , & agentStatRefreshInterval , "agent-stats-refresh-interval" , "" , "CODER_AGENT_STATS_REFRESH_INTERVAL" , time . Minute * 10 , "How frequently agent stats are recorded" )
2022-09-19 16:36:18 +00:00
_ = root . Flags ( ) . MarkHidden ( "agent-stats-refresh-interval" )
2022-03-22 19:17:50 +00:00
return root
}
2022-07-31 22:49:25 +00:00
// parseURL parses a string into a URL. It works around some technically correct
// but undesired behavior of url.Parse by prepending a scheme if one does not
// exist so that the URL does not get parsed improprely.
func parseURL ( ctx context . Context , u string ) ( * url . URL , error ) {
var (
hasScheme = strings . HasPrefix ( u , "http:" ) || strings . HasPrefix ( u , "https:" )
)
if ! hasScheme {
// Append a scheme if it doesn't have one. Otherwise the hostname
// will likely get parsed as the scheme and cause methods like Hostname()
// to return an empty string, largely obviating the purpose of this
// function.
u = "https://" + u
}
parsed , err := url . Parse ( u )
if err != nil {
return nil , err
}
// If the specified url is a loopback device and no scheme has been
// specified, prefer http over https. It's unlikely anyone intends to use
// https on a loopback and if they do they can specify a scheme.
if local , _ := isLocalURL ( ctx , parsed ) ; local && ! hasScheme {
parsed . Scheme = "http"
}
return parsed , nil
}
// 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 ,
logger slog . Logger ,
cacheDir string ,
errCh chan error ,
dev bool ,
2022-07-27 15:21:21 +00:00
) ( srv * provisionerd . Server , err error ) {
ctx , cancel := context . WithCancel ( ctx )
defer func ( ) {
if err != nil {
cancel ( )
}
} ( )
err = os . MkdirAll ( cacheDir , 0 o700 )
2022-03-30 22:59:54 +00:00
if err != nil {
return nil , xerrors . Errorf ( "mkdir %q: %w" , cacheDir , err )
}
2022-03-22 19:17:50 +00:00
terraformClient , terraformServer := provisionersdk . TransportPipe ( )
go func ( ) {
2022-07-27 15:21:21 +00:00
<- ctx . Done ( )
_ = terraformClient . Close ( )
_ = terraformServer . Close ( )
} ( )
go func ( ) {
defer cancel ( )
2022-03-22 19:17:50 +00:00
err := terraform . Serve ( ctx , & terraform . ServeOptions {
ServeOptions : & provisionersdk . ServeOptions {
Listener : terraformServer ,
} ,
2022-03-28 19:57:19 +00:00
CachePath : cacheDir ,
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 {
string ( database . ProvisionerTypeTerraform ) : proto . NewDRPCProvisionerClient ( provisionersdk . Conn ( terraformClient ) ) ,
}
// include echo provisioner when in dev mode
if dev {
echoClient , echoServer := provisionersdk . TransportPipe ( )
go func ( ) {
2022-07-27 15:21:21 +00:00
<- ctx . Done ( )
_ = echoClient . Close ( )
_ = echoServer . Close ( )
} ( )
go func ( ) {
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
}
} ( )
provisioners [ string ( database . ProvisionerTypeEcho ) ] = proto . NewDRPCProvisionerClient ( provisionersdk . Conn ( echoClient ) )
}
2022-05-26 03:14:08 +00:00
return provisionerd . New ( coderAPI . ListenProvisionerDaemon , & provisionerd . Options {
2022-03-22 19:17:50 +00:00
Logger : logger ,
2022-04-11 23:54:30 +00:00
PollInterval : 500 * time . Millisecond ,
UpdateInterval : 500 * time . Millisecond ,
2022-05-19 22:47:45 +00:00
Provisioners : provisioners ,
WorkDirectory : tempDir ,
2022-09-16 16:43:22 +00:00
Tracer : coderAPI . TracerProvider ,
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
func printLogo ( cmd * cobra . Command , spooky bool ) {
if spooky {
2022-08-22 22:02:50 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , ` ▄ █ █ █ █ ▄ ▒ █ █ █ █ █ ▓ █ █ █ █ █ ▄ ▓ █ █ █ █ █ █ █ ▀ █ █ █
2022-06-15 21:02:18 +00:00
▒ █ █ ▀ ▀ █ ▒ █ █ ▒ █ █ ▒ ▒ █ █ ▀ █ █ ▌ ▓ █ ▀ ▓ █ █ ▒ █ █ ▒
▒ ▓ █ ▄ ▒ █ █ ░ █ █ ▒ ░ █ █ █ ▌ ▒ █ █ █ ▓ █ █ ░ ▄ █ ▒
2022-08-22 22:02:50 +00:00
▒ ▓ ▓ ▄ ▄ █ █ ▒ ▒ █ █ █ █ ░ ░ ▓ █ ▄ ▌ ▒ ▓ █ ▄ ▒ █ █ ▀ ▀ █ ▄
2022-06-15 21:02:18 +00:00
▒ ▓ █ █ █ ▀ ░ ░ █ █ █ █ ▓ ▒ ░ ░ ▒ █ █ █ █ ▓ ░ ▒ █ █ █ █ ▒ ░ █ █ ▓ ▒ █ █ ▒
░ ░ ▒ ▒ ░ ░ ▒ ░ ▒ ░ ▒ ░ ▒ ▒ ▓ ▒ ░ ░ ▒ ░ ░ ░ ▒ ▓ ░ ▒ ▓ ░
░ ▒ ░ ▒ ▒ ░ ░ ▒ ▒ ░ ░ ░ ░ ▒ ░ ▒ ░
2022-08-22 22:02:50 +00:00
░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░
░ ░
2022-04-19 16:40:01 +00:00
` )
return
}
2022-06-15 21:02:18 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , "%s - Remote development on your infrastucture\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" )
}
if len ( tlsCertFiles ) == 0 {
return nil , xerrors . New ( "--tls-cert-file is required when tls is enabled" )
}
if len ( tlsKeyFiles ) == 0 {
return nil , xerrors . New ( "--tls-key-file is required when tls is enabled" )
}
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 {
return nil , xerrors . Errorf ( "load TLS key pair %d (%q, %q): %w" , i , certFile , keyFile , err )
}
certs [ i ] = cert
}
return certs , nil
}
func configureServerTLS ( listener net . Listener , tlsMinVersion , tlsClientAuth string , tlsCertFiles , tlsKeyFiles [ ] string , tlsClientCAFile string ) ( net . Listener , 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
}
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
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 {
return nil , xerrors . Errorf ( "read %q: %w" , tlsClientCAFile , err )
}
if ! caPool . AppendCertsFromPEM ( data ) {
return nil , xerrors . Errorf ( "failed to parse CA certificate in tls-client-ca-file" )
}
tlsConfig . ClientCAs = caPool
}
return tls . NewListener ( listener , tlsConfig ) , nil
}
2022-04-18 17:37:01 +00:00
2022-08-09 01:49:51 +00:00
func configureGithubOAuth2 ( accessURL * url . URL , clientID , clientSecret string , allowSignups 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-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 ,
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-05-03 12:48:02 +00:00
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 ) )
2022-08-29 17:07:49 +00:00
// 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
2022-08-21 22:32:53 +00:00
srv := & http . Server {
2022-08-29 17:07:49 +00:00
Addr : addr ,
Handler : handler ,
2022-08-21 22:32:53 +00:00
}
2022-05-03 12:48:02 +00:00
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 ( ) }
}
2022-06-10 18:35:51 +00:00
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 )
}
pgPort , err := strconv . Atoi ( pgPortRaw )
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
}