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

supabase / cli / 8998328342

08 May 2024 07:59AM UTC coverage: 57.396% (-0.07%) from 57.468%
8998328342

Pull #2129

github

J0
fix: add send sms hook as test
Pull Request #2129: feat: add HTTP hook Secret Configuration

26 of 56 new or added lines in 2 files covered. (46.43%)

10 existing lines in 3 files now uncovered.

6402 of 11154 relevant lines covered (57.4%)

656.46 hits per line

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

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

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

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

33
func suggestUpdateCmd(serviceImages map[string]string) string {
×
34
        cmd := "You are running outdated service versions locally:\n"
×
35
        for k, v := range serviceImages {
×
36
                cmd += fmt.Sprintf("%s => %s\n", k, v)
×
37
        }
×
38
        cmd += fmt.Sprintf("Run %s to update them.", utils.Aqua("supabase link"))
×
39
        return cmd
×
40
}
41

42
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
4✔
43
        // Sanity checks.
4✔
44
        {
8✔
45
                if err := utils.LoadConfigFS(fsys); err != nil {
6✔
46
                        return err
2✔
47
                }
2✔
48
                if err := utils.AssertSupabaseDbIsRunning(); err == nil {
3✔
49
                        fmt.Fprintln(os.Stderr, utils.Aqua("supabase start")+" is already running.")
1✔
50
                        utils.CmdSuggestion = fmt.Sprintf("Run %s to show status of local Supabase containers.", utils.Aqua("supabase status"))
1✔
51
                        return nil
1✔
52
                } else if !errors.Is(err, utils.ErrNotRunning) {
3✔
53
                        return err
1✔
54
                }
1✔
55
                if _, err := utils.LoadAccessTokenFS(fsys); err == nil {
×
56
                        if ref, err := flags.LoadProjectRef(fsys); err == nil {
×
57
                                local := services.GetServiceImages()
×
58
                                remote := services.GetRemoteImages(ctx, ref)
×
59
                                for _, image := range local {
×
60
                                        parts := strings.Split(image, ":")
×
61
                                        if version, ok := remote[image]; ok && version == parts[1] {
×
62
                                                delete(remote, image)
×
63
                                        }
×
64
                                }
65
                                if len(remote) > 0 {
×
66
                                        fmt.Fprintln(os.Stderr, suggestUpdateCmd(remote))
×
67
                                }
×
68
                        }
69
                }
70
        }
71

72
        if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error {
×
73
                dbConfig := pgconn.Config{
×
74
                        Host:     utils.DbId,
×
75
                        Port:     5432,
×
76
                        User:     "postgres",
×
77
                        Password: utils.Config.Db.Password,
×
78
                        Database: "postgres",
×
79
                }
×
80
                return run(p, ctx, fsys, excludedContainers, dbConfig)
×
81
        }); err != nil {
×
82
                if ignoreHealthCheck && errors.Is(err, reset.ErrUnhealthy) {
×
83
                        fmt.Fprintln(os.Stderr, err)
×
84
                } else {
×
85
                        if err := utils.DockerRemoveAll(context.Background(), io.Discard); err != nil {
×
86
                                fmt.Fprintln(os.Stderr, err)
×
87
                        }
×
88
                        return err
×
89
                }
90
        }
91

92
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
93
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
94
        return nil
×
95
}
96

97
type kongConfig struct {
98
        GotrueId      string
99
        RestId        string
100
        RealtimeId    string
101
        StorageId     string
102
        PgmetaId      string
103
        EdgeRuntimeId string
104
        LogflareId    string
105
        ApiHost       string
106
        ApiPort       uint16
107
}
108

109
var (
110
        //go:embed templates/kong.yml
111
        kongConfigEmbed    string
112
        kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
113

114
        //go:embed templates/custom_nginx.template
115
        nginxConfigEmbed string
116
        // Hardcoded configs which match nginxConfigEmbed
117
        nginxEmailTemplateDir   = "/home/kong/templates/email"
118
        nginxTemplateServerPort = 8088
119
)
120

121
type vectorConfig struct {
122
        ApiKey        string
123
        LogflareId    string
124
        KongId        string
125
        GotrueId      string
126
        RestId        string
127
        RealtimeId    string
128
        StorageId     string
129
        EdgeRuntimeId string
130
        DbId          string
131
}
132

