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

supabase / cli / 15239106693

25 May 2025 02:59PM UTC coverage: 60.201% (-0.02%) from 60.221%
15239106693

Pull #3609

github

web-flow
Merge 21c588ad3 into 09d487004
Pull Request #3609: Update mailpit label for supabase status in terminal and other reference to inbucket

22 of 26 new or added lines in 5 files covered. (84.62%)

9 existing lines in 4 files now uncovered.

9026 of 14993 relevant lines covered (60.2%)

509.14 hits per line

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

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

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

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

36
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
3✔
37
        // Sanity checks.
3✔
38
        {
6✔
39
                if err := flags.LoadConfig(fsys); err != nil {
4✔
40
                        return err
1✔
41
                }
1✔
42
                if err := utils.AssertSupabaseDbIsRunning(); err == nil {
3✔
43
                        fmt.Fprintln(os.Stderr, utils.Aqua("supabase start")+" is already running.")
1✔
44
                        names := status.CustomName{}
1✔
45
                        return status.Run(ctx, names, utils.OutputPretty, fsys)
1✔
46
                } else if !errors.Is(err, utils.ErrNotRunning) {
3✔
47
                        return err
1✔
48
                }
1✔
49
                if err := flags.LoadProjectRef(fsys); err == nil {
×
50
                        _ = services.CheckVersions(ctx, fsys)
×
51
                }
×
52
        }
53

54
        if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error {
×
55
                dbConfig := pgconn.Config{
×
56
                        Host:     utils.DbId,
×
57
                        Port:     5432,
×
58
                        User:     "postgres",
×
59
                        Password: utils.Config.Db.Password,
×
60
                        Database: "postgres",
×
61
                }
×
62
                return run(p, ctx, fsys, excludedContainers, dbConfig)
×
63
        }); err != nil {
×
64
                if ignoreHealthCheck && start.IsUnhealthyError(err) {
×
65
                        fmt.Fprintln(os.Stderr, err)
×
66
                } else {
×
67
                        if err := utils.DockerRemoveAll(context.Background(), os.Stderr, utils.Config.ProjectId); err != nil {
×
68
                                fmt.Fprintln(os.Stderr, err)
×
69
                        }
×
70
                        return err
×
71
                }
72
        }
73

74
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
75
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
76
        return nil
×
77
}
78

79
type kongConfig struct {
80
        GotrueId      string
81
        RestId        string
82
        RealtimeId    string
83
        StorageId     string
84
        PgmetaId      string
85
        EdgeRuntimeId string
86
        LogflareId    string
87
        PoolerId      string
88
        ApiHost       string
89
        ApiPort       uint16
90
}
91

92
var (
93
        //go:embed templates/kong.yml
94
        kongConfigEmbed    string
95
        kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
96

97
        //go:embed templates/custom_nginx.template
98
        nginxConfigEmbed string
99
        // Hardcoded configs which match nginxConfigEmbed
100
        nginxEmailTemplateDir   = "/home/kong/templates/email"
101
        nginxTemplateServerPort = 8088
102
)
103

104
type vectorConfig struct {
105
        ApiKey        string
106
        VectorId      string
107
        LogflareId    string
108
        KongId        string
109
        GotrueId      string
110
        RestId        string
111
        RealtimeId    string
112
        StorageId     string
113
        EdgeRuntimeId string
114
        DbId          string
115
}
116

117
var (
118
        //go:embed templates/vector.yaml
119
        vectorConfigEmbed    string
120
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
121
)
122

123
type poolerTenant struct {
124
        DbHost            string
125
        DbPort            uint16
126
        DbDatabase        string
127
        DbPassword        string
128
        ExternalId        string
129
        ModeType          config.PoolMode
130
        DefaultMaxClients uint
131
        DefaultPoolSize   uint
132
}
133

134
var (
135
        //go:embed templates/pooler.exs
136
        poolerTenantEmbed    string
137
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
138
)
139

140
var serviceTimeout = 30 * time.Second
141

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

148
        jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
2✔
149
        if err != nil {
2✔
150
                return err
×
151
        }
×
152

153
        // Start Postgres.
154
        w := utils.StatusWriter{Program: p}
2✔
155
        if dbConfig.Host == utils.DbId {
4✔
156
                if err := start.StartDatabase(ctx, "", fsys, w, options...); err != nil {
2✔
157
                        return err
×
158
                }
×
159
        }
160

161
        var started []string
2✔
162
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
163
        var isImgProxyEnabled = utils.Config.Storage.ImageTransformation != nil &&
2✔
164
                utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
2✔
165
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
166

2✔
167
        // Start Logflare
