Skip to content

Commit fd66daf

Browse files
committed
feat: Add built-in PostgreSQL for simple production setup
Fixes #2321.
1 parent 0a949aa commit fd66daf

File tree

19 files changed

+405
-467
lines changed

19 files changed

+405
-467
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ To install, run:
4747
curl -fsSL https://coder.com/install.sh | sh
4848
```
4949

50-
Once installed, you can run a temporary deployment in dev mode (all data is in-memory and destroyed on exit):
50+
Once installed, you can start a production deployment with a single command:
5151

5252
```sh
53-
coder server --dev
53+
# Automatically sets up PostgreSQL and an external access URL on *.try.coder.app
54+
coder server --postgres-builtin --tunnel
55+
56+
# Requires a PostgreSQL instance and external access URL
57+
coder server --postgres-url <url> --access-url <url>
5458
```
5559

5660
Use `coder --help` to get a complete list of flags and environment variables. Use our [quickstart guide](./docs/quickstart.md) for a full walkthrough.

cli/config/file.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ func (r Root) DotfilesURL() File {
2525
return File(filepath.Join(string(r), "dotfilesurl"))
2626
}
2727

28+
func (r Root) PostgresPath() string {
29+
return filepath.Join(string(r), "postgres")
30+
}
31+
32+
func (r Root) PostgresPassword() File {
33+
return File(filepath.Join(r.PostgresPath(), "password"))
34+
}
35+
36+
func (r Root) PostgresPort() File {
37+
return File(filepath.Join(r.PostgresPath(), "port"))
38+
}
39+
2840
// File provides convenience methods for interacting with *os.File.
2941
type File string
3042

cli/login.go

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ func init() {
3737
}
3838

3939
func login() *cobra.Command {
40-
return &cobra.Command{
40+
var (
41+
email string
42+
username string
43+
password string
44+
)
45+
cmd := &cobra.Command{
4146
Use: "login <url>",
4247
Short: "Authenticate with a Coder deployment",
4348
Args: cobra.ExactArgs(1),
@@ -66,71 +71,77 @@ func login() *cobra.Command {
6671
return xerrors.Errorf("has initial user: %w", err)
6772
}
6873
if !hasInitialUser {
69-
if !isTTY(cmd) {
70-
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
71-
}
7274
_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"Your Coder deployment hasn't been set up!\n")
7375

74-
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
75-
Text: "Would you like to create the first user?",
76-
Default: "yes",
77-
IsConfirm: true,
78-
})
79-
if errors.Is(err, cliui.Canceled) {
80-
return nil
81-
}
82-
if err != nil {
83-
return err
84-
}
85-
currentUser, err := user.Current()
86-
if err != nil {
87-
return xerrors.Errorf("get current user: %w", err)
88-
}
89-
username, err := cliui.Prompt(cmd, cliui.PromptOptions{
90-
Text: "What " + cliui.Styles.Field.Render("username") + " would you like?",
91-
Default: currentUser.Username,
92-
})
93-
if errors.Is(err, cliui.Canceled) {
94-
return nil
95-
}
96-
if err != nil {
97-
return xerrors.Errorf("pick username prompt: %w", err)
98-
}
99-
100-
email, err := cliui.Prompt(cmd, cliui.PromptOptions{
101-
Text: "What's your " + cliui.Styles.Field.Render("email") + "?",
102-
Validate: func(s string) error {
103-
err := validator.New().Var(s, "email")
104-
if err != nil {
105-
return xerrors.New("That's not a valid email address!")
106-
}
76+
if username == "" {
77+
if !isTTY(cmd) {
78+
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
79+
}
80+
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
81+
Text: "Would you like to create the first user?",
82+
Default: "yes",
83+
IsConfirm: true,
84+
})
85+
if errors.Is(err, cliui.Canceled) {
86+
return nil
87+
}
88+
if err != nil {
10789
return err
108-
},
109-
})
110-
if err != nil {
111-
return xerrors.Errorf("specify email prompt: %w", err)
90+
}
91+
currentUser, err := user.Current()
92+
if err != nil {
93+
return xerrors.Errorf("get current user: %w", err)
94+
}
95+
username, err = cliui.Prompt(cmd, cliui.PromptOptions{
96+
Text: "What " + cliui.Styles.Field.Render("username") + " would you like?",
97+
Default: currentUser.Username,
98+
})
99+
if errors.Is(err, cliui.Canceled) {
100+
return nil
101+
}
102+
if err != nil {
103+
return xerrors.Errorf("pick username prompt: %w", err)
104+
}
112105
}
113106

114-
password, err := cliui.Prompt(cmd, cliui.PromptOptions{
115-
Text: "Enter a " + cliui.Styles.Field.Render("password") + ":",
116-
Secret: true,
117-
Validate: cliui.ValidateNotEmpty,
118-
})
119-
if err != nil {
120-
return xerrors.Errorf("specify password prompt: %w", err)
107+
if email == "" {
108+
email, err = cliui.Prompt(cmd, cliui.PromptOptions{
109+
Text: "What's your " + cliui.Styles.Field.Render("email") + "?",
110+
Validate: func(s string) error {
111+
err := validator.New().Var(s, "email")
112+
if err != nil {
113+
return xerrors.New("That's not a valid email address!")
114+
}
115+
return err
116+
},
117+
})
118+
if err != nil {
119+
return xerrors.Errorf("specify email prompt: %w", err)
120+
}
121121
}
122-
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
123-
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
124-
Secret: true,
125-
Validate: func(s string) error {
126-
if s != password {
127-
return xerrors.Errorf("Passwords do not match")
128-
}
129-
return nil
130-
},
131-
})
132-
if err != nil {
133-
return xerrors.Errorf("confirm password prompt: %w", err)
122+
123+
if password == "" {
124+
password, err = cliui.Prompt(cmd, cliui.PromptOptions{
125+
Text: "Enter a " + cliui.Styles.Field.Render("password") + ":",
126+
Secret: true,
127+
Validate: cliui.ValidateNotEmpty,
128+
})
129+
if err != nil {
130+
return xerrors.Errorf("specify password prompt: %w", err)
131+
}
132+
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
133+
Text: "Confirm " + cliui.Styles.Field.Render("password") + ":",
134+
Secret: true,
135+
Validate: func(s string) error {
136+
if s != password {
137+
return xerrors.Errorf("Passwords do not match")
138+
}
139+
return nil
140+
},
141+
})
142+
if err != nil {
143+
return xerrors.Errorf("confirm password prompt: %w", err)
144+
}
134145
}
135146

136147
_, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
@@ -219,6 +230,10 @@ func login() *cobra.Command {
219230
return nil
220231
},
221232
}
233+
cmd.Flags().StringVarP(&email, "email", "e", "", "Specifies an email address to authenticate with.")
234+
cmd.Flags().StringVarP(&username, "username", "u", "", "Specifies a username to authenticate with.")
235+
cmd.Flags().StringVarP(&password, "password", "p", "", "Specifies a password to authenticate with.")
236+
return cmd
222237
}
223238

224239
// isWSL determines if coder-cli is running within Windows Subsystem for Linux

cli/login_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ func TestLogin(t *testing.T) {
5656
<-doneChan
5757
})
5858

59+
t.Run("InitialUserFlags", func(t *testing.T) {
60+
t.Parallel()
61+
client := coderdtest.New(t, nil)
62+
// The --force-tty flag is required on Windows, because the `isatty` library does not
63+
// accurately detect Windows ptys when they are not attached to a process:
64+
// https://github.com/mattn/go-isatty/issues/59
65+
doneChan := make(chan struct{})
66+
root, _ := clitest.New(t, "login", client.URL.String(), "--username", "testuser", "--email", "user@coder.com", "--password", "password")
67+
pty := ptytest.New(t)
68+
root.SetIn(pty.Input())
69+
root.SetOut(pty.Output())
70+
go func() {
71+
defer close(doneChan)
72+
err := root.Execute()
73+
assert.NoError(t, err)
74+
}()
75+
pty.ExpectMatch("Welcome to Coder")
76+
<-doneChan
77+
})
78+
5979
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
6080
t.Parallel()
6181
ctx, cancel := context.WithCancel(context.Background())

cli/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ func Root() *cobra.Command {
5757
SilenceUsage: true,
5858
Long: `Coder — A tool for provisioning self-hosted development environments.
5959
`,
60-
Example: ` Start Coder in "dev" mode. This dev-mode requires no further setup, and your local ` + cliui.Styles.Code.Render("coder") + ` CLI will be authenticated to talk to it. This makes it easy to experiment with Coder.
61-
` + cliui.Styles.Code.Render("$ coder server --dev") + `
60+
Example: ` Start a Coder server.
61+
` + cliui.Styles.Code.Render("$ coder server") + `
6262
6363
Get started by creating a template from an example.
6464
` + cliui.Styles.Code.Render("$ coder templates init"),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy