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

supabase / cli / 9444125855

10 Jun 2024 07:21AM UTC coverage: 60.042% (+0.01%) from 60.03%
9444125855

push

github

web-flow
feat: support custom pooler and realtime image (#2401)

6 of 15 new or added lines in 4 files covered. (40.0%)

2 existing lines in 1 file now uncovered.

6909 of 11507 relevant lines covered (60.04%)

632.89 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

137
type poolerTenant struct {
138
        DbHost            string
139
        DbPort            uint16
140
        DbDatabase        string
141
        DbPassword        string
142
        ExternalId        string
143
        ModeType          utils.PoolMode
144
        DefaultMaxClients uint
145
        DefaultPoolSize   uint
146
}
147

148
var (
149
        //go:embed templates/pooler.exs
150
        poolerTenantEmbed    string
151
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
152
)
153

154
var serviceTimeout = 30 * time.Second
155

156
func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
157
        excluded := make(map[string]bool)
2✔
158
        for _, name := range excludedContainers {
17✔
159
                excluded[name] = true
15✔
160
        }
15✔
161

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

220
        // Start Postgres.
221
        w := utils.StatusWriter{Program: p}
2✔
222
        if dbConfig.Host == utils.DbId {
4✔
223
                if err := start.StartDatabase(ctx, fsys, w, options...); err != nil {
2✔
224
                        return err
×
225
                }
×
226
        }
227

228
        var started []string
2✔
229
        // Start Logflare
2✔
230
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.LogflareImage, excluded) {
2✔
231
                env := []string{
×
232
                        "DB_DATABASE=" + dbConfig.Database,
×
233
                        "DB_HOSTNAME=" + dbConfig.Host,
×
234
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
×
235
                        "DB_SCHEMA=_analytics",
×
236
                        "DB_USERNAME=supabase_admin",
×
237
                        "DB_PASSWORD=" + dbConfig.Password,
×
238
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
×
239
                        "LOGFLARE_SINGLE_TENANT=true",
×
240
                        "LOGFLARE_SUPABASE_MODE=true",
×
241
                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
×
242
                        "LOGFLARE_LOG_LEVEL=warn",
×
243
                        "LOGFLARE_NODE_HOST=127.0.0.1",
×
244
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
×
245
                        "RELEASE_COOKIE=cookie",
×
246
                }
×
247
                bind := []string{}
×
248

×
249
                switch utils.Config.Analytics.Backend {
×
250
                case utils.LogflareBigQuery:
×
251
                        workdir, err := os.Getwd()
×
252
                        if err != nil {
×
253
                                return errors.Errorf("failed to get working directory: %w", err)
×
254
                        }
×
255
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
256
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
257
                        // This is hardcoded in studio frontend
×
258
                        env = append(env,
×
259
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
260
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
261
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
262
                        )
×
263
                case utils.LogflarePostgres:
×
264
                        env = append(env,
×
265
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
×
266
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
×
267
                        )
×
268
                }
269

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

315
        // Start Kong.
316
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
317
        if !isContainerExcluded(utils.KongImage, excluded) {
3✔
318
                var kongConfigBuf bytes.Buffer
1✔
319
                if err := kongConfigTemplate.Execute(&kongConfigBuf, kongConfig{
1✔
320
                        GotrueId:      utils.GotrueId,
1✔
321
                        RestId:        utils.RestId,
1✔
322
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
323
                        StorageId:     utils.StorageId,
1✔
324
                        PgmetaId:      utils.PgmetaId,
1✔
325
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
326
                        LogflareId:    utils.LogflareId,
1✔
327
                        ApiHost:       utils.Config.Hostname,
1✔
328
                        ApiPort:       utils.Config.Api.Port,
1✔
329
                }); err != nil {
1✔
330
                        return errors.Errorf("failed to exec template: %w", err)
×
331
                }
×
332

333
                binds := []string{}
1✔
334
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
335
                        if len(tmpl.ContentPath) == 0 {
10✔
336
                                continue
5✔
337
                        }
338
                        hostPath := tmpl.ContentPath
×
339
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
340
                                var err error
×
341
                                hostPath, err = filepath.Abs(hostPath)
×
342
                                if err != nil {
×
343
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
344
                                }
×
345
                        }
346
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
347
                        binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath))
×
348
                }
349

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

392
        // Start GoTrue.
393
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
394
                var testOTP bytes.Buffer
1✔
395
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
396
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
397
                }
×
398

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

1✔
402
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
403
                        "GOTRUE_API_PORT=9999",
1✔
404

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

1✔
408
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
409
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
410
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
411

1✔
412
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
413
                        "GOTRUE_JWT_AUD=authenticated",
1✔
414
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
415
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
416
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
417
                        fmt.Sprintf("GOTRUE_JWT_ISSUER=http://%s:%d/auth/v1", utils.Config.Hostname, utils.Config.Api.Port),
1✔
418

1✔
419
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
420
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
421
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
422

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

1✔
425
                        "GOTRUE_SMTP_HOST=" + utils.InbucketId,
1✔
426
                        "GOTRUE_SMTP_PORT=2500",
1✔
427
                        "GOTRUE_SMTP_ADMIN_EMAIL=admin@email.com",
1✔
428
                        fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency),
1✔
429
                        // TODO: To be reverted to `/auth/v1/verify` once
1✔
430
                        // https://github.com/supabase/supabase/issues/16100
1✔
431
                        // is fixed on upstream GoTrue.
1✔
432
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
433
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
434
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
435
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port),
1✔
436
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
437

1✔
438
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
439
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
440
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
441
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
442
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
443
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
444
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
445

1✔
446
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
447
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
448
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
449
                }
1✔
450

1✔
451
                for id, tmpl := range utils.Config.Auth.Email.Template {
6✔
452
                        if len(tmpl.ContentPath) > 0 {
5✔
453
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
454
                                        strings.ToUpper(id),
×
455
                                        utils.KongId,
×
456
                                        nginxTemplateServerPort,
×
457
                                        id+filepath.Ext(tmpl.ContentPath),
×
458
                                ))
×
459
                        }
×
460
                        if len(tmpl.Subject) > 0 {
5✔
461
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
462
                                        strings.ToUpper(id),
×
463
                                        tmpl.Subject,
×
464
                                ))
×
465
                        }
×
466
                }
467

468
                if utils.Config.Auth.Sms.Twilio.Enabled {
1✔
469
                        env = append(
×
470
                                env,
×
471
                                "GOTRUE_SMS_PROVIDER=twilio",
×
472
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
473
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken,
×
474
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
475
                        )
×
476
                }
×
477
                if utils.Config.Auth.Sms.TwilioVerify.Enabled {
1✔
478
                        env = append(
×
479
                                env,
×
480
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
481
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
482
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken,
×
483
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
484
                        )
×
485
                }
×
486
                if utils.Config.Auth.Sms.Messagebird.Enabled {
1✔
487
                        env = append(
×
488
                                env,
×
489
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
490
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey,
×
491
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
492
                        )
×
493
                }
×
494
                if utils.Config.Auth.Sms.Textlocal.Enabled {
1✔
495
                        env = append(
×
496
                                env,
×
497
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
498
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey,
×
499
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
500
                        )
×
501
                }
×
502
                if utils.Config.Auth.Sms.Vonage.Enabled {
1✔
503
                        env = append(
×
504
                                env,
×
505
                                "GOTRUE_SMS_PROVIDER=vonage",
×
506
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
507
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret,
×
508
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
509
                        )
×
510
                }
×
511
                if utils.Config.Auth.Hook.MFAVerificationAttempt.Enabled {
1✔
512
                        env = append(
×
513
                                env,
×
514
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
515
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.MFAVerificationAttempt.URI,
×
516
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets,
×
517
                        )
×
518
                }
×
519

520
                if utils.Config.Auth.Hook.PasswordVerificationAttempt.Enabled {
1✔
521
                        env = append(
×
522
                                env,
×
523
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
524
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.PasswordVerificationAttempt.URI,
×
525
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets,
×
526
                        )
×
527
                }
×
528

529
                if utils.Config.Auth.Hook.CustomAccessToken.Enabled {
1✔
530
                        env = append(
×
531
                                env,
×
532
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
533
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+utils.Config.Auth.Hook.CustomAccessToken.URI,
×
534
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+utils.Config.Auth.Hook.CustomAccessToken.Secrets,
×
535
                        )
×
536
                }
×
537

538
                if utils.Config.Auth.Hook.SendSMS.Enabled {
1✔
539
                        env = append(
×
540
                                env,
×
541
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
542
                                "GOTRUE_HOOK_SEND_SMS_URI="+utils.Config.Auth.Hook.SendSMS.URI,
×
543
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+utils.Config.Auth.Hook.SendSMS.Secrets,
×
544
                        )
×
545
                }
×
546

547
                if utils.Config.Auth.Hook.SendEmail.Enabled {
1✔
548
                        env = append(
×
549
                                env,
×
550
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
551
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+utils.Config.Auth.Hook.SendEmail.URI,
×
552
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets,
×
553
                        )
×
554
                }
×
555

556
                for name, config := range utils.Config.Auth.External {
19✔
557
                        env = append(
18✔
558
                                env,
18✔
559
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
18✔
560
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
18✔
561
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret),
18✔
562
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
18✔
563
                        )
18✔
564

18✔
565
                        if config.RedirectUri != "" {
18✔
566
                                env = append(env,
×
567
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), config.RedirectUri),
×
568
                                )
×
569
                        } else {
18✔
570
                                env = append(env,
18✔
571
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=http://%s:%d/auth/v1/callback", strings.ToUpper(name), utils.Config.Hostname, utils.Config.Api.Port),
18✔
572
                                )
18✔
573
                        }
18✔
574

575
                        if config.Url != "" {
18✔
576
                                env = append(env,
×
577
                                        fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url),
×
578
                                )
×
579
                        }
×
580
                }
581

582
                if _, err := utils.DockerStart(
1✔
583
                        ctx,
1✔
584
                        container.Config{
1✔
585
                                Image:        utils.Config.Auth.Image,
1✔
586
                                Env:          env,
1✔
587
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
588
                                Healthcheck: &container.HealthConfig{
1✔
589
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
590
                                                "http://127.0.0.1:9999/health",
1✔
591
                                        },
1✔
592
                                        Interval: 10 * time.Second,
1✔
593
                                        Timeout:  2 * time.Second,
1✔
594
                                        Retries:  3,
1✔
595
                                },
1✔
596
                        },
1✔
597
                        start.WithSyslogConfig(container.HostConfig{
1✔
598
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
599
                        }),
1✔
600
                        network.NetworkingConfig{
1✔
601
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
602
                                        utils.NetId: {
1✔
603
                                                Aliases: utils.GotrueAliases,
1✔
604
                                        },
1✔
605
                                },
1✔
606
                        },
1✔
607
                        utils.GotrueId,
1✔
608
                ); err != nil {
1✔
609
                        return err
×
610
                }
×
611
                started = append(started, utils.GotrueId)
1✔
612
        }
613

614
        // Start Inbucket.
615
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.InbucketImage, excluded) {
3✔
616
                inbucketPortBindings := nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
617
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
618
                        inbucketPortBindings["2500/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
619
                }
×
620
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
621
                        inbucketPortBindings["1100/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
622
                }
×
623
                if _, err := utils.DockerStart(
1✔
624
                        ctx,
1✔
625
                        container.Config{
1✔
626
                                Image: utils.InbucketImage,
1✔
627
                        },
1✔
628
                        container.HostConfig{
1✔
629
                                Binds: []string{
1✔
630
                                        // Override default mount points to avoid creating multiple anonymous volumes
1✔
631
                                        // Ref: https://github.com/inbucket/inbucket/blob/v3.0.4/Dockerfile#L52
1✔
632
                                        utils.InbucketId + ":/config",
1✔
633
                                        utils.InbucketId + ":/storage",
1✔
634
                                },
1✔
635
                                PortBindings:  inbucketPortBindings,
1✔
636
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
637
                        },
1✔
638
                        network.NetworkingConfig{
1✔
639
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
640
                                        utils.NetId: {
1✔
641
                                                Aliases: utils.InbucketAliases,
1✔
642
                                        },
1✔
643
                                },
1✔
644
                        },
1✔
645
                        utils.InbucketId,
1✔
646
                ); err != nil {
1✔
647
                        return err
×
648
                }
