• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 6252516071

20 Sep 2023 06:16PM UTC coverage: 56.616% (-0.05%) from 56.663%
6252516071

Pull #1511

github

sweatybridge
fix(pooler): pin pgbouncer image to an immutable tag
Pull Request #1511: fix(pooler): pin pgbouncer image to an immutable tag

4 of 4 new or added lines in 1 file covered. (100.0%)

4989 of 8812 relevant lines covered (56.62%)

821.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.1
/internal/start/start.go
1
package start
2

3
import (
4
        "bytes"
5
        "context"
6
        _ "embed"
7
        "encoding/json"
8
        "errors"
9
        "fmt"
10
        "os"
11
        "path"
12
        "path/filepath"
13
        "strconv"
14
        "strings"
15
        "text/template"
16
        "time"
17

18
        "github.com/docker/docker/api/types/container"
19
        "github.com/docker/go-connections/nat"
20
        "github.com/jackc/pgconn"
21
        "github.com/jackc/pgx/v4"
22
        "github.com/spf13/afero"
23
        "github.com/supabase/cli/internal/db/reset"
24
        "github.com/supabase/cli/internal/db/start"
25
        "github.com/supabase/cli/internal/functions/serve"
26
        "github.com/supabase/cli/internal/gen/keys"
27
        "github.com/supabase/cli/internal/status"
28
        "github.com/supabase/cli/internal/utils"
29
)
30

31
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool, projectRef, dbUrl string) error {
4✔
32
        // Sanity checks.
4✔
33
        {
8✔
34
                if err := utils.LoadConfigFS(fsys); err != nil {
6✔
35
                        return err
2✔
36
                }
2✔
37
                if err := utils.AssertDockerIsRunning(ctx); err != nil {
3✔
38
                        return err
1✔
39
                }
1✔
40
                if _, err := utils.Docker.ContainerInspect(ctx, utils.DbId); err == nil {
2✔
41
                        fmt.Fprintln(os.Stderr, utils.Aqua("supabase start")+" is already running.")
1✔
42
                        fmt.Fprintln(os.Stderr, "Run "+utils.Aqua("supabase status")+" to show status of local Supabase containers.")
1✔
43
                        return nil
1✔
44
                }
1✔
45
        }
46

47
        if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error {
×
48
                var dbConfig pgconn.Config
×
49
                if len(dbUrl) > 0 {
×
50
                        config, err := pgconn.ParseConfig(dbUrl)
×
51
                        if err != nil {
×
52
                                return err
×
53
                        }
×
54
                        dbConfig = *config
×
55
                } else if len(projectRef) > 0 {
×
56
                        branch := keys.GetGitBranch(fsys)
×
57
                        if err := keys.GenerateSecrets(ctx, projectRef, branch, fsys); err != nil {
×
58
                                return err
×
59
                        }
×
60
                        dbConfig = pgconn.Config{
×
61
                                Host:     fmt.Sprintf("%s-%s.fly.dev", projectRef, branch),
×
62
                                Port:     5432,
×
63
                                User:     "postgres",
×
64
                                Password: utils.Config.Db.Password,
×
65
                                Database: "postgres",
×
66
                        }
×
67
                } else {
×
68
                        dbConfig = pgconn.Config{
×
69
                                Host:     utils.DbId,
×
70
                                Port:     5432,
×
71
                                User:     "postgres",
×
72
                                Password: utils.Config.Db.Password,
×
73
                                Database: "postgres",
×
74
                        }
×
75
                }
×
76
                return run(p, ctx, fsys, excludedContainers, dbConfig)
×
77
        }); err != nil {
×
78
                if ignoreHealthCheck && errors.Is(err, reset.ErrUnhealthy) {
×
79
                        fmt.Fprintln(os.Stderr, err)
×
80
                } else {
×
81
                        utils.DockerRemoveAll(context.Background())
×
82
                        return err
×
83
                }
×
84
        }
85

86
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
87
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
88
        return nil
×
89
}
90