133
var (
134
        //go:embed templates/vector.yaml
135
        vectorConfigEmbed    string
136
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
137
)
138

139
func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
140
        excluded := make(map[string]bool)
2✔
141
        for _, name := range excludedContainers {
20✔
142
                excluded[name] = true
18✔
143
        }
18✔
144

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

206
        // Start Postgres.
207
        w := utils.StatusWriter{Program: p}
2✔
208
        if dbConfig.Host == utils.DbId {
4✔
209
                if err := start.StartDatabase(ctx, fsys, w, options...); err != nil {
2✔
210
                        return err
×
211
                }
×
212
        }
213

214
        var started []string
2✔
215
        // Start Logflare
2✔
216
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.LogflareImage, excluded) {
2✔
217
                env := []string{
×
218
                        "DB_DATABASE=" + dbConfig.Database,
×
219
                        "DB_HOSTNAME=" + dbConfig.Host,
×
220
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
×
221
                        "DB_SCHEMA=_analytics",
×
222
                        "DB_USERNAME=supabase_admin",
×
223
                        "DB_PASSWORD=" + dbConfig.Password,
×
224
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
×
225
                        "LOGFLARE_SINGLE_TENANT=true",
×
226
                        "LOGFLARE_SUPABASE_MODE=true",
×
227
                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
×
228
                        "LOGFLARE_LOG_LEVEL=warn",
×
229
                        "LOGFLARE_NODE_HOST=127.0.0.1",
×
230
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
×
231
                        "RELEASE_COOKIE=cookie",
×
232
                }
×
233
                bind := []string{}
×
234

×
235
                switch utils.Config.Analytics.Backend {
×
236
                case utils.LogflareBigQuery:
×
237
                        workdir, err := os.Getwd()
×
238
                        if err != nil {
×
239
                                return errors.Errorf("failed to get working directory: %w", err)
×
240
                        }
×
241
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
242
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
243
                        // This is hardcoded in studio frontend
×
244
                        env = append(env,
×
245
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
246
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
247
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
248
                        )
×
249
                case utils.LogflarePostgres:
×
250
                        env = append(env,
×
251
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
×
252
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
×
253
                        )
×
254
                }
255

256
                if _, err := utils.DockerStart(
×
257
                        ctx,
×
258
                        container.Config{
×
259
                                Hostname: "127.0.0.1",
×
260
                                Image:    utils.LogflareImage,
×
261
                                Env:      env,
×
262
                                // Original entrypoint conflicts with healthcheck due to 15 seconds sleep:
×
263
                                // https://github.com/Logflare/logflare/blob/staging/run.sh#L35
×
264
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > run.sh && sh run.sh
×
265
./logflare eval Logflare.Release.migrate
×
266
./logflare start --sname logflare
×
267
EOF
×
268
`},
×
269
                                Healthcheck: &container.HealthConfig{
×
270
                                        Test:        []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/health"},
×
271
                                        Interval:    10 * time.Second,
×
272
                                        Timeout:     2 * time.Second,
×
273
                                        Retries:     3,
×
274
                                        StartPeriod: 10 * time.Second,
×
275
                                },
×
276
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
×
277
                        },
×
278
                        container.HostConfig{
×
279
                                Binds:         bind,
×
280
                                PortBindings:  nat.PortMap{"4000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.Port), 10)}}},
×
281
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
282
                        },
×
283
                        network.NetworkingConfig{
×
284
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
285
                                        utils.NetId: {
×
286
                                                Aliases: utils.LogflareAliases,
×
287
                                        },
×
288
                                },
×
289
                        },
×
290
                        utils.LogflareId,
×
291
                ); err != nil {
×
292
                        return err
×
293
                }
×
294
                if err := reset.WaitForServiceReady(ctx, []string{utils.LogflareId}); err != nil {
×
295
                        return err
×
296
                }
×
297
        }
298

299
        // Start Kong.
300
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
301
        if !isContainerExcluded(utils.KongImage, excluded) {
3✔
302
                var kongConfigBuf bytes.Buffer
1✔
303
                if err := kongConfigTemplate.Execute(&kongConfigBuf, kongConfig{
1✔
304
                        GotrueId:      utils.GotrueId,
1✔
305
                        RestId:        utils.RestId,
1✔
306
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
307
                        StorageId:     utils.StorageId,
1✔
308
                        PgmetaId:      utils.PgmetaId,
1✔
309
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
310
                        LogflareId:    utils.LogflareId,
1✔
311
                        ApiHost:       utils.Config.Hostname,
1✔
312
                        ApiPort:       utils.Config.Api.Port,
1✔
313
                }); err != nil {
1✔
314
                        return errors.Errorf("failed to exec template: %w", err)
×
315
                }
×
316

317
                binds := []string{}
1✔
318
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
319
                        if len(tmpl.ContentPath) == 0 {
10✔
320
                                continue
5✔
321
                        }
322
                        hostPath := tmpl.ContentPath
×
323
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
324
                                var err error
×
325
                                hostPath, err = filepath.Abs(hostPath)
×
326
                                if err != nil {
×
327
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
328
                                }
×
329
                        }
330
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
331
                        binds = append(binds, fmt.Sprintf("%s:%s:rw,z", hostPath, dockerPath))
×
332
                }
333

334
                if _, err := utils.DockerStart(
1✔
335
                        ctx,
1✔
336
                        container.Config{
1✔
337
                                Image: utils.KongImage,
1✔
338
                                Env: []string{
1✔
339
                                        "KONG_DATABASE=off",
1✔
340
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
341
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
342
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
343
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
344
                                        // sizeable response headers azure can generate
1✔
345
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
346
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
347
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
348
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
349
                                },
1✔
350
                                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✔
351
` + kongConfigBuf.String() + `
1✔
352
EOF
1✔
353
` + nginxConfigEmbed + `
1✔
354
EOF
1✔
355
`},
1✔
356
                        },
1✔
357
                        start.WithSyslogConfig(container.HostConfig{
1✔
358
                                Binds:         binds,
1✔
359
                                PortBindings:  nat.PortMap{"8000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)}}},
1✔
360
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
361
                        }),
1✔
362
                        network.NetworkingConfig{
1✔
363
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
364
                                        utils.NetId: {
1✔
365
                                                Aliases: utils.KongAliases,
1✔
366
                                        },
1✔
367
                                },
1✔
368
                        },
1✔
369
                        utils.KongId,
1✔
370
                ); err != nil {
1✔
371
                        return err
×
372
                }