×
649
                started = append(started, utils.InbucketId)
1✔
650
        }
651

652
        // Start Realtime.
653
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.Config.Realtime.Image, excluded) {
3✔
654
                if _, err := utils.DockerStart(
1✔
655
                        ctx,
1✔
656
                        container.Config{
1✔
657
                                Image: utils.Config.Realtime.Image,
1✔
658
                                Env: []string{
1✔
659
                                        "PORT=4000",
1✔
660
                                        "DB_HOST=" + dbConfig.Host,
1✔
661
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
662
                                        "DB_USER=supabase_admin",
1✔
663
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
664
                                        "DB_NAME=" + dbConfig.Database,
1✔
665
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
666
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
667
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
668
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
669
                                        "FLY_APP_NAME=realtime",
1✔
670
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
671
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
672
                                        "ENABLE_TAILSCALE=false",
1✔
673
                                        "DNS_NODES=''",
1✔
674
                                        "RLIMIT_NOFILE=",
1✔
675
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
676
                                },
1✔
677
                                // TODO: remove this after deprecating PG14
1✔
678
                                Cmd: []string{
1✔
679
                                        "/bin/sh", "-c",
1✔
680
                                        "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server",
1✔
681
                                },
1✔
682
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
683
                                Healthcheck: &container.HealthConfig{
1✔
684
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
685
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
686
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
687
                                                "http://127.0.0.1:4000/api/ping",
1✔
688
                                        },
1✔
689
                                        Interval: 10 * time.Second,
1✔
690
                                        Timeout:  2 * time.Second,
1✔
691
                                        Retries:  3,
1✔
692
                                },
1✔
693
                        },
1✔
694
                        start.WithSyslogConfig(container.HostConfig{
1✔
695
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
696
                        }),
1✔
697
                        network.NetworkingConfig{
1✔
698
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
699
                                        utils.NetId: {
1✔
700
                                                Aliases: utils.RealtimeAliases,
1✔
701
                                        },
1✔
702
                                },
1✔
703
                        },
1✔
704
                        utils.RealtimeId,
1✔
705
                ); err != nil {
1✔
706
                        return err
×
707
                }
×
708
                started = append(started, utils.RealtimeId)
1✔
709
        }
710

711
        // Start PostgREST.
712
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
713
                if _, err := utils.DockerStart(
1✔
714
                        ctx,
1✔
715
                        container.Config{
1✔
716
                                Image: utils.Config.Api.Image,
1✔
717
                                Env: []string{
1✔
718
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
719
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
720
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
721
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
722
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
723
                                        "PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
724
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
725
                                },
1✔
726
                                // PostgREST does not expose a shell for health check
1✔
727
                        },
1✔
728
                        start.WithSyslogConfig(container.HostConfig{
1✔
729
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
730
                        }),
1✔
731
                        network.NetworkingConfig{
1✔
732
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
733
                                        utils.NetId: {
1✔
734
                                                Aliases: utils.RestAliases,
1✔
735
                                        },
1✔
736
                                },
1✔
737
                        },
1✔
738
                        utils.RestId,
1✔
739
                ); err != nil {
1✔
740
                        return err
×
741
                }
×
742
                started = append(started, utils.RestId)
1✔
743
        }
744

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

802
        // Start Storage ImgProxy.