91
type kongConfig struct {
92
        GotrueId      string
93
        RestId        string
94
        RealtimeId    string
95
        StorageId     string
96
        PgmetaId      string
97
        EdgeRuntimeId string
98
        LogflareId    string
99
        ApiPort       uint
100
}
101

102
var (
103
        //go:embed templates/kong.yml
104
        kongConfigEmbed    string
105
        kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
106

107
        //go:embed templates/custom_nginx.template
108
        nginxConfigEmbed string
109
        // Hardcoded configs which match nginxConfigEmbed
110
        nginxEmailTemplateDir   = "/home/kong/templates/email"
111
        nginxTemplateServerPort = 8088
112
)
113

114
type vectorConfig struct {
115
        ApiKey        string
116
        LogflareId    string
117
        KongId        string
118
        GotrueId      string
119
        RestId        string
120
        RealtimeId    string
121
        StorageId     string
122
        EdgeRuntimeId string
123
        DbId          string
124
}
125

126
var (
127
        //go:embed templates/vector.yaml
128
        vectorConfigEmbed    string
129
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
130
)
131

132
func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
133
        excluded := make(map[string]bool)
2✔
134
        for _, name := range excludedContainers {
19✔
135
                excluded[name] = true
17✔
136
        }
17✔
137

138
        // Start vector
139
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.VectorImage, excluded) {
2✔
140
                var vectorConfigBuf bytes.Buffer
×
141
                if err := vectorConfigTemplate.Execute(&vectorConfigBuf, vectorConfig{
×
142
                        ApiKey:        utils.Config.Analytics.ApiKey,
×
143
                        LogflareId:    utils.LogflareId,
×
144
                        KongId:        utils.KongId,
×
145
                        GotrueId:      utils.GotrueId,
×
146
                        RestId:        utils.RestId,
×
147
                        RealtimeId:    utils.RealtimeId,
×
148
                        StorageId:     utils.StorageId,
×
149
                        EdgeRuntimeId: utils.EdgeRuntimeId,
×
150
                        DbId:          utils.DbId,
×
151
                }); err != nil {
×
152
                        return err
×
153
                }
×
154
                p.Send(utils.StatusMsg("Starting syslog driver..."))
×
155
                if _, err := utils.DockerStart(
×
156
                        ctx,
×
157
                        container.Config{
×
158
                                Image: utils.VectorImage,
×
159
                                Env: []string{
×
160
                                        "VECTOR_CONFIG=/etc/vector/vector.yaml",
×
161
                                },
×
162
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/vector/vector.yaml && vector
×
163
` + vectorConfigBuf.String() + `
×
164
EOF
×
165
`},
×
166
                                Healthcheck: &container.HealthConfig{
×
167
                                        Test: []string{"CMD",
×
168
                                                "wget",
×
169
                                                "--no-verbose",
×
170
                                                "--tries=1",
×
171
                                                "--spider",
×
172
                                                "http://localhost:9001/health"},
×
173
                                        Interval: 10 * time.Second,
×
174
                                        Timeout:  2 * time.Second,
×
175
                                        Retries:  3,
×
176
                                },
×
177
                                ExposedPorts: nat.PortSet{"9000/tcp": {}},
×
178
                        },
×
179
                        container.HostConfig{
×
180
                                PortBindings:  nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.VectorPort), 10)}}},
×
181
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
182
                        },
×
183
                        utils.VectorId,
×
184
                ); err != nil {
×
185
                        return err
×
186
                }
×
187
                if err := reset.WaitForServiceReady(ctx, []string{utils.VectorId}); err != nil {
×
188
                        return err
×
189
                }
×
190
        }
191

192
        // Start Postgres.
193
        w := utils.StatusWriter{Program: p}
2✔
194
        if dbConfig.Host == utils.DbId {
4✔
195
                if err := start.StartDatabase(ctx, fsys, w, options...); err != nil {
2✔
196
                        return err
×
197
                }
×
198
        }
199

200
        var started []string
2✔
201
        // Start Logflare
2✔
202
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.LogflareImage, excluded) {
2✔
203
                env := []string{
×
204
                        "DB_DATABASE=" + dbConfig.Database,
×
205
                        "DB_HOSTNAME=" + dbConfig.Host,
×
206
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
×
207
                        "DB_SCHEMA=_analytics",
×
208
                        "DB_USERNAME=supabase_admin",
×
209
                        "DB_PASSWORD=" + dbConfig.Password,
×
210
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
×
211
                        "LOGFLARE_SINGLE_TENANT=true",
×
212
                        "LOGFLARE_SUPABASE_MODE=true",
×
213
                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
×
214
                        "LOGFLARE_LOG_LEVEL=warn",
×
215
                        "LOGFLARE_NODE_HOST=127.0.0.1",
×
216
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
×
217
                        "RELEASE_COOKIE=cookie",
×
218
                }
×
219
                bind := []string{}
×
220

×
221
                switch utils.Config.Analytics.Backend {
×
222
                case utils.LogflareBigQuery:
×
223
                        workdir, err := os.Getwd()
×
224
                        if err != nil {
×
225
                                return err
×
226
                        }
×
227
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
228
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
229
                        // This is hardcoded in studio frontend
×
230
                        env = append(env,
×
231
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
232
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
233
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
234
                        )
×
235
                case utils.LogflarePostgres:
×
236
                        env = append(env,
×
237
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
×
238
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
×
239
                        )
×
240
                }
241

242
                if _, err := utils.DockerStart(
×
243
                        ctx,
×
244
                        container.Config{
×
245
                                Hostname: "127.0.0.1",
×
246
                                Image:    utils.LogflareImage,
×
247
                                Env:      env,
×
248
                                // Original entrypoint conflicts with healthcheck due to 15 seconds sleep:
×
249
                                // https://github.com/Logflare/logflare/blob/staging/run.sh#L35
×
250
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > run.sh && sh run.sh
×
251
./logflare eval Logflare.Release.migrate
×
252
./logflare start --sname logflare
×
253
EOF
×
254
`},
×
255
                                Healthcheck: &container.HealthConfig{
×
256
                                        Test:        []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://localhost:4000/health"},
×
257
                                        Interval:    10 * time.Second,
×
258
                                        Timeout:     2 * time.Second,
×
259
                                        Retries:     3,
×
260
                                        StartPeriod: 10 * time.Second,
×
261
                                },
×
262
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
×
263
                        },
×
264
                        container.HostConfig{
×
265
                                Binds:         bind,
×
266
                                PortBindings:  nat.PortMap{"4000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.Port), 10)}}},
×
267
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
268
                        },
×
269
                        utils.LogflareId,
×
270
                ); err != nil {
×
271
                        return err
×
272
                }
×
273
                if err := reset.WaitForServiceReady(ctx, []string{utils.LogflareId}); err != nil {
×
274
                        return err
×
275
                }
×
276
        }