×
373
                started = append(started, utils.KongId)
1✔
374
        }
375

376
        // Start GoTrue.
377
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
378
                var testOTP bytes.Buffer
1✔
379
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
380
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
381
                }
×
382

383
                env := []string{
1✔
384
                        fmt.Sprintf("API_EXTERNAL_URL=http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port),
1✔
385

1✔
386
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
387
                        "GOTRUE_API_PORT=9999",
1✔
388

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

1✔
392
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
393
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
394
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
395

1✔
396
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
397
                        "GOTRUE_JWT_AUD=authenticated",
1✔
398
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
399
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
400
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
401
                        fmt.Sprintf("GOTRUE_JWT_ISSUER=http://%s:%d/auth/v1", utils.Config.Hostname, utils.Config.Api.Port),
1✔
402

1✔
403
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
404
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
405
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
406

1✔
407
                        fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns),
1✔
408

1✔
409
                        "GOTRUE_SMTP_HOST=" + utils.InbucketId,
1✔
410
                        "GOTRUE_SMTP_PORT=2500",
1✔
411
                        "GOTRUE_SMTP_ADMIN_EMAIL=admin@email.com",
1✔
412
                        fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency),
1✔
413
                        // TODO: To be reverted to `/auth/v1/verify` once
1✔
414
                        // https://github.com/supabase/supabase/issues/16100
1✔
415
                        // is fixed on upstream GoTrue.
1✔
416
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
417
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
418
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
419
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
420
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
421

1✔
422
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
423
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
424
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
425
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
426
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
427
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
428
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
429

1✔
430
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
431
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
432
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
433
                }
1✔
434

1✔
435
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
436
                        if len(tmpl.ContentPath) > 0 {
5✔
437
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
438
                                        strings.ToUpper(id),
×
439
                                        utils.KongId,
×
440
                                        nginxTemplateServerPort,
×
441
                                        id+filepath.Ext(tmpl.ContentPath),
×
442
                                ))