2✔
168
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.Image, excluded) {
3✔
169
                env := []string{
1✔
170
                        "DB_DATABASE=_supabase",
1✔
171
                        "DB_HOSTNAME=" + dbConfig.Host,
1✔
172
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
173
                        "DB_SCHEMA=_analytics",
1✔
174
                        "DB_USERNAME=supabase_admin",
1✔
175
                        "DB_PASSWORD=" + dbConfig.Password,
1✔
176
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
1✔
177
                        "LOGFLARE_SINGLE_TENANT=true",
1✔
178
                        "LOGFLARE_SUPABASE_MODE=true",
1✔
179
                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
1✔
180
                        "LOGFLARE_LOG_LEVEL=warn",
1✔
181
                        "LOGFLARE_NODE_HOST=127.0.0.1",
1✔
182
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
1✔
183
                        "RELEASE_COOKIE=cookie",
1✔
184
                }
1✔
185
                bind := []string{}
1✔
186

1✔
187
                switch utils.Config.Analytics.Backend {
1✔
188
                case config.LogflareBigQuery:
×
189
                        workdir, err := os.Getwd()
×
190
                        if err != nil {
×
191
                                return errors.Errorf("failed to get working directory: %w", err)
×
192
                        }
×
193
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
194
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
195
                        // This is hardcoded in studio frontend
×
196
                        env = append(env,
×
197
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
198
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
199
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
200
                        )
×
201
                case config.LogflarePostgres:
1✔
202
                        env = append(env,
1✔
203
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
1✔
204
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
1✔
205
                        )
1✔
206
                }
207

208
                if _, err := utils.DockerStart(
1✔
209
                        ctx,
1✔
210
                        container.Config{
1✔
211
                                Hostname: "127.0.0.1",
1✔
212
                                Image:    utils.Config.Analytics.Image,
1✔
213
                                Env:      env,
1✔
214
                                // Original entrypoint conflicts with healthcheck due to 15 seconds sleep:
1✔
215
                                // https://github.com/Logflare/logflare/blob/staging/run.sh#L35
1✔
216
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > run.sh && sh run.sh
1✔
217
./logflare eval Logflare.Release.migrate
1✔
218
./logflare start --sname logflare
1✔
219
EOF
1✔
220
`},
1✔
221
                                Healthcheck: &container.HealthConfig{
1✔
222
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
223
                                                "http://127.0.0.1:4000/health",
1✔
224
                                        },
1✔
225
                                        Interval:    10 * time.Second,
1✔
226
                                        Timeout:     2 * time.Second,
1✔
227
                                        Retries:     3,
1✔
228
                                        StartPeriod: 10 * time.Second,
1✔
229
                                },
1✔
230
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
231
                        },
1✔
232
                        container.HostConfig{
1✔
233
                                Binds:         bind,
1✔
234
                                PortBindings:  nat.PortMap{"4000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.Port), 10)}}},
1✔
235
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
236
                        },
1✔
237
                        network.NetworkingConfig{
1✔
238
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
239
                                        utils.NetId: {
1✔
240
                                                Aliases: utils.LogflareAliases,
1✔
241
                                        },
1✔
242
                                },
1✔
243
                        },
1✔
244
                        utils.LogflareId,
1✔
245
                ); err != nil {
1✔
246
                        return err
×
247
                }
×
248
                started = append(started, utils.LogflareId)
1✔
249
        }
250

251
        // Start vector
252
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.VectorImage, excluded) {
3✔
253
                var vectorConfigBuf bytes.Buffer
1✔
254
                if err := vectorConfigTemplate.Option("missingkey=error").Execute(&vectorConfigBuf, vectorConfig{
1✔
255
                        ApiKey:        utils.Config.Analytics.ApiKey,
1✔
256
                        VectorId:      utils.VectorId,
1✔
257
                        LogflareId:    utils.LogflareId,
1✔
258
                        KongId:        utils.KongId,
1✔
259
                        GotrueId:      utils.GotrueId,
1✔
260
                        RestId:        utils.RestId,
1✔
261
                        RealtimeId:    utils.RealtimeId,
1✔
262
                        StorageId:     utils.StorageId,
1✔
263
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
264
                        DbId:          utils.DbId,
1✔
265
                }); err != nil {
1✔
266
                        return errors.Errorf("failed to exec template: %w", err)
×
267
                }
×
268
                var binds, env, securityOpts []string
1✔
269
                // Special case for GitLab pipeline
1✔
270
                parsed, err := client.ParseHostURL(utils.Docker.DaemonHost())
1✔
271
                if err != nil {
1✔
272
                        return errors.Errorf("failed to parse docker host: %w", err)
×
273
                }
×
274
                // Ref: https://vector.dev/docs/reference/configuration/sources/docker_logs/#docker_host
275
                dindHost := &url.URL{Scheme: "http", Host: net.JoinHostPort(utils.DinDHost, "2375")}
1✔
276
                switch parsed.Scheme {
1✔
277
                case "tcp":
×
278
                        if _, port, err := net.SplitHostPort(parsed.Host); err == nil {
×
279
                                dindHost.Host = net.JoinHostPort(utils.DinDHost, port)
×
280
                        }
×
281
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
282
                case "npipe":
×
283
                        const dockerDaemonNeededErr = "Analytics on Windows requires Docker daemon exposed on tcp://localhost:2375.\nSee https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=windows#running-supabase-locally for more details."
×
284
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), dockerDaemonNeededErr)
×
285
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
286
                case "unix":
×
287
                        if dindHost, err = client.ParseHostURL(client.DefaultDockerHost); err != nil {
×
288
                                return errors.Errorf("failed to parse default host: %w", err)
×
289
                        } else if strings.HasSuffix(parsed.Host, "/.docker/run/docker.sock") {
×
290
                                fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires mounting default docker socket:", dindHost.Host)
×
291
                                binds = append(binds, fmt.Sprintf("%[1]s:%[1]s:ro", dindHost.Host))
×
292
                        } else {
×
293
                                // Podman and OrbStack can mount root-less socket without issue
×
294
                                binds = append(binds, fmt.Sprintf("%s:%s:ro", parsed.Host, dindHost.Host))
×
295
                                securityOpts = append(securityOpts, "label:disable")
×
296
                        }
×
297
                }
298
                if _, err := utils.DockerStart(
1✔
299
                        ctx,
1✔
300
                        container.Config{
1✔
301
                                Image: utils.Config.Analytics.VectorImage,
1✔
302
                                Env:   env,
1✔
303
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/vector/vector.yaml && vector --config /etc/vector/vector.yaml
1✔
304
` + vectorConfigBuf.String() + `
1✔
305
EOF
1✔
306
`},
1✔
307
                                Healthcheck: &container.HealthConfig{
1✔
308
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
309
                                                "http://127.0.0.1:9001/health",
1✔
310
                                        },
1✔
311
                                        Interval: 10 * time.Second,
1✔
312
                                        Timeout:  2 * time.Second,
1✔
313
                                        Retries:  3,
1✔
314
                                },
1✔
315
                        },
1✔
316
                        container.HostConfig{
1✔
317
                                Binds:         binds,
1✔
318
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
319
                                SecurityOpt:   securityOpts,
1✔
320
                        },
1✔
321
                        network.NetworkingConfig{
1✔
322
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
323
                                        utils.NetId: {
1✔
324
                                                Aliases: utils.VectorAliases,
1✔
325
                                        },
1✔
326
                                },
1✔
327
                        },
1✔
328
                        utils.VectorId,
1✔
329
                ); err != nil {
1✔
330
                        return err
×
331
                }
×
332
                if parsed.Scheme != "npipe" {
2✔
333
                        started = append(started, utils.VectorId)
1✔
334
                }
1✔
335
        }
336

337
        // Start Kong.
338
        if !isContainerExcluded(utils.Config.Api.KongImage, excluded) {
3✔
339
                var kongConfigBuf bytes.Buffer
1✔
340
                if err := kongConfigTemplate.Option("missingkey=error").Execute(&kongConfigBuf, kongConfig{
1✔
341
                        GotrueId:      utils.GotrueId,
1✔
342
                        RestId:        utils.RestId,
1✔
343
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
344
                        StorageId:     utils.StorageId,
1✔
345
                        PgmetaId:      utils.PgmetaId,
1✔
346
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
347
                        LogflareId:    utils.LogflareId,
1✔
348
                        PoolerId:      utils.PoolerId,
1✔
349
                        ApiHost:       utils.Config.Hostname,
1✔
350
                        ApiPort:       utils.Config.Api.Port,
1✔
351
                }); err != nil {
1✔
352
                        return errors.Errorf("failed to exec template: %w", err)
×
353
                }
×
354

355
                binds := []string{}
1✔
356
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
357
                        if len(tmpl.ContentPath) == 0 {
×
358
                                continue
×
359
                        }
360
                        hostPath := tmpl.ContentPath
×
361
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
362
                                var err error
×
363
                                hostPath, err = filepath.Abs(hostPath)
×
364
                                if err != nil {
×
365
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
366
                                }
×
367
                        }
368
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
369
                        binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath))
×
370
                }
371

372
                dockerPort := uint16(8000)
1✔
373
                if utils.Config.Api.Tls.Enabled {
1✔
374
                        dockerPort = 8443
×
375
                }
×
376
                if _, err := utils.DockerStart(
1✔
377
                        ctx,
1✔
378
                        container.Config{
1✔
379
                                Image: utils.Config.Api.KongImage,
1✔
380
                                Env: []string{
1✔
381
                                        "KONG_DATABASE=off",
1✔
382
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
383
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
384
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
385
                                        fmt.Sprintf("KONG_PORT_MAPS=%d:8000", utils.Config.Api.Port),
1✔
386
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
387
                                        // sizeable response headers azure can generate
1✔
388
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
389
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
390
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
391
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
392
                                        // Use modern TLS certificate
1✔
393
                                        "KONG_SSL_CERT=/home/kong/localhost.crt",
1✔
394
                                        "KONG_SSL_CERT_KEY=/home/kong/localhost.key",
1✔
395
                                },
1✔
396
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && \
1✔
397
cat <<'EOF' > /home/kong/custom_nginx.template && \
1✔
398
cat <<'EOF' > /home/kong/localhost.crt && \
1✔
399
cat <<'EOF' > /home/kong/localhost.key && \
1✔
400
./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template
1✔
401
` + kongConfigBuf.String() + `
1✔
402
EOF
1✔
403
` + nginxConfigEmbed + `
1✔
404
EOF
1✔
405
` + status.KongCert + `
1✔
406
EOF
1✔
407
` + status.KongKey + `
1✔
408
EOF
1✔
409
`},
1✔
410
                                ExposedPorts: nat.PortSet{
1✔
411
                                        "8000/tcp": {},
1✔
412
                                        "8443/tcp": {},
1✔
413
                                        nat.Port(fmt.Sprintf("%d/tcp", nginxTemplateServerPort)): {},
1✔
414
                                },
1✔
415
                        },
1✔
416
                        container.HostConfig{
1✔
417
                                Binds: binds,
1✔
418
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
1✔
419
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)},
1✔
420
                                }},
1✔
421
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
422
                        },
1✔
423
                        network.NetworkingConfig{
1✔
424
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
425
                                        utils.NetId: {
1✔
426
                                                Aliases: utils.KongAliases,
1✔
427
                                        },
1✔
428
                                },
1✔
429
                        },
1✔
430
                        utils.KongId,
1✔
431
                ); err != nil {
1✔
432
                        return err
×
433
                }
×
434
                started = append(started, utils.KongId)
1✔
435
        }
436

437
        // Start GoTrue.
438
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
439
                var testOTP bytes.Buffer
1✔
440
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
441
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
442
                }
×
443

444
                env := []string{
1✔
445
                        "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl,
1✔
446

1✔
447
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
448
                        "GOTRUE_API_PORT=9999",
1✔
449

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

1✔
453
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
454
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
455
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
456

1✔
457
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
458
                        "GOTRUE_JWT_AUD=authenticated",
1✔
459
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
460
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
461
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
462
                        "GOTRUE_JWT_ISSUER=" + utils.GetApiUrl("/auth/v1"),
1✔
463

1✔
464
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
465
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
466
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
467
                        fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength),
1✔
468
                        fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry),
1✔
469

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

1✔
472
                        fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency),
1✔
473

1✔
474
                        "GOTRUE_MAILER_URLPATHS_INVITE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
475
                        "GOTRUE_MAILER_URLPATHS_CONFIRMATION=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
476
                        "GOTRUE_MAILER_URLPATHS_RECOVERY=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
477
                        "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
478
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
479

1✔
480
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
481
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
482
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
483
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
484
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
485
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
486
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
487

1✔
488
                        fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength),
1✔
489
                        fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()),
1✔
490
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
491
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
492
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
493
                        fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange),
1✔
494
                        fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
1✔
495
                        fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
1✔
496
                        fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
1✔
497
                        fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
1✔
498
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled),
1✔
499
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled),
1✔
500
                        fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
1✔
501

1✔
502
                        // Add rate limit configurations
1✔
503
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers),
1✔
504
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh),
1✔
505
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps),
1✔
506
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications),
1✔
507
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent),
1✔
508
                }
1✔
509

1✔
510
                if utils.Config.Auth.Email.Smtp != nil && utils.Config.Auth.Email.Smtp.Enabled {
1✔
511
                        env = append(env,
×
512
                                fmt.Sprintf("GOTRUE_RATE_LIMIT_EMAIL_SENT=%v", utils.Config.Auth.RateLimit.EmailSent),
×
513
                                fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host),
×
514
                                fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port),
×
515
                                fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User),
×
516
                                fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass.Value),
×
517
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail),
×
518
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName),
×
519
                        )
×
520
                } else if utils.Config.Mailpit.Enabled {
2✔
521
                        env = append(env,
1✔
522
                                "GOTRUE_SMTP_HOST="+utils.MailpitId,
1✔
523
                                "GOTRUE_SMTP_PORT=1025",
1✔
524
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Mailpit.AdminEmail),
1✔
525
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Mailpit.SenderName),
1✔
526
                        )
1✔
527
                }
1✔
528

529
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
530
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
531
                }
×
532
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
533
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
534
                }
×
535

536
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
537
                        if len(tmpl.ContentPath) > 0 {
×
538
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
539
                                        strings.ToUpper(id),
×
540
                                        utils.KongId,
×
541
                                        nginxTemplateServerPort,
×
542
                                        id+filepath.Ext(tmpl.ContentPath),
×
543
                                ))
×
544
                        }
×
545
                        if tmpl.Subject != nil {
×
546
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
547
                                        strings.ToUpper(id),
×
548
                                        *tmpl.Subject,
×
549
                                ))
×
550
                        }
×
551
                }
552

553
                switch {
1✔
554
                case utils.Config.Auth.Sms.Twilio.Enabled:
×
555
                        env = append(
×
556
                                env,
×
557
                                "GOTRUE_SMS_PROVIDER=twilio",
×
558
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
559
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken.Value,
×
560
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
561
                        )
×
562
                case utils.Config.Auth.Sms.TwilioVerify.Enabled:
×
563
                        env = append(
×
564
                                env,
×
565
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
566
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
567
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken.Value,
×
568
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
569
                        )
×
570
                case utils.Config.Auth.Sms.Messagebird.Enabled:
×
571
                        env = append(
×
572
                                env,
×
573
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
574
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey.Value,
×
575
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
576
                        )
×
577
                case utils.Config.Auth.Sms.Textlocal.Enabled:
×
578
                        env = append(
×
579
                                env,
×
580
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
581
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey.Value,
×
582
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
583
                        )
×
584
                case utils.Config.Auth.Sms.Vonage.Enabled:
×
585
                        env = append(
×
586
                                env,
×
587
                                "GOTRUE_SMS_PROVIDER=vonage",
×
588
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
589
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret.Value,
×
590
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
591
                        )
×
592
                }
593

594
                if captcha := utils.Config.Auth.Captcha; captcha != nil {
1✔
595
                        env = append(
×
596
                                env,
×
597
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_ENABLED=%v", captcha.Enabled),
×
598
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_PROVIDER=%v", captcha.Provider),
×
599
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_SECRET=%v", captcha.Secret.Value),
×
600
                        )
×
601
                }
×
602

603
                if hook := utils.Config.Auth.Hook.MFAVerificationAttempt; hook != nil && hook.Enabled {
1✔
604
                        env = append(
×
605
                                env,
×
606
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
607
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
608
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
609
                        )
×
610
                }
×
611
                if hook := utils.Config.Auth.Hook.PasswordVerificationAttempt; hook != nil && hook.Enabled {
1✔
612
                        env = append(
×
613
                                env,
×
614
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
615
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
616
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
617
                        )
×
618
                }
×
619
                if hook := utils.Config.Auth.Hook.CustomAccessToken; hook != nil && hook.Enabled {
1✔
620
                        env = append(
×
621
                                env,
×
622
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
623
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+hook.URI,
×
624
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets.Value,
×
625
                        )
×
626
                }
×
627
                if hook := utils.Config.Auth.Hook.SendSMS; hook != nil && hook.Enabled {
1✔
628
                        env = append(
×
629
                                env,
×
630
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
631
                                "GOTRUE_HOOK_SEND_SMS_URI="+hook.URI,
×
632
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets.Value,
×
633
                        )
×
634
                }
×
635
                if hook := utils.Config.Auth.Hook.SendEmail; hook != nil && hook.Enabled {
1✔
636
                        env = append(
×
637
                                env,
×
638
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
639
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+hook.URI,
×
640
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets.Value,
×
641
                        )
×
642
                }
×
643

644
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
645
                        env = append(
×
646
                                env,
×
647
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
648
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
649
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
650
                        )
×
651
                }
×
652

653
                for name, config := range utils.Config.Auth.External {
2✔
654
                        env = append(
1✔
655
                                env,
1✔
656
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
1✔
657
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
1✔
658
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value),
1✔
659
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
1✔
660
                        )
1✔
661

1✔
662
                        redirectUri := config.RedirectUri
1✔
663
                        if redirectUri == "" {
2✔
664
                                redirectUri = utils.GetApiUrl("/auth/v1/callback")
1✔
665
                        }
1✔
666
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
1✔
667

1✔
668
                        if config.Url != "" {
1✔
669
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
670
                        }
×
671
                }
672

673
                if _, err := utils.DockerStart(
1✔
674
                        ctx,
1✔
675
                        container.Config{
1✔
676
                                Image:        utils.Config.Auth.Image,
1✔
677
                                Env:          env,
1✔
678
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
679
                                Healthcheck: &container.HealthConfig{
1✔
680
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
681
                                                "http://127.0.0.1:9999/health",
1✔
682
                                        },
1✔
683
                                        Interval: 10 * time.Second,
1✔
684
                                        Timeout:  2 * time.Second,
1✔
685
                                        Retries:  3,
1✔
686
                                },
1✔
687
                        },
1✔
688
                        container.HostConfig{
1✔
689
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
690
                        },
1✔
691
                        network.NetworkingConfig{
1✔
692
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
693
                                        utils.NetId: {
1✔
694
                                                Aliases: utils.GotrueAliases,
1✔
695
                                        },
1✔
696
                                },
1✔
697
                        },
1✔
698
                        utils.GotrueId,
1✔
699
                ); err != nil {
1✔
700
                        return err
×
701
                }
×
702
                started = append(started, utils.GotrueId)
1✔
703
        }
704

705
        // Start Mailpit
706
        if utils.Config.Mailpit.Enabled && !isContainerExcluded(utils.Config.Mailpit.Image, excluded) {
3✔
707
                mailpitPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Mailpit.Port), 10)}}}
1✔
708
                if utils.Config.Mailpit.SmtpPort != 0 {
1✔
NEW
709
                        mailpitPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Mailpit.SmtpPort), 10)}}
×
UNCOV
710
                }
×
711
                if utils.Config.Mailpit.Pop3Port != 0 {
1✔
NEW
712
                        mailpitPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Mailpit.Pop3Port), 10)}}
×
UNCOV
713
                }
×
714
                if _, err := utils.DockerStart(
1✔
715
                        ctx,
1✔
716
                        container.Config{
1✔
717
                                Image: utils.Config.Mailpit.Image,
1✔
718
                        },
1✔
719
                        container.HostConfig{
1✔
720
                                PortBindings:  mailpitPortBindings,
1✔
721
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
722
                        },
1✔
723
                        network.NetworkingConfig{
1✔
724
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
725
                                        utils.NetId: {
1✔
726
                                                Aliases: utils.MailpitAliases,
1✔
727
                                        },
1✔
728
                                },
1✔
729
                        },
1✔
730
                        utils.MailpitId,
1✔
731
                ); err != nil {
1✔
732
                        return err
×
733
                }
×
734
                started = append(started, utils.MailpitId)
1✔
735
        }
736

737
        // Start Realtime.
738
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.Config.Realtime.Image, excluded) {
3✔
739
                if _, err := utils.DockerStart(
1✔
740
                        ctx,
1✔
741
                        container.Config{
1✔
742
                                Image: utils.Config.Realtime.Image,
1✔
743
                                Env: []string{
1✔
744
                                        "PORT=4000",
1✔
745
                                        "DB_HOST=" + dbConfig.Host,
1✔
746
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
747
                                        "DB_USER=supabase_admin",
1✔
748
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
749
                                        "DB_NAME=" + dbConfig.Database,
1✔
750
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
751
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
752
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
753
                                        fmt.Sprintf("API_JWT_JWKS=%s", jwks),
1✔
754
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
755
                                        "APP_NAME=realtime",
1✔
756
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
757
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
758
                                        "DNS_NODES=''",
1✔
759
                                        "RLIMIT_NOFILE=",
1✔
760
                                        "SEED_SELF_HOST=true",
1✔
761
                                        "RUN_JANITOR=true",
1✔
762
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
763
                                },
1✔
764
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
765
                                Healthcheck: &container.HealthConfig{
1✔
766
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
767
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
768
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
769
                                                "http://127.0.0.1:4000/api/ping",
1✔
770
                                        },
1✔
771
                                        Interval: 10 * time.Second,
1✔
772
                                        Timeout:  2 * time.Second,
1✔
773
                                        Retries:  3,
1✔
774
                                },
1✔
775
                        },
1✔
776
                        container.HostConfig{
1✔
777
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
778
                        },
1✔
779
                        network.NetworkingConfig{
1✔
780
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
781
                                        utils.NetId: {
1✔
782
                                                Aliases: utils.RealtimeAliases,
1✔
783
                                        },
1✔
784
                                },
1✔
785
                        },
1✔
786
                        utils.RealtimeId,
1✔
787
                ); err != nil {
1✔
788
                        return err
×
789
                }
×
790
                started = append(started, utils.RealtimeId)
1✔
791
        }
792

793
        // Start PostgREST.
794
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
795
                if _, err := utils.DockerStart(
1✔
796
                        ctx,
1✔
797
                        container.Config{
1✔
798
                                Image: utils.Config.Api.Image,
1✔
799
                                Env: []string{
1✔
800
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
801
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
802
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
803
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
804
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
805
                                        fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
1✔
806
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
807
                                },
1✔
808
                                // PostgREST does not expose a shell for health check
1✔
809
                        },
1✔
810
                        container.HostConfig{
1✔
811
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
812
                        },
1✔
813
                        network.NetworkingConfig{
1✔
814
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
815
                                        utils.NetId: {
1✔
816
                                                Aliases: utils.RestAliases,
1✔
817
                                        },
1✔
818
                                },
1✔
819
                        },
1✔
820
                        utils.RestId,
1✔
821
                ); err != nil {
1✔
822
                        return err
×
823
                }
×
824
                started = append(started, utils.RestId)
1✔
825
        }
826

827
        // Start Storage.
828
        if isStorageEnabled {
3✔
829
                dockerStoragePath := "/mnt"
1✔
830
                if _, err := utils.DockerStart(
1✔
831
                        ctx,
1✔
832
                        container.Config{
1✔
833
                                Image: utils.Config.Storage.Image,
1✔
834
                                Env: []string{
1✔
835
                                        "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
1✔
836
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
837
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
838
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
839
                                        fmt.Sprintf("JWT_JWKS=%s", jwks),
1✔
840
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
841
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
842
                                        "STORAGE_BACKEND=file",
1✔
843
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
844
                                        "TENANT_ID=stub",
1✔
845
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
846
                                        "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
1✔
847
                                        "GLOBAL_S3_BUCKET=stub",
1✔
848
                                        fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
1✔
849
                                        fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
1✔
850
                                        "TUS_URL_PATH=/storage/v1/upload/resumable",
1✔
851
                                        "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
1✔
852
                                        "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
1✔
853
                                        "S3_PROTOCOL_PREFIX=/storage/v1",
1✔
854
                                        "UPLOAD_FILE_SIZE_LIMIT=52428800000",
1✔
855
                                        "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
1✔
856
                                },
1✔
857
                                Healthcheck: &container.HealthConfig{
1✔
858
                                        // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
859
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
860
                                                "http://127.0.0.1:5000/status",
1✔
861
                                        },
1✔
862
                                        Interval: 10 * time.Second,
1✔
863
                                        Timeout:  2 * time.Second,
1✔
864
                                        Retries:  3,
1✔
865
                                },
1✔
866
                        },
1✔
867
                        container.HostConfig{
1✔
868
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
869
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
870
                        },
1✔
871
                        network.NetworkingConfig{
1✔
872
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
873
                                        utils.NetId: {
1✔
874
                                                Aliases: utils.StorageAliases,
1✔
875
                                        },
1✔
876
                                },
1✔
877
                        },
1✔
878
                        utils.StorageId,
1✔
879
                ); err != nil {
1✔
880
                        return err
×
881
                }
×
882
                started = append(started, utils.StorageId)
1✔
883
        }
884

885
        // Start Storage ImgProxy.
886
        if isStorageEnabled && isImgProxyEnabled {
2✔
887
                if _, err := utils.DockerStart(
×
888
                        ctx,
×
889
                        container.Config{
×
890
                                Image: utils.Config.Storage.ImgProxyImage,
×
891
                                Env: []string{
×
892
                                        "IMGPROXY_BIND=:5001",
×
893
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
×
894
                                        "IMGPROXY_USE_ETAG=/",
×
895
                                        "IMGPROXY_MAX_SRC_RESOLUTION=50",
×
896
                                        "IMGPROXY_MAX_SRC_FILE_SIZE=25000000",
×
897
                                        "IMGPROXY_MAX_ANIMATION_FRAMES=60",
×
898
                                        "IMGPROXY_ENABLE_WEBP_DETECTION=true",
×
899
                                },
×
900
                                Healthcheck: &container.HealthConfig{
×
901
                                        Test:     []string{"CMD", "imgproxy", "health"},
×
902
                                        Interval: 10 * time.Second,
×
903
                                        Timeout:  2 * time.Second,
×
904
                                        Retries:  3,
×
905
                                },
×
906
                        },
×
907
                        container.HostConfig{
×
908
                                VolumesFrom:   []string{utils.StorageId},
×
909
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
910
                        },
×
911
                        network.NetworkingConfig{
×
912
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
913
                                        utils.NetId: {
×
914
                                                Aliases: utils.ImgProxyAliases,
×
915
                                        },
×
916
                                },
×
917
                        },
×
918
                        utils.ImgProxyId,
×
919
                ); err != nil {
×
920
                        return err
×
921
                }
×
922
                started = append(started, utils.ImgProxyId)
×
923
        }
924

925
        // Start all functions.
926
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.Config.EdgeRuntime.Image, excluded) {
3✔
927
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
928
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, fsys); err != nil {
1✔
929
                        return err
×
930
                }
×
931
                started = append(started, utils.EdgeRuntimeId)
1✔
932
        }
933

934
        // Start pg-meta.
935
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
936
                if _, err := utils.DockerStart(
1✔
937
                        ctx,
1✔
938
                        container.Config{
1✔
939
                                Image: utils.Config.Studio.PgmetaImage,
1✔
940
                                Env: []string{
1✔
941
                                        "PG_META_PORT=8080",
1✔
942
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
943
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
944
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
945
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
946
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
947
                                },
1✔
948
                                Healthcheck: &container.HealthConfig{
1✔
949
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:8080/health').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
950
                                        Interval: 10 * time.Second,
1✔
951
                                        Timeout:  2 * time.Second,
1✔
952
                                        Retries:  3,
1✔
953
                                },
1✔
954
                        },
1✔
955
                        container.HostConfig{
1✔
956
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
957
                        },
1✔
958
                        network.NetworkingConfig{
1✔
959
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
960
                                        utils.NetId: {
1✔
961
                                                Aliases: utils.PgmetaAliases,
1✔
962
                                        },
1✔
963
                                },
1✔
964
                        },
1✔
965
                        utils.PgmetaId,
1✔
966
                ); err != nil {
1✔
967
                        return err
×
968
                }
×
969
                started = append(started, utils.PgmetaId)
1✔
970
        }
971

972
        // Start Studio.
973
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.Image, excluded) {
3✔
974
                if _, err := utils.DockerStart(
1✔
975
                        ctx,
1✔
976
                        container.Config{
1✔
977
                                Image: utils.Config.Studio.Image,
1✔
978
                                Env: []string{
1✔
979
                                        "CURRENT_CLI_VERSION=" + utils.Version,
1✔
980
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
981
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
982
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
983
                                        "SUPABASE_PUBLIC_URL=" + utils.Config.Studio.ApiUrl,
1✔
984
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
985
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
986
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
987
                                        "LOGFLARE_API_KEY=" + utils.Config.Analytics.ApiKey,
1✔
988
                                        "OPENAI_API_KEY=" + utils.Config.Studio.OpenaiApiKey.Value,
1✔
989
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
990
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
991
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
992
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
993
                                        "HOSTNAME=0.0.0.0",
1✔
994
                                },
1✔
995
                                Healthcheck: &container.HealthConfig{
1✔
996
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:3000/api/platform/profile').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
997
                                        Interval: 10 * time.Second,
1✔
998
                                        Timeout:  2 * time.Second,
1✔
999
                                        Retries:  3,
1✔
1000
                                },
1✔
1001
                        },
1✔
1002
                        container.HostConfig{
1✔
1003
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
1004
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1005
                        },
1✔
1006
                        network.NetworkingConfig{
1✔
1007
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1008
                                        utils.NetId: {
1✔
1009
                                                Aliases: utils.StudioAliases,
1✔
1010
                                        },
1✔
1011
                                },
1✔
1012
                        },
1✔
1013
                        utils.StudioId,
1✔
1014
                ); err != nil {
1✔
1015
                        return err
×
1016
                }
×
1017
                started = append(started, utils.StudioId)
1✔
1018
        }
1019

1020
        // Start pooler.
1021
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
1022
                portSession := uint16(5432)
×
1023
                portTransaction := uint16(6543)
×
1024
                dockerPort := portTransaction
×
1025
                if utils.Config.Db.Pooler.PoolMode == config.SessionMode {
×
1026
                        dockerPort = portSession
×
1027
                }
×
1028
                // Create pooler tenant
1029
                var poolerTenantBuf bytes.Buffer
×
1030
                if err := poolerTenantTemplate.Option("missingkey=error").Execute(&poolerTenantBuf, poolerTenant{
×
1031
                        DbHost:            dbConfig.Host,
×
1032
                        DbPort:            dbConfig.Port,
×
1033
                        DbDatabase:        dbConfig.Database,
×
1034
                        DbPassword:        dbConfig.Password,
×
1035
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
1036
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
1037
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
1038
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
1039
                }); err != nil {
×
1040
                        return errors.Errorf("failed to exec template: %w", err)
×
1041
                }
×
1042
                if _, err := utils.DockerStart(
×
1043
                        ctx,
×
1044
                        container.Config{
×
1045
                                Image: utils.Config.Db.Pooler.Image,
×
1046
                                Env: []string{
×
1047
                                        "PORT=4000",
×
1048
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
1049
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
1050
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
×
1051
                                        "CLUSTER_POSTGRES=true",
×
1052
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
1053
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
1054
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1055
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1056
                                        "REGION=local",
×
1057
                                        "RUN_JANITOR=true",
×
1058
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
×
1059
                                },
×
1060
                                Cmd: []string{
×
1061
                                        "/bin/sh", "-c",
×
1062
                                        fmt.Sprintf("/app/bin/migrate && /app/bin/supavisor eval '%s' && /app/bin/server", poolerTenantBuf.String()),
×
1063
                                },
×
1064
                                ExposedPorts: nat.PortSet{
×
1065
                                        "4000/tcp": {},
×
1066
                                        nat.Port(fmt.Sprintf("%d/tcp", portSession)):     {},
×
1067
                                        nat.Port(fmt.Sprintf("%d/tcp", portTransaction)): {},
×
1068
                                },
×
1069
                                Healthcheck: &container.HealthConfig{
×
1070
                                        Test:     []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/api/health"},
×
1071
                                        Interval: 10 * time.Second,
×
1072
                                        Timeout:  2 * time.Second,
×
1073
                                        Retries:  3,
×
1074
                                },
×
1075
                        },
×
1076
                        container.HostConfig{
×
1077
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
×
1078
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)},
×
1079
                                }},
×
1080
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
1081
                        },
×
1082
                        network.NetworkingConfig{
×
1083
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
1084
                                        utils.NetId: {
×
1085
                                                Aliases: utils.PoolerAliases,
×
1086
                                        },
×
1087
                                },
×
1088
                        },
×
1089
                        utils.PoolerId,
×
1090
                ); err != nil {
×
1091
                        return err
×
1092
                }
×
1093
                started = append(started, utils.PoolerId)
×
1094
        }
1095

1096
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
1097
        if utils.NoBackupVolume && utils.SliceContains(started, utils.StorageId) {
3✔
1098
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1099
                        return err
×
1100
                }
×
1101
                // Disable prompts when seeding
1102
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
1103
                        return err
×
1104
                }
×
1105
        }
1106
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1107
}
1108

1109
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
22✔
1110
        short := utils.ShortContainerImageName(imageName)
22✔
1111
        val, ok := excluded[short]
22✔
1112
        return ok && val
22✔
1113
}
22✔
1114

1115
func ExcludableContainers() []string {
1✔
1116
        names := []string{}
1✔
1117
        for _, image := range config.Images.Services() {
14✔
1118
                names = append(names, utils.ShortContainerImageName(image))
13✔
1119
        }
13✔
1120
        return names
1✔
1121
}
1122

1123
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1124
        numOfKeyPairs := len(input)
5✔
1125
        i := 0
5✔
1126
        for k, v := range input {
15✔
1127
                output.WriteString(k)
10✔
1128
                output.WriteString(":")
10✔
1129
                output.WriteString(v)
10✔
1130
                i++
10✔
1131
                if i < numOfKeyPairs {
16✔
1132
                        output.WriteString(",")
6✔
1133
                }
6✔
1134
        }
1135
}
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