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

supabase / cli / 12068378695

28 Nov 2024 11:54AM UTC coverage: 59.583% (+0.01%) from 59.573%
12068378695

Pull #2885

github

sweatybridge
chore: add missing testdata
Pull Request #2885: feat: add minimum password length and password requirements config

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

14 existing lines in 1 file now uncovered.

6401 of 10743 relevant lines covered (59.58%)

6.07 hits per line

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

68.49
/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
        "golang.org/x/mod/semver"
35
)
36

37
func suggestUpdateCmd(serviceImages map[string]string) string {
×
38
        cmd := fmt.Sprintln(utils.Yellow("WARNING:"), "You are running different service versions locally than your linked project:")
×
39
        for k, v := range serviceImages {
×
40
                cmd += fmt.Sprintf("%s => %s\n", k, v)
×
41
        }
×
42
        cmd += fmt.Sprintf("Run %s to update them.", utils.Aqua("supabase link"))
×
43
        return cmd
×
44
}
45

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

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

96
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
97
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
98
        return nil
×
99
}
100

101
type kongConfig struct {
102
        GotrueId      string
103
        RestId        string
104
        RealtimeId    string
105
        StorageId     string
106
        PgmetaId      string
107
        EdgeRuntimeId string
108
        LogflareId    string
109
        PoolerId      string
110
        ApiHost       string
111
        ApiPort       uint16
112
}
113

114
// TODO: deprecate after removing storage headers from kong
115
func StorageVersionBelow(target string) bool {
2✔
116
        parts := strings.Split(utils.Config.Storage.Image, ":v")
2✔
117
        return semver.Compare(parts[len(parts)-1], target) < 0
2✔
118
}
2✔
119

120
var (
121
        //go:embed templates/kong.yml
122
        kongConfigEmbed    string
123
        kongConfigTemplate = template.Must(template.New("kongConfig").Funcs(template.FuncMap{
124
                "StorageVersionBelow": StorageVersionBelow,
125
        }).Parse(kongConfigEmbed))
126

127
        //go:embed templates/custom_nginx.template
128
        nginxConfigEmbed string
129
        // Hardcoded configs which match nginxConfigEmbed
130
        nginxEmailTemplateDir   = "/home/kong/templates/email"
131
        nginxTemplateServerPort = 8088
132
)
133

134
type vectorConfig struct {
135
        ApiKey        string
136
        VectorId      string
137
        LogflareId    string
138
        KongId        string
139
        GotrueId      string
140
        RestId        string
141
        RealtimeId    string
142
        StorageId     string
143
        EdgeRuntimeId string
144
        DbId          string
145
}
146

147
var (
148
        //go:embed templates/vector.yaml
149
        vectorConfigEmbed    string
150
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
151
)
152

153
type poolerTenant struct {
154
        DbHost            string
155
        DbPort            uint16
156
        DbDatabase        string
157
        DbPassword        string
158
        ExternalId        string
159
        ModeType          config.PoolMode
160
        DefaultMaxClients uint
161
        DefaultPoolSize   uint
162
}
163

164
var (
165
        //go:embed templates/pooler.exs
166
        poolerTenantEmbed    string
167
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
168
)
169

170
var serviceTimeout = 30 * time.Second
171

172
func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
173
        excluded := make(map[string]bool)
2✔
174
        for _, name := range excludedContainers {
17✔
175
                excluded[name] = true
15✔
176
        }
15✔
177

178
        jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
2✔
179
        if err != nil {
2✔
180
                return err
×
181
        }
×
182

183
        // Start Postgres.
184
        w := utils.StatusWriter{Program: p}
2✔
185
        if dbConfig.Host == utils.DbId {
4✔
186
                if err := start.StartDatabase(ctx, fsys, w, options...); err != nil {
2✔
187
                        return err
×
188
                }
×
189
        }
190

191
        var started []string
2✔
192
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
193
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
194

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

1✔
215
                switch utils.Config.Analytics.Backend {
1✔
216
                case config.LogflareBigQuery:
×
217
                        workdir, err := os.Getwd()
×
218
                        if err != nil {
×
219
                                return errors.Errorf("failed to get working directory: %w", err)
×
220
                        }
×
221
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
222
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
223
                        // This is hardcoded in studio frontend
×
224
                        env = append(env,
×
225
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
226
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
227
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
228
                        )
×
229
                case config.LogflarePostgres:
1✔
230
                        env = append(env,
1✔
231
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
1✔
232
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
1✔
233
                        )
1✔
234
                }