×
443
                        }
×
444
                        if len(tmpl.Subject) > 0 {
5✔
445
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
446
                                        strings.ToUpper(id),
×
447
                                        tmpl.Subject,
×
448
                                ))
×
449
                        }
×
450
                }
451

452
                if utils.Config.Auth.Sms.Twilio.Enabled {
1✔
453
                        env = append(
×
454
                                env,
×
455
                                "GOTRUE_SMS_PROVIDER=twilio",
×
456
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
457
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken,
×
458
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
459
                        )
×
460
                }
×
461
                if utils.Config.Auth.Sms.TwilioVerify.Enabled {
1✔
462
                        env = append(
×
463
                                env,
×
464
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
465
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
466
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken,
×
467
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
468
                        )
×
469
                }
×
470
                if utils.Config.Auth.Sms.Messagebird.Enabled {
1✔
471
                        env = append(
×
472
                                env,
×
473
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
474
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey,
×
475
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
476
                        )
×
477
                }
×
478
                if utils.Config.Auth.Sms.Textlocal.Enabled {
1✔
479
                        env = append(
×
480
                                env,
×
481
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
482
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey,
×
483
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
484
                        )
×
485
                }
×
486
                if utils.Config.Auth.Sms.Vonage.Enabled {
1✔
487
                        env = append(
×
488
                                env,
×
489
                                "GOTRUE_SMS_PROVIDER=vonage",
×
490
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
491
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret,
×
492
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
493
                        )
×
494
                }
×
495
                if utils.Config.Auth.Hook.MFAVerificationAttempt.Enabled {
1✔
496
                        env = append(
×
497
                                env,
×
498
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
499
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.MFAVerificationAttempt.URI,
×
NEW
500
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets,
×
501
                        )
×
502
                }
×
503

504
                if utils.Config.Auth.Hook.PasswordVerificationAttempt.Enabled {
1✔
505
                        env = append(
×
506
                                env,
×
507
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
508
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.PasswordVerificationAttempt.URI,
×
NEW
509
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets,
×
510
                        )
×
511
                }
×
512

513
                if utils.Config.Auth.Hook.CustomAccessToken.Enabled {
1✔
514
                        env = append(
×
515
                                env,
×
516
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
517
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+utils.Config.Auth.Hook.CustomAccessToken.URI,
×
NEW
518
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+utils.Config.Auth.Hook.CustomAccessToken.Secrets,
×
NEW
519
                        )
×
NEW
520
                }
×
521

522
                if utils.Config.Auth.Hook.SendSMS.Enabled {
1✔
NEW
523
                        env = append(
×
NEW
524
                                env,
×
NEW
525
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
NEW
526
                                "GOTRUE_HOOK_SEND_SMS_URI="+utils.Config.Auth.Hook.SendSMS.URI,
×
NEW
527
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+utils.Config.Auth.Hook.SendSMS.Secrets,
×
NEW
528
                        )
×
NEW
529
                }
×
530

531
                if utils.Config.Auth.Hook.SendEmail.Enabled {
1✔
NEW
532
                        env = append(
×
NEW
533
                                env,
×
NEW
534
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
NEW
535
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+utils.Config.Auth.Hook.SendEmail.URI,
×
NEW
536
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets,
×
537
                        )
×
538
                }
×
539

540
                for name, config := range utils.Config.Auth.External {
19✔
541
                        env = append(
18✔
542
                                env,
18✔
543
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
18✔
544
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
18✔
545
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret),
18✔
546
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
18✔
547
                        )
18✔
548

18✔
549
                        if config.RedirectUri != "" {
18✔
550
                                env = append(env,
×
551
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), config.RedirectUri),
×
552
                                )
×
553
                        } else {
18✔
554
                                env = append(env,
18✔
555
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=http://%s:%d/auth/v1/callback", strings.ToUpper(name), utils.Config.Hostname, utils.Config.Api.Port),
18✔
556
                                )
18✔
557
                        }
18✔
558

559
                        if config.Url != "" {
18✔
560
                                env = append(env,
×
561
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url),
×
562
                                )
