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

supabase / cli / 15836777110

23 Jun 2025 10:49PM UTC coverage: 55.457% (-0.06%) from 55.517%
15836777110

Pull #3749

github

web-flow
Merge b33430ec3 into f3aaa4964
Pull Request #3749: feat: add support for before-user-created hook

1 of 8 new or added lines in 1 file covered. (12.5%)

5 existing lines in 1 file now uncovered.

6026 of 10866 relevant lines covered (55.46%)

6.26 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

140
var serviceTimeout = 30 * time.Second
141

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

653
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
654
                        env = append(
×
655
                                env,
×
656
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
657
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
658
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
659
                        )
×
660
                }
×
661

662
                for name, config := range utils.Config.Auth.External {
2✔
663
                        env = append(
1✔
664
                                env,
1✔
665
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
1✔
666
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
1✔
667
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value),
1✔
668
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
1✔
669
                        )
1✔
670

1✔
671
                        redirectUri := config.RedirectUri
1✔
672
                        if redirectUri == "" {
2✔
673
                                redirectUri = utils.GetApiUrl("/auth/v1/callback")
1✔
674
                        }
1✔
675
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
1✔
676

1✔
677
                        if config.Url != "" {
1✔
678
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
679
                        }
×
680
                }
681
                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=%v", utils.Config.Auth.Web3.Solana.Enabled))
1✔
682

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

715
        // Start Mailpit
716
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
717
                inbucketPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
718
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
719
                        inbucketPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
720
                }
×
721
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
722
                        inbucketPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
723
                }
×
724
                if _, err := utils.DockerStart(
1✔
725
                        ctx,
1✔
726
                        container.Config{
1✔
727
                                Image: utils.Config.Inbucket.Image,
1✔
728
                        },
1✔
729
                        container.HostConfig{
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.Value,
1✔
763
                                        fmt.Sprintf("API_JWT_JWKS=%s", jwks),
1✔
764
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
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
                                        "RUN_JANITOR=true",
1✔
772
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
773
                                },
1✔
774
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
775
                                Healthcheck: &container.HealthConfig{
1✔
776
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
777
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
778
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
779
                                                "http://127.0.0.1:4000/api/ping",
1✔
780
                                        },
1✔
781
                                        Interval: 10 * time.Second,
1✔
782
                                        Timeout:  2 * time.Second,
1✔
783
                                        Retries:  3,
1✔
784
                                },
1✔
785
                        },
1✔
786
                        container.HostConfig{
1✔
787
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
788
                        },
1✔
789
                        network.NetworkingConfig{
1✔
790
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
791
                                        utils.NetId: {
1✔
792
                                                Aliases: utils.RealtimeAliases,
1✔
793
                                        },
1✔
794
                                },
1✔
795
                        },
1✔
796
                        utils.RealtimeId,
1✔
797
                ); err != nil {
1✔
798
                        return err
×
799
                }
×
800
                started = append(started, utils.RealtimeId)
1✔
801
        }
802

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

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

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

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

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

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

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

1106
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
1107
        if utils.NoBackupVolume && utils.SliceContains(started, utils.StorageId) {
3✔
1108
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1109
                        return err
×
1110
                }
×
1111
                // Disable prompts when seeding
1112
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
1113
                        return err
×
1114
                }
×
1115
        }
1116
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1117
}
1118

1119
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
22✔
1120
        short := utils.ShortContainerImageName(imageName)
22✔
1121
        val, ok := excluded[short]
22✔
1122
        return ok && val
22✔
1123
}
22✔
1124

1125
func ExcludableContainers() []string {
1✔
1126
        names := []string{}
1✔
1127
        for _, image := range config.Images.Services() {
14✔
1128
                names = append(names, utils.ShortContainerImageName(image))
13✔
1129
        }
13✔
1130
        return names
1✔
1131
}
1132

1133
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1134
        numOfKeyPairs := len(input)
5✔
1135
        i := 0
5✔
1136
        for k, v := range input {
15✔
1137
                output.WriteString(k)
10✔
1138
                output.WriteString(":")
10✔
1139
                output.WriteString(v)
10✔
1140
                i++
10✔
1141
                if i < numOfKeyPairs {
16✔
1142
                        output.WriteString(",")
6✔
1143
                }
6✔
1144
        }
1145
}
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