Skip to content

Commit ccd0616

Browse files
authored
feat: Add built-in PostgreSQL for simple production setup (#2345)
* feat: Add built-in PostgreSQL for simple production setup Fixes #2321. * Use fork of embedded-postgres for cache path
1 parent bb4ecd7 commit ccd0616

File tree

23 files changed

+408
-475
lines changed

23 files changed

+408
-475
lines changed

.goreleaser.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ nfpms:
9393
type: "config|noreplace"
9494
- src: coder.service
9595
dst: /usr/lib/systemd/system/coder.service
96+
scripts:
97+
preinstall: preinstall.sh
9698

9799
# Image templates are empty on snapshots to avoid lengthy builds for development.
98100
dockers:

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 an external access URL on *.try.coder.app
54+
coder server --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](https://coder.com/docs/coder-oss/latest/quickstart) 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: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/spf13/cobra"
1818
"golang.org/x/xerrors"
1919

20+
"github.com/coder/coder/cli/cliflag"
2021
"github.com/coder/coder/cli/cliui"
2122
"github.com/coder/coder/codersdk"
2223
)
@@ -37,7 +38,12 @@ func init() {
3738
}
3839

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

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-
}
77+
if username == "" {
78+
if !isTTY(cmd) {
79+
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
80+
}
81+
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
82+
Text: "Would you like to create the first user?",
83+
Default: "yes",
84+
IsConfirm: true,
85+
})
86+
if errors.Is(err, cliui.Canceled) {
87+
return nil
88+
}
89+
if err != nil {
10790
return err
108-
},
109-
})
110-
if err != nil {
111-
return xerrors.Errorf("specify email prompt: %w", err)
91+
}
92+
currentUser, err := user.Current()
93+
if err != nil {
94+
return xerrors.Errorf("get current user: %w", err)
95+
}
96+
username, err = cliui.Prompt(cmd, cliui.PromptOptions{
97+
Text: "What " + cliui.Styles.Field.Render("username") + " would you like?",
98+
Default: currentUser.Username,
99+
})
100+
if errors.Is(err, cliui.Canceled) {
101+
return nil
102+
}
103+
if err != nil {
104+
return xerrors.Errorf("pick username prompt: %w", err)
105+
}
112106
}
113107

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

136148
_, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
@@ -219,6 +231,10 @@ func login() *cobra.Command {
219231
return nil
220232
},
221233
}
234+
cliflag.StringVarP(cmd.Flags(), &email, "email", "e", "CODER_EMAIL", "", "Specifies an email address to authenticate with.")
235+
cliflag.StringVarP(cmd.Flags(), &username, "username", "u", "CODER_USERNAME", "", "Specifies a username to authenticate with.")
236+
cliflag.StringVarP(cmd.Flags(), &password, "password", "p", "CODER_PASSWORD", "", "Specifies a password to authenticate with.")
237+
return cmd
222238
}
223239

224240
// 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