×
563
                        }
×
564
                }
565

566
                if _, err := utils.DockerStart(
1✔
567
                        ctx,
1✔
568
                        container.Config{
1✔
569
                                Image:        utils.Config.Auth.Image,
1✔
570
                                Env:          env,
1✔
571
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
572
                                Healthcheck: &container.HealthConfig{
1✔
573
                                        Test:     []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9999/health"},
1✔
574
                                        Interval: 10 * time.Second,
1✔
575
                                        Timeout:  2 * time.Second,
1✔
576
                                        Retries:  3,
1✔
577
                                },
1✔
578
                        },
1✔
579
                        start.WithSyslogConfig(container.HostConfig{
1✔
580
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
581
                        }),
1✔
582
                        network.NetworkingConfig{
1✔
583
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
584
                                        utils.NetId: {
1✔
585
                                                Aliases: utils.GotrueAliases,
1✔
586
                                        },
1✔
587
                                },
1✔
588
                        },
1✔
589
                        utils.GotrueId,
1✔
590
                ); err != nil {
1✔
591
                        return err
×
592
                }
×
593
                started = append(started, utils.GotrueId)
1✔
594
        }
595

596
        // Start Inbucket.
597
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.InbucketImage, excluded) {
3✔
598
                inbucketPortBindings := nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
599
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
600
                        inbucketPortBindings["2500/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
601
                }
×
602
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
603
                        inbucketPortBindings["1100/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
604
                }
×
605
                if _, err := utils.DockerStart(
1✔
606
                        ctx,
1✔
607
                        container.Config{
1✔
608
                                Image: utils.InbucketImage,
1✔
609
                        },
1✔
610
                        container.HostConfig{
1✔
611
                                Binds: []string{
1✔
612
                                        // Override default mount points to avoid creating multiple anonymous volumes
1✔
613
                                        // Ref: https://github.com/inbucket/inbucket/blob/v3.0.4/Dockerfile#L52
1✔
614
                                        utils.InbucketId + ":/config",
1✔
615
                                        utils.InbucketId + ":/storage",
1✔
616
                                },
1✔
617
                                PortBindings:  inbucketPortBindings,
1✔
618
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
619
                        },
1✔
620
                        network.NetworkingConfig{
1✔
621
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
622
                                        utils.NetId: {
1✔
623
                                                Aliases: utils.InbucketAliases,
1✔
624
                                        },
1✔
625
                                },
1✔
626
                        },
1✔
627
                        utils.InbucketId,
1✔
628
                ); err != nil {
1✔
629
                        return err
×
630
                }
×
631
                started = append(started, utils.InbucketId)
1✔
632
        }
633

634
        // Start Realtime.
635
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.RealtimeImage, excluded) {
3✔
636
                if _, err := utils.DockerStart(
1✔
637
                        ctx,
1✔
638
                        container.Config{
1✔
639
                                Image: utils.RealtimeImage,
1✔
640
                                Env: []string{
1✔
641
                                        "PORT=4000",
1✔
642
                                        "DB_HOST=" + dbConfig.Host,
1✔
643
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
644
                                        "DB_USER=supabase_admin",
1✔
645
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
646
                                        "DB_NAME=" + dbConfig.Database,
1✔
647
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
648
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
649
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
650
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
651
                                        "FLY_APP_NAME=realtime",
1✔
652
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
653
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
654
                                        "ENABLE_TAILSCALE=false",
1✔
655
                                        "DNS_NODES=''",
1✔
656
                                        "RLIMIT_NOFILE=",
1✔
657
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
658
                                },
1✔
659
                                // TODO: remove this after deprecating PG14
1✔
660
                                Cmd: []string{
1✔
661
                                        "/bin/sh", "-c",
1✔
662
                                        "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server",
1✔
663
                                },
1✔
664
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
665
                                Healthcheck: &container.HealthConfig{
1✔
666
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "-H", "Authorization: Bearer " + utils.Config.Auth.AnonKey,
1✔
667
                                                fmt.Sprintf("http://127.0.0.1:4000/api/tenants/%s/health", utils.Config.Realtime.TenantId),
1✔
668
                                        },
1✔
669
                                        Interval: 10 * time.Second,
1✔
670
                                        Timeout:  2 * time.Second,
1✔
671
                                        Retries:  3,
1✔
672
                                },
1✔
673
                        },
1✔
674
                        start.WithSyslogConfig(container.HostConfig{
1✔
675
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
676
                        }),
1✔
677
                        network.NetworkingConfig{
1✔
678
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
679
                                        utils.NetId: {
1✔
680
                                                Aliases: utils.RealtimeAliases,
1✔
681
                                        },
1✔
682
                                },
1✔
683
                        },