803
        if utils.Config.Storage.Enabled && utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.ImageProxyImage, excluded) {
3✔
804
                if _, err := utils.DockerStart(
1✔
805
                        ctx,
1✔
806
                        container.Config{
1✔
807
                                Image: utils.ImageProxyImage,
1✔
808
                                Env: []string{
1✔
809
                                        "IMGPROXY_BIND=:5001",
1✔
810
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
1✔
811
                                        "IMGPROXY_USE_ETAG=/",
1✔
812
                                },
1✔
813
                                Healthcheck: &container.HealthConfig{
1✔
814
                                        Test:     []string{"CMD", "imgproxy", "health"},
1✔
815
                                        Interval: 10 * time.Second,
1✔
816
                                        Timeout:  2 * time.Second,
1✔
817
                                        Retries:  3,
1✔
818
                                },
1✔
819
                        },
1✔
820
                        container.HostConfig{
1✔
821
                                VolumesFrom:   []string{utils.StorageId},
1✔
822
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
823
                        },
1✔
824
                        network.NetworkingConfig{
1✔
825
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
826
                                        utils.NetId: {
1✔
827
                                                Aliases: utils.ImgProxyAliases,
1✔
828
                                        },
1✔
829
                                },
1✔
830
                        },
1✔
831
                        utils.ImgProxyId,
1✔
832
                ); err != nil {
1✔
833
                        return err
×
834
                }
×
835
                started = append(started, utils.ImgProxyId)
1✔
836
        }
837

838
        // Start all functions.
839
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.EdgeRuntimeImage, excluded) {
3✔
840
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
841
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, w, fsys); err != nil {
1✔
842
                        return err
×
843
                }
×
844
                started = append(started, utils.EdgeRuntimeId)
1✔
845
        }
846

847
        // Start pg-meta.
848
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
849
                if _, err := utils.DockerStart(
1✔
850
                        ctx,
1✔
851
                        container.Config{
1✔
852
                                Image: utils.Config.Studio.PgmetaImage,
1✔
853
                                Env: []string{
1✔
854
                                        "PG_META_PORT=8080",
1✔
855
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
856
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
857
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
858
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
859
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
860
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
861
                                },
1✔
862
                                Healthcheck: &container.HealthConfig{
1✔
863
                                        Test:     []string{"CMD", "node", `--eval='fetch("http://127.0.0.1:8080/health").then((r) => {if (r.status !== 200) throw new Error(r.status)})'`},
1✔
864
                                        Interval: 10 * time.Second,
1✔
865
                                        Timeout:  2 * time.Second,
1✔
866
                                        Retries:  3,
1✔
867
                                },
1✔
868
                        },
1✔
869
                        container.HostConfig{
1✔
870
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
871
                        },
1✔
872
                        network.NetworkingConfig{
1✔
873
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
874
                                        utils.NetId: {
1✔
875
                                                Aliases: utils.PgmetaAliases,
1✔
876
                                        },
1✔
877
                                },
1✔
878
                        },
1✔
879
                        utils.PgmetaId,
1✔
880
                ); err != nil {
1✔
881
                        return err
×
882
                }
×
883
                started = append(started, utils.PgmetaId)
1✔
884
        }
885

886
        // Start Studio.
887
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.Image, excluded) {
3✔
888
                if _, err := utils.DockerStart(
1✔
889
                        ctx,
1✔
890
                        container.Config{
1✔
891
                                Image: utils.Config.Studio.Image,
1✔
892
                                Env: []string{
1✔
893
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
894
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
895
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
896
                                        fmt.Sprintf("SUPABASE_PUBLIC_URL=%s:%v/", utils.Config.Studio.ApiUrl, utils.Config.Api.Port),
1✔
897
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey,
1✔
898
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
1✔
899
                                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
1✔
900
                                        "OPENAI_KEY=" + utils.Config.Studio.OpenaiApiKey,
1✔
901
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
902
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
903
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
904
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
905
                                        "HOSTNAME=0.0.0.0",
1✔
906
                                },
1✔
907
                                Healthcheck: &container.HealthConfig{
1✔
908
                                        Test:     []string{"CMD", "node", `--eval='fetch("http://127.0.0.1:3000/api/profile", (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})'`},
1✔
909
                                        Interval: 10 * time.Second,
1✔
910
                                        Timeout:  2 * time.Second,
1✔
911
                                        Retries:  3,
1✔
912
                                },
1✔
913
                        },
1✔
914
                        container.HostConfig{
1✔
915
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
916
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
917
                        },
1✔
918
                        network.NetworkingConfig{
1✔
919
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
920
                                        utils.NetId: {
1✔
921
                                                Aliases: utils.StudioAliases,
1✔
922
                                        },
1✔
923
                                },
1✔
924
                        },
1✔
925
                        utils.StudioId,
1✔
926
                ); err != nil {
1✔
927
                        return err
×
928
                }