277

278
        // Start Kong.
279
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
280
        if !isContainerExcluded(utils.KongImage, excluded) {
3✔
281
                var kongConfigBuf bytes.Buffer
1✔
282
                if err := kongConfigTemplate.Execute(&kongConfigBuf, kongConfig{
1✔
283
                        GotrueId:      utils.GotrueId,
1✔
284
                        RestId:        utils.RestId,
1✔
285
                        RealtimeId:    utils.RealtimeId,
1✔
286
                        StorageId:     utils.StorageId,
1✔
287
                        PgmetaId:      utils.PgmetaId,
1✔
288
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
289
                        LogflareId:    utils.LogflareId,
1✔
290
                        ApiPort:       utils.Config.Api.Port,
1✔
291
                }); err != nil {
1✔
292
                        return err
×
293
                }
×
294

295
                binds := []string{}
1✔
296
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
297
                        if len(tmpl.ContentPath) == 0 {
10✔
298
                                continue
5✔
299
                        }
300
                        hostPath := tmpl.ContentPath
×
301
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
302
                                var err error
×
303
                                hostPath, err = filepath.Abs(hostPath)
×
304
                                if err != nil {
×
305
                                        return err
×
306
                                }
×
307
                        }
308
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
309
                        binds = append(binds, fmt.Sprintf("%s:%s:rw,z", hostPath, dockerPath))