1✔
684
                        utils.RealtimeId,
1✔
685
                ); err != nil {
1✔
686
                        return err
×
687
                }
×
688
                started = append(started, utils.RealtimeId)
1✔
689
        }
690

691
        // Start PostgREST.
692
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
693
                if _, err := utils.DockerStart(
1✔
694
                        ctx,
1✔
695
                        container.Config{
1✔
696
                                Image: utils.Config.Api.Image,
1✔
697
                                Env: []string{
1✔
698
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
699
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
700
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
701
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
702
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
703
                                        "PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
704
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
705
                                },
1✔
706
                                // PostgREST does not expose a shell for health check
1✔
707
                        },
1✔
708
                        start.WithSyslogConfig(container.HostConfig{
1✔
709
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
710
                        }),
1✔
711
                        network.NetworkingConfig{
1✔
712
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
713
                                        utils.NetId: {
1✔
714
                                                Aliases: utils.RestAliases,
1✔
715
                                        },
1✔
716
                                },
1✔
717
                        },
1✔
718
                        utils.RestId,
1✔
719
                ); err != nil {
1✔
720
                        return err
×
721
                }
×
722
                started = append(started, utils.RestId)
1✔
723
        }
724

725
        // Start Storage.
726
        if utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded) {
3✔
727
                dockerStoragePath := "/mnt"
1✔
728
                if _, err := utils.DockerStart(
1✔
729
                        ctx,
1✔
730
                        container.Config{
1✔
731
                                Image: utils.Config.Storage.Image,
1✔
732
                                Env: []string{
1✔
733
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey,
1✔
734
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
1✔
735
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
736
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
737
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
738
                                        "STORAGE_BACKEND=file",
1✔
739
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
740
                                        "TENANT_ID=stub",
1✔
741
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
742
                                        "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
1✔
743
                                        "GLOBAL_S3_BUCKET=stub",
1✔
744
                                        fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", utils.Config.Storage.ImageTransformation.Enabled),
1✔
745
                                        fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
1✔
746
                                        "TUS_URL_PATH=/storage/v1/upload/resumable",
1✔
747
                                        "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
1✔
748
                                        "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
1✔
749
                                        "S3_PROTOCOL_PREFIX=/storage/v1",
1✔
750
                                        "S3_ALLOW_FORWARDED_HEADER=true",
1✔
751
                                        "UPLOAD_FILE_SIZE_LIMIT=52428800000",
1✔
752
                                        "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
1✔
753
                                },
1✔
754
                                Healthcheck: &container.HealthConfig{
1✔
755
                                        // For some reason, 127.0.0.1 resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
756
                                        Test:     []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5000/status"},
1✔
757
                                        Interval: 10 * time.Second,
1✔
758
                                        Timeout:  2 * time.Second,
1✔
759
                                        Retries:  3,
1✔
760
                                },
1✔
761
                        },
1✔
762
                        start.WithSyslogConfig(container.HostConfig{
1✔
763
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
764
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
765
                        }),
1✔
766
                        network.NetworkingConfig{
1✔
767
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
768
                                        utils.NetId: {
1✔
769
                                                Aliases: utils.StorageAliases,
1✔
770
                                        },
1✔
771
                                },
1✔
772
                        },
1✔
773
                        utils.StorageId,
1✔
774
                ); err != nil {
1✔
775
                        return err
×
776
                }
×
777
                started = append(started, utils.StorageId)
1✔
778
        }
779

780
        // Start Storage ImgProxy.