235

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

279
        // Start vector
280
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.VectorImage, excluded) {
3✔
281
                var vectorConfigBuf bytes.Buffer
1✔
282
                if err := vectorConfigTemplate.Option("missingkey=error").Execute(&vectorConfigBuf, vectorConfig{
1✔
283
                        ApiKey:        utils.Config.Analytics.ApiKey,
1✔
284
                        VectorId:      utils.VectorId,
1✔
285
                        LogflareId:    utils.LogflareId,
1✔
286
                        KongId:        utils.KongId,
1✔
287
                        GotrueId:      utils.GotrueId,
1✔
288
                        RestId:        utils.RestId,
1✔
289
                        RealtimeId:    utils.RealtimeId,
1✔
290
                        StorageId:     utils.StorageId,
1✔
291
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
292
                        DbId:          utils.DbId,
1✔
293
                }); err != nil {
1✔
294
                        return errors.Errorf("failed to exec template: %w", err)
×
295
                }
×
296
                var binds, env []string
1✔
297
                // Special case for GitLab pipeline
1✔
298
                parsed, err := client.ParseHostURL(utils.Docker.DaemonHost())
1✔
299
                if err != nil {
1✔
300
                        return errors.Errorf("failed to parse docker host: %w", err)
×
301
                }
×
302
                // Ref: https://vector.dev/docs/reference/configuration/sources/docker_logs/#docker_host
303
                dindHost := url.URL{Scheme: "http", Host: net.JoinHostPort(utils.DinDHost, "2375")}
1✔
304
                switch parsed.Scheme {
1✔
305
                case "tcp":
×
306
                        if _, port, err := net.SplitHostPort(parsed.Host); err == nil {
×
307
                                dindHost.Host = net.JoinHostPort(utils.DinDHost, port)
×
308
                        }
×
309
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
310
                case "npipe":
×
311
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires docker daemon exposed on tcp://localhost:2375")
×
312
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
313
                case "unix":
×
314
                        if parsed, err = client.ParseHostURL(client.DefaultDockerHost); err != nil {
×
315
                                return errors.Errorf("failed to parse default host: %w", err)
×
316
                        }
×
317
                        if utils.Docker.DaemonHost() != client.DefaultDockerHost {
×
318
                                fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires mounting default docker socket:", parsed.Host)
×
319
                        }
×
320
                        binds = append(binds, fmt.Sprintf("%[1]s:%[1]s:ro", parsed.Host))
×
321
                }
322
                if _, err := utils.DockerStart(
1✔
323
                        ctx,
1✔
324
                        container.Config{
1✔
325
                                Image: utils.Config.Analytics.VectorImage,
1✔
326
                                Env:   env,
1✔
327
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/vector/vector.yaml && vector --config /etc/vector/vector.yaml
1✔
328
` + vectorConfigBuf.String() + `
1✔
329
EOF
1✔
330
`},
1✔
331
                                Healthcheck: &container.HealthConfig{
1✔
332
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
333
                                                "http://127.0.0.1:9001/health",
1✔
334
                                        },
1✔
335
                                        Interval: 10 * time.Second,
1✔
336
                                        Timeout:  2 * time.Second,
1✔
337
                                        Retries:  3,
1✔
338
                                },
1✔
339
                        },
1✔
340
                        container.HostConfig{
1✔
341
                                Binds:         binds,
1✔
342
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
343
                        },
1✔
344
                        network.NetworkingConfig{
1✔
345
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
346
                                        utils.NetId: {
1✔
347
                                                Aliases: utils.VectorAliases,
1✔
348
                                        },
1✔
349
                                },
1✔
350
                        },
1✔
351
                        utils.VectorId,
1✔
352
                ); err != nil {
1✔
353
                        return err
×
354
                }
×
355
                started = append(started, utils.VectorId)
1✔
356
        }
357

358
        // Start Kong.
359
        if !isContainerExcluded(utils.Config.Api.KongImage, excluded) {
3✔
360
                var kongConfigBuf bytes.Buffer
1✔
361
                if err := kongConfigTemplate.Option("missingkey=error").Execute(&kongConfigBuf, kongConfig{
1✔
362
                        GotrueId:      utils.GotrueId,
1✔
363
                        RestId:        utils.RestId,
1✔
364
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
365
                        StorageId:     utils.StorageId,
1✔
366
                        PgmetaId:      utils.PgmetaId,
1✔
367
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
368
                        LogflareId:    utils.LogflareId,
1✔
369
                        PoolerId:      utils.PoolerId,
1✔
370
                        ApiHost:       utils.Config.Hostname,
1✔
371
                        ApiPort:       utils.Config.Api.Port,
1✔
372
                }); err != nil {
1✔
373
                        return errors.Errorf("failed to exec template: %w", err)
×
374
                }
×
375

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

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

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

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

1✔
468
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
469
                        "GOTRUE_API_PORT=9999",
1✔
470

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

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

1✔
478
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
479
                        "GOTRUE_JWT_AUD=authenticated",
1✔
480
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
481
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
482
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
483
                        "GOTRUE_JWT_ISSUER=" + utils.GetApiUrl("/auth/v1"),
1✔
484

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

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

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

1✔
495
                        "GOTRUE_MAILER_URLPATHS_INVITE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
496
                        "GOTRUE_MAILER_URLPATHS_CONFIRMATION=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
497
                        "GOTRUE_MAILER_URLPATHS_RECOVERY=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
498
                        "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
499
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
500

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

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

1✔
524
                if utils.Config.Auth.Email.Smtp != nil {
1✔
525
                        env = append(env,
×
526
                                fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host),
×
527
                                fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port),
×
528
                                fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User),
×
529
                                fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass),
×
530
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail),
×
531
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName),
×
532
                        )
×
533
                } else if utils.Config.Inbucket.Enabled {
2✔
534
                        env = append(env,
1✔
535
                                "GOTRUE_SMTP_HOST="+utils.InbucketId,
1✔
536
                                "GOTRUE_SMTP_PORT=2500",
1✔
537
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Inbucket.AdminEmail),
1✔
538
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Inbucket.SenderName),
1✔
539
                        )
1✔
540
                }
1✔
541

542
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
543
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
544
                }
×
545
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
546
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
547
                }
×
548

549
                for id, tmpl := range utils.Config.Auth.Email.Template {
7✔
550
                        if len(tmpl.ContentPath) > 0 {
6✔
551
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
552
                                        strings.ToUpper(id),
×
553
                                        utils.KongId,
×
554
                                        nginxTemplateServerPort,
×
555
                                        id+filepath.Ext(tmpl.ContentPath),
×
556
                                ))
×
557
                        }
×
558
                        if tmpl.Subject != nil {
6✔
559
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
560
                                        strings.ToUpper(id),
×
561
                                        *tmpl.Subject,
×
562
                                ))
×
563
                        }
×
564
                }
565

566
                switch {
1✔
567
                case utils.Config.Auth.Sms.Twilio.Enabled:
×
568
                        env = append(
×
569
                                env,
×
570
                                "GOTRUE_SMS_PROVIDER=twilio",
×
571
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
572
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken,
×
573
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
574
                        )
×
575
                case utils.Config.Auth.Sms.TwilioVerify.Enabled:
×
576
                        env = append(
×
577
                                env,
×
578
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
579
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
580
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken,
×
581
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
582
                        )
×
583
                case utils.Config.Auth.Sms.Messagebird.Enabled:
×
584
                        env = append(
×
585
                                env,
×
586
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
587
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey,
×
588
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
589
                        )
×
590
                case utils.Config.Auth.Sms.Textlocal.Enabled:
×
591
                        env = append(
×
592
                                env,
×
593
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
594
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey,
×
595
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
596
                        )
×
597
                case utils.Config.Auth.Sms.Vonage.Enabled:
×
598
                        env = append(
×
599
                                env,
×
600
                                "GOTRUE_SMS_PROVIDER=vonage",
×
601
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
602
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret,
×
603
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
604
                        )
×
605
                }
606

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

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

657
                for name, config := range utils.Config.Auth.External {
20✔
658
                        env = append(
19✔
659
                                env,
19✔
660
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
19✔
661
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
19✔
662
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret),
19✔
663
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
19✔
664
                        )
19✔
665

19✔
666
                        redirectUri := config.RedirectUri
19✔
667
                        if redirectUri == "" {
38✔
668
                                redirectUri = utils.GetApiUrl("/auth/v1/callback")
19✔
669
                        }
19✔
670
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
19✔
671

19✔
672
                        if config.Url != "" {
19✔
673
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
674
                        }
×
675
                }
676

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

709
        // Start Inbucket.
710
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
711
                inbucketPortBindings := nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
712
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
713
                        inbucketPortBindings["2500/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
714
                }
×
715
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
716
                        inbucketPortBindings["1100/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
717
                }
×
718
                if _, err := utils.DockerStart(
1✔
719
                        ctx,
1✔
720
                        container.Config{
1✔
721
                                Image: utils.Config.Inbucket.Image,
1✔
722
                        },
1✔
723
                        container.HostConfig{
1✔
724
                                Binds: []string{
1✔
725
                                        // Override default mount points to avoid creating multiple anonymous volumes
1✔
726
                                        // Ref: https://github.com/inbucket/inbucket/blob/v3.0.4/Dockerfile#L52
1✔
727
                                        utils.InbucketId + ":/config",
1✔
728
                                        utils.InbucketId + ":/storage",
1✔
729
                                },
1✔
730
                                PortBindings:  inbucketPortBindings,
1✔
731
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
732
                        },
1✔
733
                        network.NetworkingConfig{
1✔
734
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
735
                                        utils.NetId: {
1✔
736
                                                Aliases: utils.InbucketAliases,
1✔
737
                                        },
1✔
738
                                },
1✔
739
                        },
1✔
740
                        utils.InbucketId,
1✔
741
                ); err != nil {
1✔
742
                        return err
×
743
                }
×
744
                started = append(started, utils.InbucketId)
1✔
745
        }
746

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

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

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

894
        // Start Storage ImgProxy.
895
        if isStorageEnabled && utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImageTransformation.Image, excluded) {
896
                if _, err := utils.DockerStart(
3✔
897
                        ctx,
1✔
898
                        container.Config{
1✔
899
                                Image: utils.Config.Storage.ImageTransformation.Image,
1✔
900
                                Env: []string{
1✔
901
                                        "IMGPROXY_BIND=:5001",
1✔
902
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
1✔
903
                                        "IMGPROXY_USE_ETAG=/",
1✔
904
                                },
1✔
905
                                Healthcheck: &container.HealthConfig{
1✔
906
                                        Test:     []string{"CMD", "imgproxy", "health"},
1✔
907
                                        Interval: 10 * time.Second,
1✔
908
                                        Timeout:  2 * time.Second,
1✔
909
                                        Retries:  3,
1✔
910
                                },
1✔
911
                        },
1✔
912
                        container.HostConfig{
1✔
913
                                VolumesFrom:   []string{utils.StorageId},
1✔
914
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
915
                        },
1✔
916
                        network.NetworkingConfig{
1✔
917
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
918
                                        utils.NetId: {
1✔
919
                                                Aliases: utils.ImgProxyAliases,
1✔
920
                                        },
1✔
921
                                },
1✔
922
                        },
1✔
923
                        utils.ImgProxyId,
1✔
924
                ); err != nil {
1✔
925
                        return err
1✔
926
                }
×
UNCOV
927
                started = append(started, utils.ImgProxyId)
×
928
        }
1✔
929

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

939
        // Start pg-meta.
940
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
941
                if _, err := utils.DockerStart(
3✔
942
                        ctx,
1✔
943
                        container.Config{
1✔
944
                                Image: utils.Config.Studio.PgmetaImage,
1✔
945
                                Env: []string{
1✔
946
                                        "PG_META_PORT=8080",
1✔
947
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
948
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
949
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
950
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
951
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
952
                                },
1✔
953
                                Healthcheck: &container.HealthConfig{
1✔
954
                                        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✔
955
                                        Interval: 10 * time.Second,
1✔
956
                                        Timeout:  2 * time.Second,
1✔
957
                                        Retries:  3,
1✔
958
                                },
1✔
959
                        },
1✔
960
                        container.HostConfig{
1✔
961
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
962
                        },
1✔
963
                        network.NetworkingConfig{
1✔
964
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
965
                                        utils.NetId: {
1✔
966
                                                Aliases: utils.PgmetaAliases,
1✔
967
                                        },
1✔
968
                                },
1✔
969
                        },
1✔
970
                        utils.PgmetaId,
1✔
971
                ); err != nil {
1✔
972
                        return err
1✔
973
                }
×
UNCOV
974
                started = append(started, utils.PgmetaId)
×
975
        }
1✔
976

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

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

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

2✔
1112
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
1113
        short := utils.ShortContainerImageName(imageName)
1114
        val, ok := excluded[short]
23✔
1115
        return ok && val
23✔
1116
}
23✔
1117

23✔
1118
func ExcludableContainers() []string {
23✔
1119
        names := []string{}
1120
        for _, image := range config.ServiceImages {
2✔
1121
                names = append(names, utils.ShortContainerImageName(image))
2✔
1122
        }
28✔
1123
        return names
26✔
1124
}
26✔
1125

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