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-03-24 19:21:05 +00:00
"encoding/pem"
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-03-28 19:57:19 +00:00
"path/filepath"
2022-03-22 19:17:50 +00:00
"time"
2022-05-19 22:47:45 +00:00
"github.com/coder/coder/provisioner/echo"
2022-03-29 00:19:28 +00:00
"github.com/briandowns/spinner"
"github.com/coreos/go-systemd/daemon"
2022-04-23 22:58:57 +00:00
"github.com/google/go-github/v43/github"
2022-04-18 22:40:25 +00:00
"github.com/pion/turn/v2"
2022-04-25 04:52:07 +00:00
"github.com/pion/webrtc/v3"
2022-05-03 12:48:02 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2022-03-29 00:19:28 +00:00
"github.com/spf13/cobra"
2022-04-23 22:58:57 +00:00
"golang.org/x/oauth2"
xgithub "golang.org/x/oauth2/github"
2022-03-29 00:19:28 +00:00
"golang.org/x/xerrors"
"google.golang.org/api/idtoken"
"google.golang.org/api/option"
2022-05-19 22:43:07 +00:00
sdktrace "go.opentelemetry.io/otel/sdk/trace"
2022-03-29 00:19:28 +00:00
2022-03-22 19:17:50 +00:00
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
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-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-05-19 22:43:07 +00:00
"github.com/coder/coder/coderd/tracing"
2022-04-18 22:40:25 +00:00
"github.com/coder/coder/coderd/turnconn"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/codersdk"
2022-04-28 16:13:44 +00:00
"github.com/coder/coder/cryptorand"
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-04-25 04:52:07 +00:00
// nolint:gocyclo
2022-04-23 17:19:20 +00:00
func server ( ) * cobra . Command {
2022-03-22 19:17:50 +00:00
var (
2022-05-20 10:57:02 +00:00
accessURL string
address string
autobuildPollInterval time . Duration
promEnabled bool
promAddress string
pprofEnabled bool
pprofAddress string
cacheDir string
dev bool
devUserEmail string
devUserPassword string
postgresURL string
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
oauth2GithubClientID string
oauth2GithubClientSecret string
oauth2GithubAllowedOrganizations [ ] string
oauth2GithubAllowSignups bool
tlsCertFile string
tlsClientCAFile string
tlsClientAuth string
tlsEnable bool
tlsKeyFile string
tlsMinVersion string
turnRelayAddress string
2022-05-12 17:37:51 +00:00
tunnel bool
2022-04-25 04:52:07 +00:00
stunServers [ ] string
2022-05-19 22:43:07 +00:00
trace bool
2022-04-23 22:58:57 +00:00
secureAuthCookie bool
sshKeygenAlgorithmRaw string
spooky bool
2022-05-03 21:13:30 +00:00
verbose bool
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-04-18 17:37:01 +00:00
logger := slog . Make ( sloghuman . Sink ( os . Stderr ) )
2022-05-03 21:13:30 +00:00
if verbose {
logger = logger . Leveled ( slog . LevelDebug )
}
2022-05-19 22:43:07 +00:00
var tracerProvider * sdktrace . TracerProvider
var err error
if trace {
tracerProvider , err = tracing . TracerProvider ( cmd . Context ( ) , "coderd" )
if err != nil {
logger . Warn ( cmd . Context ( ) , "failed to start telemetry exporter" , slog . Error ( err ) )
} else {
defer func ( ) {
2022-05-19 23:28:29 +00:00
// allow time for traces to flush even if command context is canceled
2022-05-19 22:43:07 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
_ = tracerProvider . Shutdown ( ctx )
} ( )
}
2022-03-28 22:11:52 +00:00
}
2022-03-24 15:07:33 +00:00
2022-04-19 16:40:01 +00:00
printLogo ( cmd , spooky )
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-03-25 00:20:13 +00:00
listener , err = configureTLS ( listener , tlsMinVersion , tlsClientAuth , tlsCertFile , tlsKeyFile , 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 )
}
localURL := & url . URL {
Scheme : "http" ,
Host : tcpAddr . String ( ) ,
}
2022-03-24 19:21:05 +00:00
if tlsEnable {
localURL . Scheme = "https"
}
if accessURL == "" {
accessURL = localURL . String ( )
2022-04-11 23:54:30 +00:00
} else {
// If an access URL is specified, always skip tunneling.
2022-05-12 17:37:51 +00:00
tunnel = false
2022-03-24 19:21:05 +00:00
}
2022-04-14 15:29:40 +00:00
var (
tunnelErrChan <- chan error
ctxTunnel , closeTunnel = context . WithCancel ( cmd . Context ( ) )
)
defer closeTunnel ( )
2022-03-24 15:07:33 +00:00
// If we're attempting to tunnel in dev-mode, the access URL
// needs to be changed to use the tunnel.
2022-05-12 17:37:51 +00:00
if dev && tunnel {
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) , cliui . Styles . Wrap . Render (
"Coder requires a URL accessible by workspaces you provision. " +
"A free tunnel can be created for simple setup. This will " +
"expose your Coder deployment to a publicly accessible URL. " +
cliui . Styles . Field . Render ( "--access-url" ) + " can be specified instead.\n" ,
) )
2022-03-24 15:07:33 +00:00
2022-05-12 17:37:51 +00:00
// This skips the prompt if the flag is explicitly specified.
if ! cmd . Flags ( ) . Changed ( "tunnel" ) {
_ , err = cliui . Prompt ( cmd , cliui . PromptOptions {
Text : "Would you like to start a tunnel for simple setup?" ,
IsConfirm : true ,
} )
if errors . Is ( err , cliui . Canceled ) {
return err
}
2022-04-11 23:54:30 +00:00
}
2022-03-24 15:07:33 +00:00
if err == nil {
2022-04-14 15:29:40 +00:00
accessURL , tunnelErrChan , err = devtunnel . New ( ctxTunnel , localURL )
2022-03-22 19:17:50 +00:00
if err != nil {
return xerrors . Errorf ( "create tunnel: %w" , err )
}
2022-03-24 15:07:33 +00:00
}
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) )
2022-03-22 19:17:50 +00:00
}
2022-04-14 15:29:40 +00:00
2022-03-22 19:17:50 +00:00
validator , err := idtoken . NewValidator ( cmd . Context ( ) , option . WithoutAuthentication ( ) )
if err != nil {
return err
}
2022-03-24 15:07:33 +00:00
2022-03-24 19:21:05 +00:00
accessURLParsed , err := url . Parse ( accessURL )
if err != nil {
return xerrors . Errorf ( "parse access url %q: %w" , accessURL , 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-04-18 22:40:25 +00:00
turnServer , err := turnconn . New ( & turn . RelayAddressGeneratorStatic {
RelayAddress : net . ParseIP ( turnRelayAddress ) ,
Address : turnRelayAddress ,
} )
if err != nil {
return xerrors . Errorf ( "create turn server: %w" , err )
}
2022-04-25 04:52:07 +00:00
iceServers := make ( [ ] webrtc . ICEServer , 0 )
for _ , stunServer := range stunServers {
iceServers = append ( iceServers , webrtc . ICEServer {
URLs : [ ] string { stunServer } ,
} )
}
2022-03-24 15:07:33 +00:00
options := & coderd . Options {
2022-03-24 19:21:05 +00:00
AccessURL : accessURLParsed ,
2022-04-25 04:52:07 +00:00
ICEServers : iceServers ,
2022-03-24 15:07:33 +00:00
Logger : logger . Named ( "coderd" ) ,
2022-03-22 19:17:50 +00:00
Database : databasefake . New ( ) ,
Pubsub : database . NewPubsubInMemory ( ) ,
GoogleTokenValidator : validator ,
2022-03-31 17:31:06 +00:00
SecureAuthCookie : secureAuthCookie ,
2022-04-06 00:18:26 +00:00
SSHKeygenAlgorithm : sshKeygenAlgorithm ,
2022-04-18 22:40:25 +00:00
TURNServer : turnServer ,
2022-05-19 22:43:07 +00:00
TracerProvider : tracerProvider ,
2022-03-24 15:07:33 +00:00
}
2022-04-23 22:58:57 +00:00
if oauth2GithubClientSecret != "" {
options . GithubOAuth2Config , err = configureGithubOAuth2 ( accessURLParsed , oauth2GithubClientID , oauth2GithubClientSecret , oauth2GithubAllowSignups , oauth2GithubAllowedOrganizations )
if err != nil {
return xerrors . Errorf ( "configure github oauth2: %w" , err )
}
}
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , "access-url: %s\n" , accessURL )
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , "provisioner-daemons: %d\n" , provisionerDaemonCount )
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) )
2022-03-24 15:07:33 +00:00
if ! dev {
sqlDB , err := sql . Open ( "postgres" , postgresURL )
if err != nil {
return xerrors . Errorf ( "dial postgres: %w" , err )
}
err = sqlDB . Ping ( )
if err != nil {
return xerrors . Errorf ( "ping postgres: %w" , err )
}
err = database . MigrateUp ( sqlDB )
if err != nil {
return xerrors . Errorf ( "migrate up: %w" , err )
}
options . Database = database . New ( sqlDB )
options . Pubsub , err = database . NewPubsub ( cmd . Context ( ) , sqlDB , postgresURL )
if err != nil {
return xerrors . Errorf ( "create pubsub: %w" , err )
}
}
2022-05-19 22:47:45 +00:00
coderDaemon := coderd . New ( options )
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-05-03 12:48:02 +00:00
// This prevents the pprof import from being accidentally deleted.
var _ = pprof . Handler
if pprofEnabled {
//nolint:revive
defer serveHandler ( cmd . Context ( ) , logger , nil , pprofAddress , "pprof" ) ( )
}
if promEnabled {
//nolint:revive
defer serveHandler ( cmd . Context ( ) , logger , promhttp . Handler ( ) , promAddress , "prometheus" ) ( )
}
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-03-28 19:26:41 +00:00
for i := 0 ; uint8 ( i ) < provisionerDaemonCount ; i ++ {
2022-05-19 22:47:45 +00:00
daemonClose , err := newProvisionerDaemon ( cmd . Context ( ) , coderDaemon , logger , cacheDir , errCh , dev )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "create provisioner daemon: %w" , err )
}
provisionerDaemons = append ( provisionerDaemons , daemonClose )
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
defer func ( ) {
for _ , provisionerDaemon := range provisionerDaemons {
_ = provisionerDaemon . Close ( )
}
} ( )
2022-03-22 19:17:50 +00:00
2022-03-24 19:21:05 +00:00
shutdownConnsCtx , shutdownConns := context . WithCancel ( cmd . Context ( ) )
defer shutdownConns ( )
2022-03-22 19:17:50 +00:00
go func ( ) {
defer close ( errCh )
2022-03-24 19:21:05 +00:00
server := http . Server {
2022-05-02 01:31:12 +00:00
// These errors are typically noise like "TLS: EOF". Vault does similar:
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
ErrorLog : log . New ( io . Discard , "" , 0 ) ,
2022-05-19 22:47:45 +00:00
Handler : coderDaemon . Handler ( ) ,
2022-03-24 19:21:05 +00:00
BaseContext : func ( _ net . Listener ) context . Context {
return shutdownConnsCtx
} ,
}
errCh <- server . Serve ( listener )
2022-03-22 19:17:50 +00:00
} ( )
2022-03-24 15:07:33 +00:00
config := createConfig ( cmd )
2022-03-22 19:17:50 +00:00
if dev {
2022-04-28 16:13:44 +00:00
if devUserPassword == "" {
devUserPassword , err = cryptorand . String ( 10 )
if err != nil {
return xerrors . Errorf ( "generate random admin password for dev: %w" , err )
}
}
err = createFirstUser ( cmd , client , config , devUserEmail , devUserPassword )
2022-03-22 19:17:50 +00:00
if err != nil {
2022-03-24 15:07:33 +00:00
return xerrors . Errorf ( "create first user: %w" , err )
2022-03-22 19:17:50 +00:00
}
2022-04-28 16:13:44 +00:00
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , "email: %s\n" , devUserEmail )
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , "password: %s\n" , devUserPassword )
2022-04-27 14:59:37 +00:00
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) )
2022-03-24 15:07:33 +00:00
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , cliui . Styles . Wrap . Render ( ` Started in dev mode. All data is in-memory! ` + cliui . Styles . Bold . Render ( "Do not use in production" ) + ` . Press ` +
cliui . Styles . Field . Render ( "ctrl+c" ) + ` to clean up provisioned infrastructure. ` ) + "\n\n" )
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , cliui . Styles . Wrap . Render ( ` Run ` + cliui . Styles . Code . Render ( "coder templates init" ) +
" in a new terminal to start creating workspaces." ) + "\n" )
2022-03-24 15:07:33 +00:00
} else {
// 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 ( ) )
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . Prompt . String ( ) + ` Started in ` +
2022-03-24 15:07:33 +00:00
cliui . Styles . Field . Render ( "production" ) + ` mode. All data is stored in the PostgreSQL provided! Press ` + cliui . Styles . Field . Render ( "ctrl+c" ) + ` to gracefully shutdown. ` ) ) + "\n" )
2022-03-25 00:20:13 +00:00
hasFirstUser , err := client . HasFirstUser ( cmd . Context ( ) )
if ! hasFirstUser && err == nil {
// This could fail for a variety of TLS-related reasons.
// This is a helpful starter message, and not critical for user interaction.
2022-04-25 23:30:22 +00:00
_ , _ = fmt . Fprint ( cmd . ErrOrStderr ( ) , cliui . Styles . Paragraph . Render ( cliui . Styles . Wrap . Render ( cliui . Styles . FocusedPrompt . String ( ) + ` Run ` + cliui . Styles . Code . Render ( "coder login " + accessURL ) + " in a new terminal to get started.\n" ) ) )
2022-03-22 19:17:50 +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 )
}
2022-05-20 10:57:02 +00:00
autobuildPoller := time . NewTicker ( autobuildPollInterval )
defer autobuildPoller . Stop ( )
autobuildExecutor := executor . New ( cmd . Context ( ) , options . Database , logger , autobuildPoller . C )
autobuildExecutor . Run ( )
2022-05-11 22:03:02 +00:00
2022-05-06 20:45:18 +00:00
// Because the graceful shutdown includes cleaning up workspaces in dev mode, we're
// going to make it harder to accidentally skip the graceful shutdown by hitting ctrl+c
// two or more times. So the stopChan is unlimited in size and we don't call
// signal.Stop() until graceful shutdown finished--this means we swallow additional
// SIGINT after the first. To get out of a graceful shutdown, the user can send SIGQUIT
// with ctrl+\ or SIGTERM with `kill`.
2022-03-24 15:07:33 +00:00
stopChan := make ( chan os . Signal , 1 )
defer signal . Stop ( stopChan )
signal . Notify ( stopChan , os . Interrupt )
2022-03-22 19:17:50 +00:00
select {
case <- cmd . Context ( ) . Done ( ) :
2022-05-19 22:47:45 +00:00
coderDaemon . CloseWait ( )
2022-03-22 19:17:50 +00:00
return cmd . Context ( ) . Err ( )
2022-04-14 15:29:40 +00:00
case err := <- tunnelErrChan :
if err != nil {
return err
}
2022-03-22 19:17:50 +00:00
case err := <- errCh :
2022-05-10 02:19:20 +00:00
shutdownConns ( )
2022-05-19 22:47:45 +00:00
coderDaemon . CloseWait ( )
2022-03-22 19:17:50 +00:00
return err
2022-03-24 15:07:33 +00:00
case <- stopChan :
}
_ , err = daemon . SdNotify ( false , daemon . SdNotifyStopping )
if err != nil {
return xerrors . Errorf ( "notify systemd: %w" , err )
}
2022-05-06 20:45:18 +00:00
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , "\n\n" +
cliui . Styles . Bold . Render (
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ) )
2022-03-24 15:07:33 +00:00
if dev {
2022-04-25 21:11:03 +00:00
organizations , err := client . OrganizationsByUser ( cmd . Context ( ) , codersdk . Me )
if err != nil {
return xerrors . Errorf ( "get organizations: %w" , err )
}
workspaces , err := client . WorkspacesByOwner ( cmd . Context ( ) , organizations [ 0 ] . ID , codersdk . Me )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "get workspaces: %w" , err )
}
for _ , workspace := range workspaces {
before := time . Now ( )
build , err := client . CreateWorkspaceBuild ( cmd . Context ( ) , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionDelete ,
2022-03-24 15:07:33 +00:00
} )
if err != nil {
return xerrors . Errorf ( "delete workspace: %w" , err )
}
2022-05-02 15:20:47 +00:00
err = cliui . WorkspaceBuild ( cmd . Context ( ) , cmd . OutOrStdout ( ) , client , build . ID , before )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "delete workspace %s: %w" , workspace . Name , err )
}
}
}
for _ , provisionerDaemon := range provisionerDaemons {
spin := spinner . New ( spinner . CharSets [ 5 ] , 100 * time . Millisecond )
spin . Writer = cmd . OutOrStdout ( )
spin . Suffix = cliui . Styles . Keyword . Render ( " Shutting down provisioner daemon..." )
spin . Start ( )
err = provisionerDaemon . Shutdown ( cmd . Context ( ) )
if err != nil {
spin . FinalMSG = cliui . Styles . Prompt . String ( ) + "Failed to shutdown provisioner daemon: " + err . Error ( )
spin . Stop ( )
}
err = provisionerDaemon . Close ( )
if err != nil {
spin . Stop ( )
return xerrors . Errorf ( "close provisioner daemon: %w" , err )
}
spin . FinalMSG = cliui . Styles . Prompt . String ( ) + "Gracefully shut down provisioner daemon!\n"
spin . Stop ( )
2022-03-22 19:17:50 +00:00
}
2022-03-24 15:07:33 +00:00
2022-05-12 17:37:51 +00:00
if dev && tunnel {
2022-04-14 15:29:40 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Prompt . String ( ) + "Waiting for dev tunnel to close...\n" )
closeTunnel ( )
<- tunnelErrChan
}
2022-03-24 15:07:33 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , cliui . Styles . Prompt . String ( ) + "Waiting for WebSocket connections to close...\n" )
2022-03-24 19:21:05 +00:00
shutdownConns ( )
2022-05-19 22:47:45 +00:00
coderDaemon . CloseWait ( )
2022-03-24 15:07:33 +00:00
return nil
2022-03-22 19:17:50 +00:00
} ,
}
2022-03-28 19:26:41 +00:00
2022-05-20 10:57:02 +00:00
cliflag . DurationVarP ( root . Flags ( ) , & autobuildPollInterval , "autobuild-poll-interval" , "" , "CODER_AUTOBUILD_POLL_INTERVAL" , time . Minute , "Specifies the interval at which to poll for and execute automated workspace build operations." )
2022-05-03 12:48:02 +00:00
cliflag . StringVarP ( root . Flags ( ) , & accessURL , "access-url" , "" , "CODER_ACCESS_URL" , "" , "Specifies the external URL to access Coder." )
cliflag . StringVarP ( root . Flags ( ) , & address , "address" , "a" , "CODER_ADDRESS" , "127.0.0.1:3000" , "The address to serve the API and dashboard." )
cliflag . BoolVarP ( root . Flags ( ) , & promEnabled , "prometheus-enable" , "" , "CODER_PROMETHEUS_ENABLE" , false , "Enable serving prometheus metrics on the addressdefined by --prometheus-address." )
cliflag . StringVarP ( root . Flags ( ) , & promAddress , "prometheus-address" , "" , "CODER_PROMETHEUS_ADDRESS" , "127.0.0.1:2112" , "The address to serve prometheus metrics." )
2022-05-03 19:54:10 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & pprofEnabled , "pprof-enable" , "" , "CODER_PPROF_ENABLE" , false , "Enable serving pprof metrics on the address defined by --pprof-address." )
2022-05-03 12:48:02 +00:00
cliflag . StringVarP ( root . Flags ( ) , & pprofAddress , "pprof-address" , "" , "CODER_PPROF_ADDRESS" , "127.0.0.1:6060" , "The address to serve pprof." )
2022-03-28 19:57:19 +00:00
// systemd uses the CACHE_DIRECTORY environment variable!
2022-03-30 22:59:54 +00:00
cliflag . StringVarP ( root . Flags ( ) , & cacheDir , "cache-dir" , "" , "CACHE_DIRECTORY" , filepath . Join ( os . TempDir ( ) , "coder-cache" ) , "Specifies a directory to cache binaries for provision operations." )
2022-03-28 19:26:41 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & dev , "dev" , "" , "CODER_DEV_MODE" , false , "Serve Coder in dev mode for tinkering" )
2022-04-28 16:13:44 +00:00
cliflag . StringVarP ( root . Flags ( ) , & devUserEmail , "dev-admin-email" , "" , "CODER_DEV_ADMIN_EMAIL" , "admin@coder.com" , "Specifies the admin email to be used in dev mode (--dev)" )
cliflag . StringVarP ( root . Flags ( ) , & devUserPassword , "dev-admin-password" , "" , "CODER_DEV_ADMIN_PASSWORD" , "" , "Specifies the admin password to be used in dev mode (--dev) instead of a randomly generated one" )
2022-03-28 19:26:41 +00:00
cliflag . StringVarP ( root . Flags ( ) , & postgresURL , "postgres-url" , "" , "CODER_PG_CONNECTION_URL" , "" , "URL of a PostgreSQL database to connect to" )
2022-04-26 12:19:17 +00:00
cliflag . Uint8VarP ( root . Flags ( ) , & provisionerDaemonCount , "provisioner-daemons" , "" , "CODER_PROVISIONER_DAEMONS" , 3 , "The amount of provisioner daemons to create on start." )
2022-04-23 22:58:57 +00:00
cliflag . StringVarP ( root . Flags ( ) , & oauth2GithubClientID , "oauth2-github-client-id" , "" , "CODER_OAUTH2_GITHUB_CLIENT_ID" , "" ,
"Specifies a client ID to use for oauth2 with GitHub." )
cliflag . StringVarP ( root . Flags ( ) , & oauth2GithubClientSecret , "oauth2-github-client-secret" , "" , "CODER_OAUTH2_GITHUB_CLIENT_SECRET" , "" ,
"Specifies a client secret to use for oauth2 with GitHub." )
cliflag . StringArrayVarP ( root . Flags ( ) , & oauth2GithubAllowedOrganizations , "oauth2-github-allowed-orgs" , "" , "CODER_OAUTH2_GITHUB_ALLOWED_ORGS" , nil ,
"Specifies organizations the user must be a member of to authenticate with GitHub." )
cliflag . BoolVarP ( root . Flags ( ) , & oauth2GithubAllowSignups , "oauth2-github-allow-signups" , "" , "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS" , false ,
"Specifies whether new users can sign up with GitHub." )
2022-03-28 19:26:41 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & tlsEnable , "tls-enable" , "" , "CODER_TLS_ENABLE" , false , "Specifies if TLS will be enabled" )
cliflag . StringVarP ( root . Flags ( ) , & tlsCertFile , "tls-cert-file" , "" , "CODER_TLS_CERT_FILE" , "" ,
2022-03-24 19:21:05 +00:00
"Specifies the path to the certificate for TLS. It requires a PEM-encoded file. " +
"To configure the listener to use a CA certificate, concatenate the primary certificate " +
2022-03-28 19:26:41 +00:00
"and the CA certificate together. The primary certificate should appear first in the combined file" )
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-03-24 19:21:05 +00:00
` Specifies the 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" ` )
cliflag . StringVarP ( root . Flags ( ) , & tlsKeyFile , "tls-key-file" , "" , "CODER_TLS_KEY_FILE" , "" ,
"Specifies the path to the private key for the certificate. It requires a PEM-encoded file" )
cliflag . StringVarP ( root . Flags ( ) , & tlsMinVersion , "tls-min-version" , "" , "CODER_TLS_MIN_VERSION" , "tls12" ,
` Specifies the minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13" ` )
2022-05-12 17:37:51 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & tunnel , "tunnel" , "" , "CODER_DEV_TUNNEL" , true ,
"Specifies whether the dev tunnel will be enabled or not. If specified, the interactive prompt will not display." )
2022-04-25 04:52:07 +00:00
cliflag . StringArrayVarP ( root . Flags ( ) , & stunServers , "stun-server" , "" , "CODER_STUN_SERVERS" , [ ] string {
"stun:stun.l.google.com:19302" ,
} , "Specify URLs for STUN servers to enable P2P connections." )
2022-05-19 22:43:07 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & trace , "trace" , "" , "CODER_TRACE" , false , "Specifies if application tracing data is collected" )
2022-04-18 22:40:25 +00:00
cliflag . StringVarP ( root . Flags ( ) , & turnRelayAddress , "turn-relay-address" , "" , "CODER_TURN_RELAY_ADDRESS" , "127.0.0.1" ,
"Specifies the address to bind TURN connections." )
2022-03-31 17:31:06 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & secureAuthCookie , "secure-auth-cookie" , "" , "CODER_SECURE_AUTH_COOKIE" , false , "Specifies if the 'Secure' property is set on browser session cookies" )
2022-04-06 00:18:26 +00:00
cliflag . StringVarP ( root . Flags ( ) , & sshKeygenAlgorithmRaw , "ssh-keygen-algorithm" , "" , "CODER_SSH_KEYGEN_ALGORITHM" , "ed25519" , "Specifies the algorithm to use for generating ssh keys. " +
` Accepted values are "ed25519", "ecdsa", or "rsa4096" ` )
2022-04-19 16:40:01 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & spooky , "spooky" , "" , "" , false , "Specifies spookiness level" )
2022-05-03 21:13:30 +00:00
cliflag . BoolVarP ( root . Flags ( ) , & verbose , "verbose" , "v" , "CODER_VERBOSE" , false , "Enables verbose logging." )
2022-04-19 16:40:01 +00:00
_ = root . Flags ( ) . MarkHidden ( "spooky" )
2022-03-22 19:17:50 +00:00
return root
}
2022-04-28 16:13:44 +00:00
func createFirstUser ( cmd * cobra . Command , client * codersdk . Client , cfg config . Root , email , password string ) error {
if email == "" {
return xerrors . New ( "email is empty" )
}
if password == "" {
return xerrors . New ( "password is empty" )
}
_ , err := client . CreateFirstUser ( cmd . Context ( ) , codersdk . CreateFirstUserRequest {
Email : email ,
Username : "developer" ,
Password : password ,
OrganizationName : "acme-corp" ,
} )
2022-03-24 15:07:33 +00:00
if err != nil {
return xerrors . Errorf ( "create first user: %w" , err )
}
token , err := client . LoginWithPassword ( cmd . Context ( ) , codersdk . LoginWithPasswordRequest {
2022-04-28 16:13:44 +00:00
Email : email ,
Password : password ,
2022-03-24 15:07:33 +00:00
} )
if err != nil {
return xerrors . Errorf ( "login with first user: %w" , err )
}
client . SessionToken = token . SessionToken
err = cfg . URL ( ) . Write ( client . URL . String ( ) )
if err != nil {
return xerrors . Errorf ( "write local url: %w" , err )
}
err = cfg . Session ( ) . Write ( token . SessionToken )
if err != nil {
return xerrors . Errorf ( "write session token: %w" , err )
}
return nil
}
2022-05-19 22:47:45 +00:00
// nolint:revive
func newProvisionerDaemon ( ctx context . Context , coderDaemon coderd . CoderD ,
logger slog . Logger , cacheDir string , errChan chan error , dev bool ) ( * provisionerd . Server , error ) {
2022-03-30 22:59:54 +00:00
err := os . MkdirAll ( cacheDir , 0700 )
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 ( ) {
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
} )
if err != nil {
2022-04-18 20:11:59 +00:00
errChan <- err
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 ( ) {
err := echo . Serve ( ctx , & provisionersdk . ServeOptions { Listener : echoServer } )
if err != nil {
errChan <- err
}
} ( )
provisioners [ string ( database . ProvisionerTypeEcho ) ] = proto . NewDRPCProvisionerClient ( provisionersdk . Conn ( echoClient ) )
}
return provisionerd . New ( coderDaemon . 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-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 {
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , `
▄ █ █ █ █ ▄ ▒ █ █ █ █ █ ▓ █ █ █ █ █ ▄ ▓ █ █ █ █ █ █ █ ▀ █ █ █
▒ █ █ ▀ ▀ █ ▒ █ █ ▒ █ █ ▒ ▒ █ █ ▀ █ █ ▌ ▓ █ ▀ ▓ █ █ ▒ █ █ ▒
▒ ▓ █ ▄ ▒ █ █ ░ █ █ ▒ ░ █ █ █ ▌ ▒ █ █ █ ▓ █ █ ░ ▄ █ ▒
▒ ▓ ▓ ▄ ▄ █ █ ▒ ▒ █ █ █ █ ░ ░ ▓ █ ▄ ▌ ▒ ▓ █ ▄ ▒ █ █ ▀ ▀ █ ▄
▒ ▓ █ █ █ ▀ ░ ░ █ █ █ █ ▓ ▒ ░ ░ ▒ █ █ █ █ ▓ ░ ▒ █ █ █ █ ▒ ░ █ █ ▓ ▒ █ █ ▒
░ ░ ▒ ▒ ░ ░ ▒ ░ ▒ ░ ▒ ░ ▒ ▒ ▓ ▒ ░ ░ ▒ ░ ░ ░ ▒ ▓ ░ ▒ ▓ ░
░ ▒ ░ ▒ ▒ ░ ░ ▒ ▒ ░ ░ ░ ░ ▒ ░ ▒ ░
░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░
░ ░
` )
return
}
2022-03-24 19:21:05 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , ` ▄ █ ▀ ▀ █ ▄
▄ ▄ ▀ ▀ ▀ █ ▌ █ █ ▀ ▀ █ ▄ ▐ █
▄ ▄ █ █ ▀ ▀ █ ▄ ▄ ▄ █ █ █ █ █ ▀ ▀ █ ▐ █ ▀ ▀ █ █ ▄ █ ▀ ▀ █ █ ▀ ▀
█ ▌ ▄ ▌ ▐ █ █ ▌ ▀ █ ▄ ▄ ▄ █ ▌ █ █ ▐ █ █ █ █ █ ▀ ▀ █
█ █ █ █ █ █ ▀ ▄ █ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀
` )
}
2022-03-25 00:20:13 +00:00
func configureTLS ( listener net . Listener , tlsMinVersion , tlsClientAuth , tlsCertFile , tlsKeyFile , tlsClientCAFile string ) ( net . Listener , error ) {
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 )
}
if tlsCertFile == "" {
return nil , xerrors . New ( "tls-cert-file is required when tls is enabled" )
}
if tlsKeyFile == "" {
return nil , xerrors . New ( "tls-key-file is required when tls is enabled" )
}
certPEMBlock , err := os . ReadFile ( tlsCertFile )
if err != nil {
return nil , xerrors . Errorf ( "read file %q: %w" , tlsCertFile , err )
}
keyPEMBlock , err := os . ReadFile ( tlsKeyFile )
if err != nil {
return nil , xerrors . Errorf ( "read file %q: %w" , tlsKeyFile , err )
}
keyBlock , _ := pem . Decode ( keyPEMBlock )
if keyBlock == nil {
return nil , xerrors . New ( "decoded pem is blank" )
}
cert , err := tls . X509KeyPair ( certPEMBlock , keyPEMBlock )
if err != nil {
return nil , xerrors . Errorf ( "create key pair: %w" , err )
}
tlsConfig . GetCertificate = func ( chi * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
return & cert , nil
}
certPool := x509 . NewCertPool ( )
certPool . AppendCertsFromPEM ( certPEMBlock )
tlsConfig . RootCAs = certPool
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-04-23 22:58:57 +00:00
func configureGithubOAuth2 ( accessURL * url . URL , clientID , clientSecret string , allowSignups bool , allowOrgs [ ] string ) ( * coderd . GithubOAuth2Config , error ) {
redirectURL , err := accessURL . Parse ( "/api/v2/users/oauth2/github/callback" )
if err != nil {
return nil , xerrors . Errorf ( "parse github oauth callback url: %w" , err )
}
return & coderd . GithubOAuth2Config {
OAuth2Config : & oauth2 . Config {
ClientID : clientID ,
ClientSecret : clientSecret ,
Endpoint : xgithub . Endpoint ,
RedirectURL : redirectURL . String ( ) ,
Scopes : [ ] string {
"read:user" ,
"read:org" ,
"user:email" ,
} ,
} ,
AllowSignups : allowSignups ,
AllowOrganizations : allowOrgs ,
AuthenticatedUser : func ( ctx context . Context , client * http . Client ) ( * github . User , error ) {
user , _ , err := github . NewClient ( client ) . Users . Get ( ctx , "" )
return user , err
} ,
ListEmails : func ( ctx context . Context , client * http . Client ) ( [ ] * github . UserEmail , error ) {
emails , _ , err := github . NewClient ( client ) . Users . ListEmails ( ctx , & github . ListOptions { } )
return emails , err
} ,
ListOrganizationMemberships : func ( ctx context . Context , client * http . Client ) ( [ ] * github . Membership , error ) {
memberships , _ , err := github . NewClient ( client ) . Organizations . ListOrgMemberships ( ctx , & github . ListOrgMembershipsOptions {
State : "active" ,
} )
return memberships , err
} ,
} , 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 ) )
srv := & http . Server { Addr : addr , Handler : handler }
go func ( ) {
err := srv . ListenAndServe ( )
if err != nil && ! xerrors . Is ( err , http . ErrServerClosed ) {
logger . Error ( ctx , "http server listen" , slog . F ( "name" , name ) , slog . Error ( err ) )
}
} ( )
return func ( ) { _ = srv . Close ( ) }
}