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

supabase / cli / 13417341463

19 Feb 2025 04:20PM UTC coverage: 58.274% (-0.04%) from 58.318%
13417341463

Pull #3164

github

web-flow
Merge 325648321 into 29e241527
Pull Request #3164: fix: block root-less socket mount for docker engine

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

5 existing lines in 1 file now uncovered.

7779 of 13349 relevant lines covered (58.27%)

201.84 hits per line

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

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

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

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

93
// TODO: deprecate after removing storage headers from kong
94
func StorageVersionBelow(target string) bool {
2✔
95
        parts := strings.Split(utils.Config.Storage.Image, ":v")
2✔
96
        return semver.Compare(parts[len(parts)-1], target) < 0
2✔
97
}
2✔
98

99
var (
100
        //go:embed templates/kong.yml
101
        kongConfigEmbed    string
102
        kongConfigTemplate = template.Must(template.New("kongConfig").Funcs(template.FuncMap{
103
                "StorageVersionBelow": StorageVersionBelow,
104
        }).Parse(kongConfigEmbed))
105

106
        //go:embed templates/custom_nginx.template
107
        nginxConfigEmbed string
108
        // Hardcoded configs which match nginxConfigEmbed
109
        nginxEmailTemplateDir   = "/home/kong/templates/email"
110
        nginxTemplateServerPort = 8088
111
)
112

113
type vectorConfig struct {
114
        ApiKey        string
115
        VectorId      string
116
        LogflareId    string
117
        KongId        string
118
        GotrueId      string
119
        RestId        string
120
        RealtimeId    string
121
        StorageId     string
122
        EdgeRuntimeId string
123
        DbId          string
124
}
125

126
var (
127
        //go:embed templates/vector.yaml
128
        vectorConfigEmbed    string
129
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
130
)
131

132
type poolerTenant struct {
133
        DbHost            string
134
        DbPort            uint16
135
        DbDatabase        string
136
        DbPassword        string
137
        ExternalId        string
138
        ModeType          config.PoolMode
139
        DefaultMaxClients uint
140
        DefaultPoolSize   uint
141
}
142

143
var (
144
        //go:embed templates/pooler.exs
145
        poolerTenantEmbed    string
146
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
147
)
148

149
var serviceTimeout = 30 * time.Second
150

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

157
        jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
2✔
158
        if err != nil {
2✔
159
                return err
×
160
        }
×
161

162
        // Start Postgres.
163
        w := utils.StatusWriter{Program: p}
2✔
164
        if dbConfig.Host == utils.DbId {
4✔
165
                if err := start.StartDatabase(ctx, "", fsys, w, options...); err != nil {
2✔
166
                        return err
×
167
                }
×
168
        }
169

170
        var started []string
2✔
171
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
172
        var isImgProxyEnabled = utils.Config.Storage.ImageTransformation != nil &&
2✔
173
                utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
2✔
174
        p.Send(utils.StatusMsg("Starting containers..."))
2✔
175

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

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

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

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

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

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

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

443
        // Start GoTrue.
444
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
445
                var testOTP bytes.Buffer
1✔
446
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
447
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
448
                }
×
449

450
                env := []string{
1✔
451
                        "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl,
1✔
452

1✔
453
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
454
                        "GOTRUE_API_PORT=9999",
1✔
455

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

1✔
459
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
460
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
461
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
462

1✔
463
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
464
                        "GOTRUE_JWT_AUD=authenticated",
1✔
465
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
466
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
467
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
1✔
468
                        "GOTRUE_JWT_ISSUER=" + utils.GetApiUrl("/auth/v1"),
1✔
469

1✔
470
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
471
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
472
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
473
                        fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength),
1✔
474
                        fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry),
1✔
475

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

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

1✔
480
                        "GOTRUE_MAILER_URLPATHS_INVITE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
481
                        "GOTRUE_MAILER_URLPATHS_CONFIRMATION=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
482
                        "GOTRUE_MAILER_URLPATHS_RECOVERY=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
483
                        "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=" + utils.GetApiUrl("/auth/v1/verify"),
1✔
484
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
485

1✔
486
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
487
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
488
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
489
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
490
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
491
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
492
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
493

1✔
494
                        fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength),
1✔
495
                        fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()),
1✔
496
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
497
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
498
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
499
                        fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange),
1✔
500
                        fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
1✔
501
                        fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
1✔
502
                        fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
1✔
503
                        fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
1✔
504
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled),
1✔
505
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled),
1✔
506
                        fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
1✔
507
                }
1✔
508

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1023
        // Start pooler.
1024
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
1025
                portSession := uint16(5432)
×
1026
                portTransaction := uint16(6543)
×
1027
                dockerPort := portTransaction
×
1028
                if utils.Config.Db.Pooler.PoolMode == config.SessionMode {
×
1029
                        dockerPort = portSession
×
1030
                }
×
1031
                // Create pooler tenant
1032
                var poolerTenantBuf bytes.Buffer
×
1033
                if err := poolerTenantTemplate.Option("missingkey=error").Execute(&poolerTenantBuf, poolerTenant{
×
1034
                        DbHost:            dbConfig.Host,
×
1035
                        DbPort:            dbConfig.Port,
×
1036
                        DbDatabase:        dbConfig.Database,
×
1037
                        DbPassword:        dbConfig.Password,
×
1038
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
1039
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
1040
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
1041
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
1042
                }); err != nil {
×
1043
                        return errors.Errorf("failed to exec template: %w", err)
×
1044
                }
×
1045
                if _, err := utils.DockerStart(
×
1046
                        ctx,
×
1047
                        container.Config{
×
1048
                                Image: utils.Config.Db.Pooler.Image,
×
1049
                                Env: []string{
×
1050
                                        "PORT=4000",
×
1051
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
1052
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
1053
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
×
1054
                                        "CLUSTER_POSTGRES=true",
×
1055
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
1056
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
1057
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
×
1058
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
×
1059
                                        "REGION=local",
×
1060
                                        "RUN_JANITOR=true",
×
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)
×
1097
        }
1098

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

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

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

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