×
929
                started = append(started, utils.StudioId)
1✔
930
        }
931

932
        // Start pooler.
933
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
934
                portSession := uint16(5432)
×
935
                portTransaction := uint16(6543)
×
936
                dockerPort := portTransaction
×
937
                if utils.Config.Db.Pooler.PoolMode == utils.SessionMode {
×
938
                        dockerPort = portSession
×
939
                }
×
940
                // Create pooler tenant
941
                var poolerTenantBuf bytes.Buffer
×
942
                if err := poolerTenantTemplate.Execute(&poolerTenantBuf, poolerTenant{
×
943
                        DbHost:            dbConfig.Host,
×
944
                        DbPort:            dbConfig.Port,
×
945
                        DbDatabase:        dbConfig.Database,
×
946
                        DbPassword:        dbConfig.Password,
×
947
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
948
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
949
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
950
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
951
                }); err != nil {
×
952
                        return errors.Errorf("failed to exec template: %w", err)
×
953
                }
×
954
                if _, err := utils.DockerStart(
×
955
                        ctx,
×
956
                        container.Config{
×
NEW
957
                                Image: utils.Config.Db.Pooler.Image,
×
958
                                Env: []string{
×
959
                                        "PORT=4000",
×
960
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
961
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
962
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
×
963
                                        "CLUSTER_POSTGRES=true",
×
964
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
965
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
966
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
×
967
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
×
968
                                        "REGION=local",
×
969
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
×
970
                                },
×
971
                                Cmd: []string{
×
972
                                        "/bin/sh", "-c",
×
973
                                        fmt.Sprintf("/app/bin/migrate && /app/bin/supavisor eval '%s' && /app/bin/server", poolerTenantBuf.String()),
×
974
                                },
×
975
                                ExposedPorts: nat.PortSet{
×
976
                                        "4000/tcp": {},
×
977
                                        nat.Port(fmt.Sprintf("%d/tcp", portSession)):     {},
×
978
                                        nat.Port(fmt.Sprintf("%d/tcp", portTransaction)): {},
×
979
                                },
×
980
                                Healthcheck: &container.HealthConfig{
×
981
                                        Test:     []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/api/health"},
×
982
                                        Interval: 10 * time.Second,
×
983
                                        Timeout:  2 * time.Second,
×
984
                                        Retries:  3,
×
985
                                },
×
986
                        },
×
987
                        container.HostConfig{
×
988
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
×
989
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)},
×
990
                                }},
×
991
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
992
                        },
×
993
                        network.NetworkingConfig{
×
994
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
995
                                        utils.NetId: {
×
996
                                                Aliases: utils.PoolerAliases,
×
997
                                        },
×
998
                                },
×
999
                        },
×
1000
                        utils.PoolerId,
×
1001
                ); err != nil {
×
1002
                        return err
×
1003
                }
×
1004
                started = append(started, utils.PoolerId)
×
1005
        }
1006

1007
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
1008
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1009
}
1010

1011
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
20✔
1012
        short := utils.ShortContainerImageName(imageName)
20✔
1013
        val, ok := excluded[short]
20✔
1014
        return ok && val
20✔
1015
}
20✔
1016

1017
func ExcludableContainers() []string {
2✔
1018
        names := []string{}
2✔
1019
        for _, image := range utils.ServiceImages {
28✔
1020
                names = append(names, utils.ShortContainerImageName(image))
26✔
1021
        }
26✔
1022
        return names
2✔
1023
}
1024

1025
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1026
        numOfKeyPairs := len(input)
5✔
1027
        i := 0
5✔
1028
        for k, v := range input {
15✔
1029
                output.WriteString(k)
10✔
1030
                output.WriteString(":")
10✔
1031
                output.WriteString(v)
10✔
1032
                i++
10✔
1033
                if i < numOfKeyPairs {
16✔
1034
                        output.WriteString(",")
6✔
1035
                }
6✔
1036
        }
1037
}
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

© 2026 Coveralls, Inc