781
        if utils.Config.Storage.Enabled && utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.ImageProxyImage, excluded) {
3✔
782
                if _, err := utils.DockerStart(
1✔
783
                        ctx,
1✔
784
                        container.Config{
1✔
785
                                Image: utils.ImageProxyImage,
1✔
786
                                Env: []string{
1✔
787
                                        "IMGPROXY_BIND=:5001",
1✔
788
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
1✔
789
                                        "IMGPROXY_USE_ETAG=/",
1✔
790
                                },
1✔
791
                                Healthcheck: &container.HealthConfig{
1✔
792
                                        Test:     []string{"CMD", "imgproxy", "health"},
1✔
793
                                        Interval: 10 * time.Second,
1✔
794
                                        Timeout:  2 * time.Second,
1✔
795
                                        Retries:  3,
1✔
796
                                },
1✔
797
                        },
1✔
798
                        container.HostConfig{
1✔
799
                                VolumesFrom:   []string{utils.StorageId},
1✔
800
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
801
                        },
1✔
802
                        network.NetworkingConfig{
1✔
803
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
804
                                        utils.NetId: {
1✔
805
                                                Aliases: utils.ImgProxyAliases,
1✔
806
                                        },
1✔
807
                                },
1✔
808
                        },
1✔
809
                        utils.ImgProxyId,
1✔
810
                ); err != nil {
1✔
811
                        return err
×
812
                }
×
813
                started = append(started, utils.ImgProxyId)
1✔
814
        }
815

816
        // Start all functions.
817
        if !isContainerExcluded(utils.EdgeRuntimeImage, excluded) {
3✔
818
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
819
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, w, fsys); err != nil {
1✔
820
                        return err
×
821
                }
×
822
                started = append(started, utils.EdgeRuntimeId)
1✔
823
        }
824

825
        // Start pg-meta.
826
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.PgmetaImage, excluded) {
3✔
827
                if _, err := utils.DockerStart(
1✔
828
                        ctx,
1✔
829
                        container.Config{
1✔
830
                                Image: utils.PgmetaImage,
1✔
831
                                Env: []string{
1✔
832
                                        "PG_META_PORT=8080",
1✔
833
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
834
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
835
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
836
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
837
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
838
                                },
1✔
839
                                Healthcheck: &container.HealthConfig{
1✔
840
                                        Test:     []string{"CMD", "node", "-e", "fetch('http://127.0.0.1:8080/health').then((r) => {if (r.status !== 200) throw new Error(r.status)})"},
1✔
841
                                        Interval: 10 * time.Second,
1✔
842
                                        Timeout:  2 * time.Second,
1✔
843
                                        Retries:  3,
1✔
844
                                },
1✔
845
                        },
1✔
846
                        container.HostConfig{
1✔
847
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
848
                        },
1✔
849
                        network.NetworkingConfig{
1✔
850
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
851
                                        utils.NetId: {
1✔
852
                                                Aliases: utils.PgmetaAliases,
1✔
853
                                        },
1✔
854
                                },
1✔
855
                        },
1✔
856
                        utils.PgmetaId,
1✔
857
                ); err != nil {
1✔
858
                        return err
×
859
                }
×
860
                started = append(started, utils.PgmetaId)
1✔
861
        }
862

863
        // Start Studio.
864
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.StudioImage, excluded) {
3✔
865
                if _, err := utils.DockerStart(
1✔
866
                        ctx,
1✔
867
                        container.Config{
1✔
868
                                Image: utils.StudioImage,
1✔
869
                                Env: []string{
1✔
870
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
871
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
872
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
873
                                        fmt.Sprintf("SUPABASE_PUBLIC_URL=%s:%v/", utils.Config.Studio.ApiUrl, utils.Config.Api.Port),
1✔
874
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey,
1✔
875
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
1✔
876
                                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
1✔
877
                                        "OPENAI_KEY=" + utils.Config.Studio.OpenaiApiKey,
1✔
878
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
879
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
880
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
881
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
882
                                        "HOSTNAME=0.0.0.0",
1✔
883
                                },
1✔
884
                                Healthcheck: &container.HealthConfig{
1✔
885
                                        Test:     []string{"CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"},
1✔
886
                                        Interval: 10 * time.Second,
1✔
887
                                        Timeout:  2 * time.Second,
1✔
888
                                        Retries:  3,
1✔
889
                                },
1✔
890
                        },
1✔
891
                        container.HostConfig{
1✔
892
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
893
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
894
                        },
1✔
895
                        network.NetworkingConfig{
1✔
896
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
897
                                        utils.NetId: {
1✔
898
                                                Aliases: utils.StudioAliases,
1✔
899
                                        },
1✔
900
                                },
1✔
901
                        },
1✔
902
                        utils.StudioId,
1✔
903
                ); err != nil {
1✔
904
                        return err
×
905
                }