×
310
                }
311

312
                if _, err := utils.DockerStart(
1✔
313
                        ctx,
1✔
314
                        container.Config{
1✔
315
                                Image: utils.KongImage,
1✔
316
                                Env: []string{
1✔
317
                                        "KONG_DATABASE=off",
1✔
318
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
319
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
320
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
321
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
322
                                        // sizeable response headers azure can generate
1✔
323
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
324
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
325
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
326
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
327
                                },
1✔
328
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && cat <<'EOF' > /home/kong/custom_nginx.template && ./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template
1✔
329
` + kongConfigBuf.String() + `
1✔
330
EOF
1✔
331
` + nginxConfigEmbed + `
1✔
332
EOF
1✔
333
`},
1✔
334
                        },
1✔
335
                        start.WithSyslogConfig(container.HostConfig{
1✔
336
                                Binds:         binds,
1✔
337
                                PortBindings:  nat.PortMap{"8000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)}}},
1✔
338
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
339
                        }),
1✔
340
                        utils.KongId,
1✔
341
                ); err != nil {
1✔
342
                        return err
×
343
                }
×
344
                started = append(started, utils.KongId)
1✔
345
        }
346

347
        // Start GoTrue.
348
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.GotrueImage, excluded) {
3✔
349
                var testOTP bytes.Buffer
1✔
350
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
351
                        encoder := json.NewEncoder(&testOTP)
×
352
                        if err := encoder.Encode(utils.Config.Auth.Sms.TestOTP); err != nil {
×
353
                                return err
×
354
                        }
×
355
                }
356
                env := []string{
1✔
357
                        fmt.Sprintf("API_EXTERNAL_URL=http://localhost:%v", utils.Config.Api.Port),
1✔
358

1✔
359
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
360
                        "GOTRUE_API_PORT=9999",
1✔
361

1✔
362
                        "GOTRUE_DB_DRIVER=postgres",
1✔
363
                        fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
364

1✔
365
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
366
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
367
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
368

1✔
369
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
370
                        "GOTRUE_JWT_AUD=authenticated",
1✔
371
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
372
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
373
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
374

1✔
375
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
376
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
377
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
378

1✔
379
                        "GOTRUE_SMTP_HOST=" + utils.InbucketId,
1✔
380
                        "GOTRUE_SMTP_PORT=2500",
1✔
381
                        "GOTRUE_SMTP_ADMIN_EMAIL=admin@email.com",
1✔
382
                        "GOTRUE_SMTP_MAX_FREQUENCY=1s",
1✔
383
                        // TODO: To be reverted to `/auth/v1/verify` once
1✔
384
                        // https://github.com/supabase/supabase/issues/16100
1✔
385
                        // is fixed on upstream GoTrue.
1✔
386
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=http://localhost:%v/auth/v1/verify", utils.Config.Api.Port),
1✔
387
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=http://localhost:%v/auth/v1/verify", utils.Config.Api.Port),
1✔
388
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=http://localhost:%v/auth/v1/verify", utils.Config.Api.Port),
1✔
389
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=http://localhost:%v/auth/v1/verify", utils.Config.Api.Port),
1✔
390
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
391

1✔
392
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
393
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
394
                        "GOTRUE_SMS_MAX_FREQUENCY=5s",
1✔
395
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
396
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
397
                        "GOTRUE_SMS_TEMPLATE=Your code is {{ .Code }}",
1✔
398
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
399

1✔
400
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
401
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
402
                }
1✔
403

1✔
404
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
405
                        if len(tmpl.ContentPath) > 0 {
5✔
406
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
407
                                        strings.ToUpper(id),
×
408
                                        utils.KongId,
×
409
                                        nginxTemplateServerPort,
×
410
                                        id+filepath.Ext(tmpl.ContentPath),
×
411
                                ))
×
412
                        }
×
413
                        if len(tmpl.Subject) > 0 {
5✔
414
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
415
                                        strings.ToUpper(id),
×
416
                                        tmpl.Subject,
×
417
                                ))
×
418
                        }
×
419
                }
420

421
                if utils.Config.Auth.Sms.Twilio.Enabled {
1✔
422
                        env = append(
×
423
                                env,
×
424
                                "GOTRUE_SMS_PROVIDER=twilio",
×
425
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
426
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken,
×
427
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
428
                        )
×
429
                }
×
430
                if utils.Config.Auth.Sms.TwilioVerify.Enabled {
1✔
431
                        env = append(
×
432
                                env,
×
433
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
434
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
435
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken,
×
436
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
437
                        )
×
438
                }
×
439
                if utils.Config.Auth.Sms.Messagebird.Enabled {
1✔
440
                        env = append(
×
441
                                env,
×
442
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
443
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey,
×
444
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
445
                        )
×
446
                }
×
447
                if utils.Config.Auth.Sms.Textlocal.Enabled {
1✔
448
                        env = append(
×
449
                                env,
×
450
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
451
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey,
×
452
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
453
                        )
×
454
                }
×
455
                if utils.Config.Auth.Sms.Vonage.Enabled {
1✔
456
                        env = append(
×
457
                                env,
×
458
                                "GOTRUE_SMS_PROVIDER=vonage",
×
459
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
460
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret,
×
461
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
462
                        )
×
463
                }
×
464

465
                for name, config := range utils.Config.Auth.External {
18✔
466
                        env = append(
17✔
467
                                env,
17✔
468
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
17✔
469
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
17✔
470
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret),
17✔
471
                        )
17✔
472

17✔
473
                        if config.RedirectUri != "" {
17✔
474
                                env = append(env,
×
475
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), config.RedirectUri),
×
476
                                )
×
477
                        } else {
17✔
478
                                env = append(env,
17✔
479
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=http://localhost:%v/auth/v1/callback", strings.ToUpper(name), utils.Config.Api.Port),
17✔
480
                                )
17✔
481
                        }
17✔
482

483
                        if config.Url != "" {
17✔
484
                                env = append(env,
×
485
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url),
×
486
                                )
×
487
                        }
×
488
                }
489

490
                if _, err := utils.DockerStart(
1✔
491
                        ctx,
1✔
492
                        container.Config{
1✔
493
                                Image:        utils.Config.Auth.Image,
1✔
494
                                Env:          env,
1✔
495
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
496
                                Healthcheck: &container.HealthConfig{
1✔
497
                                        Test:     []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health"},
1✔
498
                                        Interval: 10 * time.Second,
1✔
499
                                        Timeout:  2 * time.Second,
1✔
500
                                        Retries:  3,
1✔
501
                                },
1✔
502
                        },
1✔
503
                        start.WithSyslogConfig(container.HostConfig{
1✔
504
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
505
                        }),
1✔
506
                        utils.GotrueId,
1✔
507
                ); err != nil {
1✔
508
                        return err
×
509
                }
×
510
                started = append(started, utils.GotrueId)
1✔
511
        }
512

513
        // Start Inbucket.
514
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.InbucketImage, excluded) {
3✔
515
                inbucketPortBindings := nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
516
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
517
                        inbucketPortBindings["2500/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
518
                }
×
519
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
520
                        inbucketPortBindings["1100/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
521
                }
×
522
                if _, err := utils.DockerStart(
1✔
523
                        ctx,
1✔
524
                        container.Config{
1✔
525
                                Image: utils.InbucketImage,
1✔
526
                        },
1✔
527
                        container.HostConfig{
1✔
528
                                Binds: []string{
1✔
529
                                        // Override default mount points to avoid creating multiple anonymous volumes
1✔
530
                                        // Ref: https://github.com/inbucket/inbucket/blob/v3.0.4/Dockerfile#L52
1✔
531
                                        utils.InbucketId + ":/config",
1✔
532
                                        utils.InbucketId + ":/storage",
1✔
533
                                },
1✔
534
                                PortBindings:  inbucketPortBindings,
1✔
535
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
536
                        },
1✔
537
                        utils.InbucketId,
1✔
538
                ); err != nil {
1✔
539
                        return err
×
540
                }
×
541
                started = append(started, utils.InbucketId)
1✔
542
        }
543

544
        // Start Realtime.
545
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.RealtimeImage, excluded) {
3✔
546
                if _, err := utils.DockerStart(
1✔
547
                        ctx,
1✔
548
                        container.Config{
1✔
549
                                Image: utils.RealtimeImage,
1✔
550
                                Env: []string{
1✔
551
                                        "PORT=4000",
1✔
552
                                        "DB_HOST=" + dbConfig.Host,
1✔
553
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
554
                                        "DB_USER=supabase_admin",
1✔
555
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
556
                                        "DB_NAME=" + dbConfig.Database,
1✔
557
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
558
                                        "DB_ENC_KEY=supabaserealtime",
1✔
559
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
560
                                        "FLY_ALLOC_ID=abc123",
1✔
561
                                        "FLY_APP_NAME=realtime",
1✔
562
                                        "SECRET_KEY_BASE=EAx3IQ/wRG1v47ZD4NE4/9RzBI8Jmil3x0yhcW4V2NHBP6c2iPIzwjofi2Ep4HIG",
1✔
563
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
1✔
564
                                        "ENABLE_TAILSCALE=false",
1✔
565
                                        "DNS_NODES=''",
1✔
566
                                        "RLIMIT_NOFILE=",
1✔
567
                                        "REALTIME_IP_VERSION=" + string(utils.Config.Realtime.IpVersion),
1✔
568
                                },
1✔
569
                                Cmd: []string{
1✔
570
                                        "/bin/sh", "-c",
1✔
571
                                        "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server",
1✔
572
                                },
1✔
573
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
574
                                Healthcheck: &container.HealthConfig{
1✔
575
                                        Test:     []string{"CMD", "bash", "-c", "printf \\0 > /dev/tcp/localhost/4000"},
1✔
576
                                        Interval: 10 * time.Second,
1✔
577
                                        Timeout:  2 * time.Second,
1✔
578
                                        Retries:  3,
1✔
579
                                },
1✔
580
                        },
1✔
581
                        start.WithSyslogConfig(container.HostConfig{
1✔
582
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
583
                        }),
1✔
584
                        utils.RealtimeId,
1✔
585
                ); err != nil {
1✔
586
                        return err
×
587
                }
×
588
                started = append(started, utils.RealtimeId)
1✔
589
        }
590

591
        // Start PostgREST.
592
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
593
                if _, err := utils.DockerStart(
1✔
594
                        ctx,
1✔
595
                        container.Config{
1✔
596
                                Image: utils.Config.Api.Image,
1✔
597
                                Env: []string{
1✔
598
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
599
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
600
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
601
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
602
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
603
                                        "PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
604
                                },
1✔
605
                                // PostgREST does not expose a shell for health check
1✔
606
                        },
1✔
607
                        start.WithSyslogConfig(container.HostConfig{
1✔
608
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
609
                        }),
1✔
610
                        utils.RestId,
1✔
611
                ); err != nil {
1✔
612
                        return err
×
613
                }
×
614
                started = append(started, utils.RestId)
1✔
615
        }
616

617
        // Start Storage.
618
        if utils.Config.Storage.Enabled && !isContainerExcluded(utils.StorageImage, excluded) {
3✔
619
                dockerStoragePath := "/mnt"
1✔
620
                if _, err := utils.DockerStart(
1✔
621
                        ctx,
1✔
622
                        container.Config{
1✔
623
                                Image: utils.StorageImage,
1✔
624
                                Env: []string{
1✔
625
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey,
1✔
626
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
1✔
627
                                        "POSTGREST_URL=http://" + utils.RestId + ":3000",
1✔
628
                                        "PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
629
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
630
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
631
                                        "STORAGE_BACKEND=file",
1✔
632
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
633
                                        "TENANT_ID=stub",
1✔
634
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
635
                                        "REGION=stub",
1✔
636
                                        "GLOBAL_S3_BUCKET=stub",
1✔
637
                                        "ENABLE_IMAGE_TRANSFORMATION=true",
1✔
638
                                        "IMGPROXY_URL=http://" + utils.ImgProxyId + ":5001",
1✔
639
                                },
1✔
640
                                Healthcheck: &container.HealthConfig{
1✔
641
                                        // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
642
                                        Test:     []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5000/status"},
1✔
643
                                        Interval: 10 * time.Second,
1✔
644
                                        Timeout:  2 * time.Second,
1✔
645
                                        Retries:  3,
1✔
646
                                },
1✔
647
                        },
1✔
648
                        start.WithSyslogConfig(container.HostConfig{
1✔
649
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
650
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
651
                        }),
1✔
652
                        utils.StorageId,
1✔
653
                ); err != nil {
1✔
654
                        return err
×
655
                }
×
656
                started = append(started, utils.StorageId)
1✔
657
        }
658

659
        // Start Storage ImgProxy.
660
        if utils.Config.Storage.Enabled && !isContainerExcluded(utils.ImageProxyImage, excluded) {
3✔
661
                if _, err := utils.DockerStart(
1✔
662
                        ctx,
1✔
663
                        container.Config{
1✔
664
                                Image: utils.ImageProxyImage,
1✔
665
                                Env: []string{
1✔
666
                                        "IMGPROXY_BIND=:5001",
1✔
667
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
1✔
668
                                        "IMGPROXY_USE_ETAG=/",
1✔
669
                                },
1✔
670
                                Healthcheck: &container.HealthConfig{
1✔
671
                                        Test:     []string{"CMD", "imgproxy", "health"},
1✔
672
                                        Interval: 10 * time.Second,
1✔
673
                                        Timeout:  2 * time.Second,
1✔
674
                                        Retries:  3,
1✔
675
                                },
1✔
676
                        },
1✔
677
                        container.HostConfig{
1✔
678
                                VolumesFrom:   []string{utils.StorageId},
1✔
679
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
680
                        },
1✔
681
                        utils.ImgProxyId,
1✔
682
                ); err != nil {
1✔
683
                        return err
×
684
                }
×
685
                started = append(started, utils.ImgProxyId)
1✔
686
        }
687

688
        // Start all functions.
689
        if !isContainerExcluded(utils.EdgeRuntimeImage, excluded) {
3✔
690
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
691
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, w, fsys); err != nil {
1✔
692
                        return err
×
693
                }
×
694
                started = append(started, utils.EdgeRuntimeId)
1✔
695
        }
696

697
        // Start pg-meta.
698
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.PgmetaImage, excluded) {
3✔
699
                if _, err := utils.DockerStart(
1✔
700
                        ctx,
1✔
701
                        container.Config{
1✔
702
                                Image: utils.PgmetaImage,
1✔
703
                                Env: []string{
1✔
704
                                        "PG_META_PORT=8080",
1✔
705
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
706
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
707
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
708
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
709
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
710
                                },
1✔
711
                                Healthcheck: &container.HealthConfig{
1✔
712
                                        Test:     []string{"CMD", "node", "-e", "require('http').get('http://localhost:8080/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"},
1✔
713
                                        Interval: 10 * time.Second,
1✔
714
                                        Timeout:  2 * time.Second,
1✔
715
                                        Retries:  3,
1✔
716
                                },
1✔
717
                        },
1✔
718
                        container.HostConfig{
1✔
719
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
720
                        },
1✔
721
                        utils.PgmetaId,
1✔
722
                ); err != nil {
1✔
723
                        return err
×
724
                }
×
725
                started = append(started, utils.PgmetaId)
1✔
726
        }
727

728
        // Start Studio.
729
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.StudioImage, excluded) {
3✔
730
                if _, err := utils.DockerStart(
1✔
731
                        ctx,
1✔
732
                        container.Config{
1✔
733
                                Image: utils.StudioImage,
1✔
734
                                Env: []string{
1✔
735
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
736
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
737
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
738
                                        fmt.Sprintf("SUPABASE_REST_URL=%s:%v/rest/v1/", utils.Config.Studio.ApiUrl, utils.Config.Api.Port),
1✔
739
                                        fmt.Sprintf("SUPABASE_PUBLIC_URL=%s:%v/", utils.Config.Studio.ApiUrl, utils.Config.Api.Port),
1✔
740
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey,
1✔
741
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
1✔
742
                                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
1✔
743
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
744
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
745
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
746
                                },
1✔
747
                                Healthcheck: &container.HealthConfig{
1✔
748
                                        Test:     []string{"CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"},
1✔
749
                                        Interval: 10 * time.Second,
1✔
750
                                        Timeout:  2 * time.Second,
1✔
751
                                        Retries:  3,
1✔
752
                                },
1✔
753
                        },
1✔
754
                        container.HostConfig{
1✔
755
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
756
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
757
                        },
1✔
758
                        utils.StudioId,
1✔
759
                ); err != nil {
1✔
760
                        return err
×
761
                }
×
762
                started = append(started, utils.StudioId)
1✔
763
        }
764

765
        // Start pooler.
766
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.PgbouncerImage, excluded) {
2✔
767
                if _, err := utils.DockerStart(
×
768
                        ctx,
×
769
                        container.Config{
×
770
                                Image: utils.PgbouncerImage,
×
771
                                Env: []string{
×
772
                                        "POSTGRESQL_HOST=" + dbConfig.Host,
×
773
                                        fmt.Sprintf("POSTGRESQL_PORT=%d", dbConfig.Port),
×
774
                                        "POSTGRESQL_USERNAME=pgbouncer",
×
775
                                        "POSTGRESQL_PASSWORD=" + dbConfig.Password,
×
776
                                        "POSTGRESQL_DATABASE=" + dbConfig.Database,
×
777
                                        "PGBOUNCER_AUTH_USER=pgbouncer",
×
778
                                        "PGBOUNCER_AUTH_QUERY=SELECT * FROM pgbouncer.get_auth($1)",
×
779
                                        fmt.Sprintf("PGBOUNCER_POOL_MODE=%s", utils.Config.Db.Pooler.PoolMode),
×
780
                                        fmt.Sprintf("PGBOUNCER_DEFAULT_POOL_SIZE=%d", utils.Config.Db.Pooler.DefaultPoolSize),
×
781
                                        fmt.Sprintf("PGBOUNCER_MAX_CLIENT_CONN=%d", utils.Config.Db.Pooler.MaxClientConn),
×
782
                                        // Default platform config: https://github.com/supabase/postgres/blob/develop/ansible/files/pgbouncer_config/pgbouncer.ini.j2
×
783
                                        "PGBOUNCER_IGNORE_STARTUP_PARAMETERS=extra_float_digits",
×
784
                                },
×
785
                                Healthcheck: &container.HealthConfig{
×
786
                                        Test:     []string{"CMD", "bash", "-c", "printf \\0 > /dev/tcp/localhost/6432"},
×
787
                                        Interval: 10 * time.Second,
×
788
                                        Timeout:  2 * time.Second,
×
789
                                        Retries:  3,
×
790
                                },
×
791
                        },
×
792
                        container.HostConfig{
×
793
                                PortBindings:  nat.PortMap{"6432/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)}}},
×
794
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
795
                        },
×
796
                        utils.PoolerId,
×
797
                ); err != nil {
×
798
                        return err
×
799
                }
×
800
                started = append(started, utils.PoolerId)
×
801
        }
802

803
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
804
        return reset.WaitForServiceReady(ctx, started)
2✔
805
}
806

807
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
20✔
808
        short := utils.ShortContainerImageName(imageName)
20✔
809
        if val, ok := excluded[short]; ok && val {
30✔
810
                return true
10✔
811
        }
10✔
812
        return false
10✔
813
}
814

815
func ExcludableContainers() []string {
2✔
816
        names := []string{}
2✔
817
        for _, image := range utils.ServiceImages {
32✔
818
                names = append(names, utils.ShortContainerImageName(image))
30✔
819
        }
30✔
820
        return names
2✔
821
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc