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

supabase / cli / 19627464325

24 Nov 2025 08:09AM UTC coverage: 55.06% (-0.03%) from 55.094%
19627464325

Pull #4489

github

web-flow
Merge bf21a8330 into 6d9e31644
Pull Request #4489: fix: enable es256 jwt signing algorithm support for gotrue

0 of 2 new or added lines in 1 file covered. (0.0%)

6 existing lines in 2 files now uncovered.

6540 of 11878 relevant lines covered (55.06%)

6.26 hits per line

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

65.39
/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
        "slices"
14
        "strconv"
15
        "strings"
16
        "text/template"
17
        "time"
18

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

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

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
        if err := run(ctx, fsys, excludedContainers, dbConfig); err != nil {
×
63
                if ignoreHealthCheck && start.IsUnhealthyError(err) {
×
64
                        fmt.Fprintln(os.Stderr, err)
×
65
                } else {
×
66
                        if err := utils.DockerRemoveAll(context.Background(), os.Stderr, utils.Config.ProjectId); err != nil {
×
67
                                fmt.Fprintln(os.Stderr, err)
×
68
                        }
×
69
                        return err
×
70
                }
71
        }
72

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

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

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

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

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

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

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

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

142
var serviceTimeout = 30 * time.Second
143

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

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

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

162
        var started []string
2✔
163
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
164
        var isImgProxyEnabled = utils.Config.Storage.ImageTransformation != nil &&
2✔
165
                utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
2✔
166
        fmt.Fprintln(os.Stderr, "Starting containers...")
2✔
167

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

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

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

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

338
        // Start Kong.
339
        if !isContainerExcluded(utils.Config.Api.KongImage, excluded) {
3✔
340
                var kongConfigBuf bytes.Buffer
1✔
341
                if err := kongConfigTemplate.Option("missingkey=error").Execute(&kongConfigBuf, kongConfig{
1✔
342
                        GotrueId:      utils.GotrueId,
1✔
343
                        RestId:        utils.RestId,
1✔
344
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
345
                        StorageId:     utils.StorageId,
1✔
346
                        StudioId:      utils.StudioId,
1✔
347
                        PgmetaId:      utils.PgmetaId,
1✔
348
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
349
                        LogflareId:    utils.LogflareId,
1✔
350
                        PoolerId:      utils.PoolerId,
1✔
351
                        ApiHost:       utils.Config.Hostname,
1✔
352
                        ApiPort:       utils.Config.Api.Port,
1✔
353
                        BearerToken: fmt.Sprintf(
1✔
354
                                // If Authorization header is set to a self-minted JWT, we want to pass it down.
1✔
355
                                // Legacy supabase-js may set Authorization header to Bearer <apikey>. We must remove it
1✔
356
                                // to avoid failing JWT validation.
1✔
357
                                // If Authorization header is missing, we want to match against apikey header to set the
1✔
358
                                // default JWT for downstream services.
1✔
359
                                // Finally, the apikey header may be set to a legacy JWT. In that case, we want to copy
1✔
360
                                // it to Authorization header for backwards compatibility.
1✔
361
                                `$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '%s' and 'Bearer %s') or (headers.apikey == '%s' and 'Bearer %s') or headers.apikey)`,
1✔
362
                                utils.Config.Auth.SecretKey.Value,
1✔
363
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
364
                                utils.Config.Auth.PublishableKey.Value,
1✔
365
                                utils.Config.Auth.AnonKey.Value,
1✔
366
                        ),
1✔
367
                        QueryToken: fmt.Sprintf(
1✔
368
                                `$((query_params.apikey == '%s' and '%s') or (query_params.apikey == '%s' and '%s') or query_params.apikey)`,
1✔
369
                                utils.Config.Auth.SecretKey.Value,
1✔
370
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
371
                                utils.Config.Auth.PublishableKey.Value,
1✔
372
                                utils.Config.Auth.AnonKey.Value,
1✔
373
                        ),
1✔
374
                }); err != nil {
1✔
375
                        return errors.Errorf("failed to exec template: %w", err)
×
376
                }
×
377

378
                binds := []string{}
1✔
379
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
380
                        if len(tmpl.ContentPath) == 0 {
×
381
                                continue
×
382
                        }
383
                        hostPath := tmpl.ContentPath
×
384
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
385
                                var err error
×
386
                                hostPath, err = filepath.Abs(hostPath)
×
387
                                if err != nil {
×
388
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
389
                                }
×
390
                        }
391
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
392
                        binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath))
×
393
                }
394

395
                dockerPort := uint16(8000)
1✔
396
                if utils.Config.Api.Tls.Enabled {
1✔
397
                        dockerPort = 8443
×
398
                }
×
399
                if _, err := utils.DockerStart(
1✔
400
                        ctx,
1✔
401
                        container.Config{
1✔
402
                                Image: utils.Config.Api.KongImage,
1✔
403
                                Env: []string{
1✔
404
                                        "KONG_DATABASE=off",
1✔
405
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
406
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
407
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
408
                                        fmt.Sprintf("KONG_PORT_MAPS=%d:8000", utils.Config.Api.Port),
1✔
409
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
410
                                        // sizeable response headers azure can generate
1✔
411
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
412
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
413
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
414
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
415
                                        // Use modern TLS certificate
1✔
416
                                        "KONG_SSL_CERT=/home/kong/localhost.crt",
1✔
417
                                        "KONG_SSL_CERT_KEY=/home/kong/localhost.key",
1✔
418
                                },
1✔
419
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && \
1✔
420
cat <<'EOF' > /home/kong/custom_nginx.template && \
1✔
421
cat <<'EOF' > /home/kong/localhost.crt && \
1✔
422
cat <<'EOF' > /home/kong/localhost.key && \
1✔
423
./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template
1✔
424
` + kongConfigBuf.String() + `
1✔
425
EOF
1✔
426
` + nginxConfigEmbed + `
1✔
427
EOF
1✔
428
` + string(utils.Config.Api.Tls.CertContent) + `
1✔
429
EOF
1✔
430
` + string(utils.Config.Api.Tls.KeyContent) + `
1✔
431
EOF
1✔
432
`},
1✔
433
                                ExposedPorts: nat.PortSet{
1✔
434
                                        "8000/tcp": {},
1✔
435
                                        "8443/tcp": {},
1✔
436
                                        nat.Port(fmt.Sprintf("%d/tcp", nginxTemplateServerPort)): {},
1✔
437
                                },
1✔
438
                        },
1✔
439
                        container.HostConfig{
1✔
440
                                Binds: binds,
1✔
441
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
1✔
442
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)},
1✔
443
                                }},
1✔
444
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
445
                        },
1✔
446
                        network.NetworkingConfig{
1✔
447
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
448
                                        utils.NetId: {
1✔
449
                                                Aliases: utils.KongAliases,
1✔
450
                                        },
1✔
451
                                },
1✔
452
                        },
1✔
453
                        utils.KongId,
1✔
454
                ); err != nil {
1✔
455
                        return err
×
456
                }
×
457
                started = append(started, utils.KongId)
1✔
458
        }
459

460
        // Start GoTrue.
461
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
462
                var testOTP bytes.Buffer
1✔
463
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
464
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
465
                }
×
466

467
                env := []string{
1✔
468
                        "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl,
1✔
469

1✔
470
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
471
                        "GOTRUE_API_PORT=9999",
1✔
472

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

1✔
476
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
477
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
478
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
479

1✔
480
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
481
                        "GOTRUE_JWT_AUD=authenticated",
1✔
482
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
483
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
484
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
485
                        "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer,
1✔
486

1✔
487
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
488
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
489
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
490
                        fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength),
1✔
491
                        fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry),
1✔
492

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

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

1✔
497
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
498
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
499
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
500
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
501
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
502

1✔
503
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
504
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
505
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
506
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
507
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
508
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
509
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
510

1✔
511
                        fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength),
1✔
512
                        fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()),
1✔
513
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
514
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
515
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
516
                        fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange),
1✔
517
                        fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
1✔
518
                        fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
1✔
519
                        fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
1✔
520
                        fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
1✔
521
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled),
1✔
522
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled),
1✔
523
                        fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
1✔
524

1✔
525
                        // Add rate limit configurations
1✔
526
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers),
1✔
527
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh),
1✔
528
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps),
1✔
529
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications),
1✔
530
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent),
1✔
531
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3),
1✔
532
                }
1✔
533

1✔
534
                // Since signing key is validated by ResolveJWKS, simply read the key file.
1✔
535
                if keys, err := afero.ReadFile(fsys, utils.Config.Auth.SigningKeysPath); err == nil && len(keys) > 0 {
1✔
536
                        env = append(env, "GOTRUE_JWT_KEYS="+string(keys))
×
NEW
537
                        // TODO: deprecate HS256 when it's no longer supported
×
NEW
538
                        env = append(env, "GOTRUE_JWT_VALID_METHODS=HS256,RS256,ES256")
×
UNCOV
539
                }
×
540

541
                if utils.Config.Auth.Email.Smtp != nil && utils.Config.Auth.Email.Smtp.Enabled {
1✔
542
                        env = append(env,
×
543
                                fmt.Sprintf("GOTRUE_RATE_LIMIT_EMAIL_SENT=%v", utils.Config.Auth.RateLimit.EmailSent),
×
544
                                fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host),
×
545
                                fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port),
×
546
                                fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User),
×
547
                                fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass.Value),
×
548
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail),
×
549
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName),
×
550
                        )
×
551
                } else if utils.Config.Inbucket.Enabled {
2✔
552
                        env = append(env,
1✔
553
                                "GOTRUE_SMTP_HOST="+utils.InbucketId,
1✔
554
                                "GOTRUE_SMTP_PORT=1025",
1✔
555
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Inbucket.AdminEmail),
1✔
556
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Inbucket.SenderName),
1✔
557
                        )
1✔
558
                }
1✔
559

560
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
561
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
562
                }
×
563
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
564
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
565
                }
×
566

567
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
568
                        if len(tmpl.ContentPath) > 0 {
×
569
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
570
                                        strings.ToUpper(id),
×
571
                                        utils.KongId,
×
572
                                        nginxTemplateServerPort,
×
573
                                        id+filepath.Ext(tmpl.ContentPath),
×
574
                                ))
×
575
                        }
×
576
                        if tmpl.Subject != nil {
×
577
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
578
                                        strings.ToUpper(id),
×
579
                                        *tmpl.Subject,
×
580
                                ))
×
581
                        }
×
582
                }
583

584
                switch {
1✔
585
                case utils.Config.Auth.Sms.Twilio.Enabled:
×
586
                        env = append(
×
587
                                env,
×
588
                                "GOTRUE_SMS_PROVIDER=twilio",
×
589
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
590
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken.Value,
×
591
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
592
                        )
×
593
                case utils.Config.Auth.Sms.TwilioVerify.Enabled:
×
594
                        env = append(
×
595
                                env,
×
596
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
597
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
598
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken.Value,
×
599
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
600
                        )
×
601
                case utils.Config.Auth.Sms.Messagebird.Enabled:
×
602
                        env = append(
×
603
                                env,
×
604
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
605
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey.Value,
×
606
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
607
                        )
×
608
                case utils.Config.Auth.Sms.Textlocal.Enabled:
×
609
                        env = append(
×
610
                                env,
×
611
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
612
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey.Value,
×
613
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
614
                        )
×
615
                case utils.Config.Auth.Sms.Vonage.Enabled:
×
616
                        env = append(
×
617
                                env,
×
618
                                "GOTRUE_SMS_PROVIDER=vonage",
×
619
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
620
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret.Value,
×
621
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
622
                        )
×
623
                }
624

625
                if captcha := utils.Config.Auth.Captcha; captcha != nil {
1✔
626
                        env = append(
×
627
                                env,
×
628
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_ENABLED=%v", captcha.Enabled),
×
629
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_PROVIDER=%v", captcha.Provider),
×
630
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_SECRET=%v", captcha.Secret.Value),
×
631
                        )
×
632
                }
×
633

634
                if hook := utils.Config.Auth.Hook.MFAVerificationAttempt; hook != nil && hook.Enabled {
1✔
635
                        env = append(
×
636
                                env,
×
637
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
638
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
639
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
640
                        )
×
641
                }
×
642
                if hook := utils.Config.Auth.Hook.PasswordVerificationAttempt; hook != nil && hook.Enabled {
1✔
643
                        env = append(
×
644
                                env,
×
645
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
646
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
647
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
648
                        )
×
649
                }
×
650
                if hook := utils.Config.Auth.Hook.CustomAccessToken; hook != nil && hook.Enabled {
1✔
651
                        env = append(
×
652
                                env,
×
653
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
654
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+hook.URI,
×
655
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets.Value,
×
656
                        )
×
657
                }
×
658
                if hook := utils.Config.Auth.Hook.SendSMS; hook != nil && hook.Enabled {
1✔
659
                        env = append(
×
660
                                env,
×
661
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
662
                                "GOTRUE_HOOK_SEND_SMS_URI="+hook.URI,
×
663
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets.Value,
×
664
                        )
×
665
                }
×
666
                if hook := utils.Config.Auth.Hook.SendEmail; hook != nil && hook.Enabled {
1✔
667
                        env = append(
×
668
                                env,
×
669
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
670
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+hook.URI,
×
671
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets.Value,
×
672
                        )
×
673
                }
×
674
                if hook := utils.Config.Auth.Hook.BeforeUserCreated; hook != nil && hook.Enabled {
1✔
675
                        env = append(
×
676
                                env,
×
677
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_ENABLED=true",
×
678
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_URI="+hook.URI,
×
679
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_SECRETS="+hook.Secrets.Value,
×
680
                        )
×
681
                }
×
682

683
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
684
                        env = append(
×
685
                                env,
×
686
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
687
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
688
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
689
                        )
×
690
                }
×
691

692
                for name, config := range utils.Config.Auth.External {
2✔
693
                        env = append(
1✔
694
                                env,
1✔
695
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
1✔
696
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
1✔
697
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value),
1✔
698
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
1✔
699
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_EMAIL_OPTIONAL=%t", strings.ToUpper(name), config.EmailOptional),
1✔
700
                        )
1✔
701

1✔
702
                        redirectUri := config.RedirectUri
1✔
703
                        if redirectUri == "" {
2✔
704
                                redirectUri = utils.Config.Auth.JwtIssuer + "/callback"
1✔
705
                        }
1✔
706
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
1✔
707

1✔
708
                        if config.Url != "" {
1✔
709
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
710
                        }
×
711
                }
712
                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=%v", utils.Config.Auth.Web3.Solana.Enabled))
1✔
713

1✔
714
                // OAuth server configuration
1✔
715
                if utils.Config.Auth.OAuthServer.Enabled {
1✔
716
                        env = append(env,
×
717
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ENABLED=%v", utils.Config.Auth.OAuthServer.Enabled),
×
718
                                "GOTRUE_OAUTH_SERVER_AUTHORIZATION_PATH="+utils.Config.Auth.OAuthServer.AuthorizationUrlPath,
×
719
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION=%v", utils.Config.Auth.OAuthServer.AllowDynamicRegistration),
×
720
                        )
×
721
                }
×
722

723
                if _, err := utils.DockerStart(
1✔
724
                        ctx,
1✔
725
                        container.Config{
1✔
726
                                Image:        utils.Config.Auth.Image,
1✔
727
                                Env:          env,
1✔
728
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
729
                                Healthcheck: &container.HealthConfig{
1✔
730
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
731
                                                "http://127.0.0.1:9999/health",
1✔
732
                                        },
1✔
733
                                        Interval: 10 * time.Second,
1✔
734
                                        Timeout:  2 * time.Second,
1✔
735
                                        Retries:  3,
1✔
736
                                },
1✔
737
                        },
1✔
738
                        container.HostConfig{
1✔
739
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
740
                        },
1✔
741
                        network.NetworkingConfig{
1✔
742
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
743
                                        utils.NetId: {
1✔
744
                                                Aliases: utils.GotrueAliases,
1✔
745
                                        },
1✔
746
                                },
1✔
747
                        },
1✔
748
                        utils.GotrueId,
1✔
749
                ); err != nil {
1✔
750
                        return err
×
751
                }
×
752
                started = append(started, utils.GotrueId)
1✔
753
        }
754

755
        // Start Mailpit
756
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
757
                inbucketPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
758
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
759
                        inbucketPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
760
                }
×
761
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
762
                        inbucketPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
763
                }
×
764
                if _, err := utils.DockerStart(
1✔
765
                        ctx,
1✔
766
                        container.Config{
1✔
767
                                Image: utils.Config.Inbucket.Image,
1✔
768
                                Env: []string{
1✔
769
                                        // Disable reverse DNS lookups in Mailpit to avoid slow/delayed DNS resolution
1✔
770
                                        "MP_SMTP_DISABLE_RDNS=true",
1✔
771
                                },
1✔
772
                                Healthcheck: &container.HealthConfig{
1✔
773
                                        Test:     []string{"CMD", "/mailpit", "readyz"},
1✔
774
                                        Interval: 10 * time.Second,
1✔
775
                                        Timeout:  2 * time.Second,
1✔
776
                                        Retries:  3,
1✔
777
                                        // StartPeriod taken from upstream Dockerfile
1✔
778
                                        StartPeriod: 10 * time.Second,
1✔
779
                                },
1✔
780
                        },
1✔
781
                        container.HostConfig{
1✔
782
                                PortBindings:  inbucketPortBindings,
1✔
783
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
784
                        },
1✔
785
                        network.NetworkingConfig{
1✔
786
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
787
                                        utils.NetId: {
1✔
788
                                                Aliases: utils.InbucketAliases,
1✔
789
                                        },
1✔
790
                                },
1✔
791
                        },
1✔
792
                        utils.InbucketId,
1✔
793
                ); err != nil {
1✔
794
                        return err
×
795
                }
×
796
                started = append(started, utils.InbucketId)
1✔
797
        }
798

799
        // Start Realtime.
800
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.Config.Realtime.Image, excluded) {
3✔
801
                if _, err := utils.DockerStart(
1✔
802
                        ctx,
1✔
803
                        container.Config{
1✔
804
                                Image: utils.Config.Realtime.Image,
1✔
805
                                Env: []string{
1✔
806
                                        "PORT=4000",
1✔
807
                                        "DB_HOST=" + dbConfig.Host,
1✔
808
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
809
                                        "DB_USER=" + utils.SUPERUSER_ROLE,
1✔
810
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
811
                                        "DB_NAME=" + dbConfig.Database,
1✔
812
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
813
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
814
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
815
                                        fmt.Sprintf("API_JWT_JWKS=%s", jwks),
1✔
816
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
817
                                        "APP_NAME=realtime",
1✔
818
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
819
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
820
                                        "DNS_NODES=''",
1✔
821
                                        "RLIMIT_NOFILE=",
1✔
822
                                        "SEED_SELF_HOST=true",
1✔
823
                                        "RUN_JANITOR=true",
1✔
824
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
825
                                },
1✔
826
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
827
                                Healthcheck: &container.HealthConfig{
1✔
828
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
829
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
830
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
831
                                                "http://127.0.0.1:4000/api/ping",
1✔
832
                                        },
1✔
833
                                        Interval: 10 * time.Second,
1✔
834
                                        Timeout:  2 * time.Second,
1✔
835
                                        Retries:  3,
1✔
836
                                },
1✔
837
                        },
1✔
838
                        container.HostConfig{
1✔
839
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
840
                        },
1✔
841
                        network.NetworkingConfig{
1✔
842
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
843
                                        utils.NetId: {
1✔
844
                                                Aliases: utils.RealtimeAliases,
1✔
845
                                        },
1✔
846
                                },
1✔
847
                        },
1✔
848
                        utils.RealtimeId,
1✔
849
                ); err != nil {
1✔
850
                        return err
×
851
                }
×
852
                started = append(started, utils.RealtimeId)
1✔
853
        }
854

855
        // Start PostgREST.
856
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
857
                if _, err := utils.DockerStart(
1✔
858
                        ctx,
1✔
859
                        container.Config{
1✔
860
                                Image: utils.Config.Api.Image,
1✔
861
                                Env: []string{
1✔
862
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
863
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
864
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
865
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
866
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
867
                                        fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
1✔
868
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
869
                                },
1✔
870
                                // PostgREST does not expose a shell for health check
1✔
871
                        },
1✔
872
                        container.HostConfig{
1✔
873
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
874
                        },
1✔
875
                        network.NetworkingConfig{
1✔
876
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
877
                                        utils.NetId: {
1✔
878
                                                Aliases: utils.RestAliases,
1✔
879
                                        },
1✔
880
                                },
1✔
881
                        },
1✔
882
                        utils.RestId,
1✔
883
                ); err != nil {
1✔
884
                        return err
×
885
                }
×
886
                started = append(started, utils.RestId)
1✔
887
        }
888

889
        // Start Storage.
890
        if isStorageEnabled {
3✔
891
                dockerStoragePath := "/mnt"
1✔
892
                if _, err := utils.DockerStart(
1✔
893
                        ctx,
1✔
894
                        container.Config{
1✔
895
                                Image: utils.Config.Storage.Image,
1✔
896
                                Env: []string{
1✔
897
                                        "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
1✔
898
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
899
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
900
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
901
                                        fmt.Sprintf("JWT_JWKS=%s", jwks),
1✔
902
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
903
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
904
                                        "STORAGE_BACKEND=file",
1✔
905
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
906
                                        "TENANT_ID=stub",
1✔
907
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
908
                                        "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
1✔
909
                                        "GLOBAL_S3_BUCKET=stub",
1✔
910
                                        fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
1✔
911
                                        fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
1✔
912
                                        "TUS_URL_PATH=/storage/v1/upload/resumable",
1✔
913
                                        "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
1✔
914
                                        "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
1✔
915
                                        "S3_PROTOCOL_PREFIX=/storage/v1",
1✔
916
                                        "UPLOAD_FILE_SIZE_LIMIT=52428800000",
1✔
917
                                        "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
1✔
918
                                        "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200",
1✔
919
                                },
1✔
920
                                Healthcheck: &container.HealthConfig{
1✔
921
                                        // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
922
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
923
                                                "http://127.0.0.1:5000/status",
1✔
924
                                        },
1✔
925
                                        Interval: 10 * time.Second,
1✔
926
                                        Timeout:  2 * time.Second,
1✔
927
                                        Retries:  3,
1✔
928
                                },
1✔
929
                        },
1✔
930
                        container.HostConfig{
1✔
931
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
932
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
933
                        },
1✔
934
                        network.NetworkingConfig{
1✔
935
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
936
                                        utils.NetId: {
1✔
937
                                                Aliases: utils.StorageAliases,
1✔
938
                                        },
1✔
939
                                },
1✔
940
                        },
1✔
941
                        utils.StorageId,
1✔
942
                ); err != nil {
1✔
943
                        return err
×
944
                }
×
945
                started = append(started, utils.StorageId)
1✔
946
        }
947

948
        // Start Storage ImgProxy.
949
        if isStorageEnabled && isImgProxyEnabled {
2✔
950
                if _, err := utils.DockerStart(
×
951
                        ctx,
×
952
                        container.Config{
×
953
                                Image: utils.Config.Storage.ImgProxyImage,
×
954
                                Env: []string{
×
955
                                        "IMGPROXY_BIND=:5001",
×
956
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
×
957
                                        "IMGPROXY_USE_ETAG=/",
×
958
                                        "IMGPROXY_MAX_SRC_RESOLUTION=50",
×
959
                                        "IMGPROXY_MAX_SRC_FILE_SIZE=25000000",
×
960
                                        "IMGPROXY_MAX_ANIMATION_FRAMES=60",
×
961
                                        "IMGPROXY_ENABLE_WEBP_DETECTION=true",
×
962
                                        "IMGPROXY_PRESETS=default=width:3000/height:8192",
×
963
                                        "IMGPROXY_FORMAT_QUALITY=jpeg=80,avif=62,webp=80",
×
964
                                },
×
965
                                Healthcheck: &container.HealthConfig{
×
966
                                        Test:     []string{"CMD", "imgproxy", "health"},
×
967
                                        Interval: 10 * time.Second,
×
968
                                        Timeout:  2 * time.Second,
×
969
                                        Retries:  3,
×
970
                                },
×
971
                        },
×
972
                        container.HostConfig{
×
973
                                VolumesFrom:   []string{utils.StorageId},
×
974
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
975
                        },
×
976
                        network.NetworkingConfig{
×
977
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
978
                                        utils.NetId: {
×
979
                                                Aliases: utils.ImgProxyAliases,
×
980
                                        },
×
981
                                },
×
982
                        },
×
983
                        utils.ImgProxyId,
×
984
                ); err != nil {
×
985
                        return err
×
986
                }
×
987
                started = append(started, utils.ImgProxyId)
×
988
        }
989

990
        // Start all functions.
991
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.Config.EdgeRuntime.Image, excluded) {
3✔
992
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
993
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, fsys); err != nil {
1✔
994
                        return err
×
995
                }
×
996
                started = append(started, utils.EdgeRuntimeId)
1✔
997
        }
998

999
        // Start pg-meta.
1000
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
1001
                if _, err := utils.DockerStart(
1✔
1002
                        ctx,
1✔
1003
                        container.Config{
1✔
1004
                                Image: utils.Config.Studio.PgmetaImage,
1✔
1005
                                Env: []string{
1✔
1006
                                        "PG_META_PORT=8080",
1✔
1007
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
1008
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
1009
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
1010
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
1011
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
1012
                                },
1✔
1013
                                Healthcheck: &container.HealthConfig{
1✔
1014
                                        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✔
1015
                                        Interval: 10 * time.Second,
1✔
1016
                                        Timeout:  2 * time.Second,
1✔
1017
                                        Retries:  3,
1✔
1018
                                },
1✔
1019
                        },
1✔
1020
                        container.HostConfig{
1✔
1021
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1022
                        },
1✔
1023
                        network.NetworkingConfig{
1✔
1024
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1025
                                        utils.NetId: {
1✔
1026
                                                Aliases: utils.PgmetaAliases,
1✔
1027
                                        },
1✔
1028
                                },
1✔
1029
                        },
1✔
1030
                        utils.PgmetaId,
1✔
1031
                ); err != nil {
1✔
1032
                        return err
×
1033
                }
×
1034
                started = append(started, utils.PgmetaId)
1✔
1035
        }
1036

1037
        // Start Studio.
1038
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.Image, excluded) {
3✔
1039
                if _, err := utils.DockerStart(
1✔
1040
                        ctx,
1✔
1041
                        container.Config{
1✔
1042
                                Image: utils.Config.Studio.Image,
1✔
1043
                                Env: []string{
1✔
1044
                                        "CURRENT_CLI_VERSION=" + utils.Version,
1✔
1045
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
1046
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
1047
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
1048
                                        "SUPABASE_PUBLIC_URL=" + utils.Config.Studio.ApiUrl,
1✔
1049
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
1050
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
1051
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
1052
                                        "LOGFLARE_PRIVATE_ACCESS_TOKEN=" + utils.Config.Analytics.ApiKey,
1✔
1053
                                        "OPENAI_API_KEY=" + utils.Config.Studio.OpenaiApiKey.Value,
1✔
1054
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
1055
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
1056
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
1057
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
1058
                                        "HOSTNAME=0.0.0.0",
1✔
1059
                                },
1✔
1060
                                Healthcheck: &container.HealthConfig{
1✔
1061
                                        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✔
1062
                                        Interval: 10 * time.Second,
1✔
1063
                                        Timeout:  2 * time.Second,
1✔
1064
                                        Retries:  3,
1✔
1065
                                },
1✔
1066
                        },
1✔
1067
                        container.HostConfig{
1✔
1068
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
1069
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1070
                        },
1✔
1071
                        network.NetworkingConfig{
1✔
1072
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1073
                                        utils.NetId: {
1✔
1074
                                                Aliases: utils.StudioAliases,
1✔
1075
                                        },
1✔
1076
                                },
1✔
1077
                        },
1✔
1078
                        utils.StudioId,
1✔
1079
                ); err != nil {
1✔
1080
                        return err
×
1081
                }
×
1082
                started = append(started, utils.StudioId)
1✔
1083
        }
1084

1085
        // Start pooler.
1086
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
1087
                portSession := uint16(5432)
×
1088
                portTransaction := uint16(6543)
×
1089
                dockerPort := portTransaction
×
1090
                if utils.Config.Db.Pooler.PoolMode == config.SessionMode {
×
1091
                        dockerPort = portSession
×
1092
                }
×
1093
                // Create pooler tenant
1094
                var poolerTenantBuf bytes.Buffer
×
1095
                if err := poolerTenantTemplate.Option("missingkey=error").Execute(&poolerTenantBuf, poolerTenant{
×
1096
                        DbHost:            dbConfig.Host,
×
1097
                        DbPort:            dbConfig.Port,
×
1098
                        DbDatabase:        dbConfig.Database,
×
1099
                        DbPassword:        dbConfig.Password,
×
1100
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
1101
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
1102
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
1103
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
1104
                }); err != nil {
×
1105
                        return errors.Errorf("failed to exec template: %w", err)
×
1106
                }
×
1107
                if _, err := utils.DockerStart(
×
1108
                        ctx,
×
1109
                        container.Config{
×
1110
                                Image: utils.Config.Db.Pooler.Image,
×
1111
                                Env: []string{
×
1112
                                        "PORT=4000",
×
1113
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
1114
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
1115
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
×
1116
                                        "CLUSTER_POSTGRES=true",
×
1117
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
1118
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
1119
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1120
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1121
                                        "REGION=local",
×
1122
                                        "RUN_JANITOR=true",
×
1123
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
×
1124
                                        "RLIMIT_NOFILE=",
×
1125
                                },
×
1126
                                Cmd: []string{
×
1127
                                        "/bin/sh", "-c",
×
1128
                                        fmt.Sprintf("/app/bin/migrate && /app/bin/supavisor eval '%s' && /app/bin/server", poolerTenantBuf.String()),
×
1129
                                },
×
1130
                                ExposedPorts: nat.PortSet{
×
1131
                                        "4000/tcp": {},
×
1132
                                        nat.Port(fmt.Sprintf("%d/tcp", portSession)):     {},
×
1133
                                        nat.Port(fmt.Sprintf("%d/tcp", portTransaction)): {},
×
1134
                                },
×
1135
                                Healthcheck: &container.HealthConfig{
×
1136
                                        Test:     []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/api/health"},
×
1137
                                        Interval: 10 * time.Second,
×
1138
                                        Timeout:  2 * time.Second,
×
1139
                                        Retries:  3,
×
1140
                                },
×
1141
                        },
×
1142
                        container.HostConfig{
×
1143
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
×
1144
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)},
×
1145
                                }},
×
1146
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
1147
                        },
×
1148
                        network.NetworkingConfig{
×
1149
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
1150
                                        utils.NetId: {
×
1151
                                                Aliases: utils.PoolerAliases,
×
1152
                                        },
×
1153
                                },
×
1154
                        },
×
1155
                        utils.PoolerId,
×
1156
                ); err != nil {
×
1157
                        return err
×
1158
                }
×
1159
                started = append(started, utils.PoolerId)
×
1160
        }
1161

1162
        fmt.Fprintln(os.Stderr, "Waiting for health checks...")
2✔
1163
        if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
3✔
1164
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1165
                        return err
×
1166
                }
×
1167
                // Disable prompts when seeding
1168
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
1169
                        return err
×
1170
                }
×
1171
        }
1172
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1173
}
1174

1175
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
22✔
1176
        short := utils.ShortContainerImageName(imageName)
22✔
1177
        val, ok := excluded[short]
22✔
1178
        return ok && val
22✔
1179
}
22✔
1180

1181
func ExcludableContainers() []string {
1✔
1182
        names := []string{}
1✔
1183
        for _, image := range config.Images.Services() {
14✔
1184
                names = append(names, utils.ShortContainerImageName(image))
13✔
1185
        }
13✔
1186
        return names
1✔
1187
}
1188

1189
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1190
        numOfKeyPairs := len(input)
5✔
1191
        i := 0
5✔
1192
        for k, v := range input {
15✔
1193
                output.WriteString(k)
10✔
1194
                output.WriteString(":")
10✔
1195
                output.WriteString(v)
10✔
1196
                i++
10✔
1197
                if i < numOfKeyPairs {
16✔
1198
                        output.WriteString(",")
6✔
1199
                }
6✔
1200
        }
1201
}
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