×
906
                started = append(started, utils.StudioId)
1✔
907
        }
908

909
        // Start pooler.
910
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.PgbouncerImage, excluded) {
2✔
911
                if _, err := utils.DockerStart(
×
912
                        ctx,
×
913
                        container.Config{
×
914
                                Image: utils.PgbouncerImage,
×
915
                                Env: []string{
×
916
                                        "POSTGRESQL_HOST=" + dbConfig.Host,
×
917
                                        fmt.Sprintf("POSTGRESQL_PORT=%d", dbConfig.Port),
×
918
                                        "POSTGRESQL_USERNAME=pgbouncer",
×
919
                                        "POSTGRESQL_PASSWORD=" + dbConfig.Password,
×
920
                                        "POSTGRESQL_DATABASE=" + dbConfig.Database,
×
921
                                        "PGBOUNCER_AUTH_USER=pgbouncer",
×
922
                                        "PGBOUNCER_AUTH_QUERY=SELECT * FROM pgbouncer.get_auth($1)",
×
923
                                        fmt.Sprintf("PGBOUNCER_POOL_MODE=%s", utils.Config.Db.Pooler.PoolMode),
×
924
                                        fmt.Sprintf("PGBOUNCER_DEFAULT_POOL_SIZE=%d", utils.Config.Db.Pooler.DefaultPoolSize),
×
925
                                        fmt.Sprintf("PGBOUNCER_MAX_CLIENT_CONN=%d", utils.Config.Db.Pooler.MaxClientConn),
×
926
                                        // Default platform config: https://github.com/supabase/postgres/blob/develop/ansible/files/pgbouncer_config/pgbouncer.ini.j2
×
927
                                        "PGBOUNCER_IGNORE_STARTUP_PARAMETERS=extra_float_digits",
×
928
                                },
×
929
                                Healthcheck: &container.HealthConfig{
×
930
                                        Test:     []string{"CMD", "bash", "-c", "printf \\0 > /dev/tcp/127.0.0.1/6432"},
×
931
                                        Interval: 10 * time.Second,
×
932
                                        Timeout:  2 * time.Second,
×
933
                                        Retries:  3,
×
934
                                },
×
935
                        },
×
936
                        container.HostConfig{
×
937
                                PortBindings:  nat.PortMap{"6432/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)}}},
×
938
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
939
                        },
×
940
                        network.NetworkingConfig{
×
941
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
942
                                        utils.NetId: {
×
943
                                                Aliases: utils.PoolerAliases,
×
944
                                        },
×
945
                                },
×
946
                        },
×
947
                        utils.PoolerId,
×
948
                ); err != nil {
×
949
                        return err
×
950
                }
×
951
                started = append(started, utils.PoolerId)
×
952
        }
953

954
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
955
        return reset.WaitForServiceReady(ctx, started)
2✔
956
}
957

958
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
20✔
959
        short := utils.ShortContainerImageName(imageName)
20✔
960
        if val, ok := excluded[short]; ok && val {
30✔
961
                return true
10✔
962
        }
10✔
963
        return false
10✔
964
}
965

966
func ExcludableContainers() []string {
68✔
967
        names := []string{}
68✔
968
        for _, image := range utils.ServiceImages {
1,156✔
969
                names = append(names, utils.ShortContainerImageName(image))
1,088✔
970
        }
1,088✔
971
        return names
68✔
972
}
973

974
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
975
        numOfKeyPairs := len(input)
5✔
976
        i := 0
5✔
977
        for k, v := range input {
15✔
978
                output.WriteString(k)
10✔
979
                output.WriteString(":")
10✔
980
                output.WriteString(v)
10✔
981
                i++
10✔
982
                if i < numOfKeyPairs {
16✔
983
                        output.WriteString(",")
6✔
984
                }
6✔
985
        }
986
}
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