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

supabase / cli / 15318069582

29 May 2025 06:47AM UTC coverage: 60.106%. First build
15318069582

Pull #3605

github

web-flow
Merge 9bb351fc3 into 0cbb76c49
Pull Request #3605: feat: add web3 solana configs

16 of 23 new or added lines in 2 files covered. (69.57%)

9031 of 15025 relevant lines covered (60.11%)

508.58 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

140
var serviceTimeout = 30 * time.Second
141

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

673
                if utils.Config.Auth.Web3.Solana.Enabled {
1✔
NEW
674
                        env = append(env, "GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=true")
×
NEW
675

×
NEW
676
                        if utils.Config.Auth.RateLimit.Web3 != 0 {
×
NEW
677
                                env = append(env, fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3))
×
NEW
678
                        }
×
679
                }
680

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

713
        // Start Mailpit
714
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
715
                inbucketPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
716
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
717
                        inbucketPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
718
                }
×
719
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
720
                        inbucketPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
721
                }
×
722
                if _, err := utils.DockerStart(
1✔
723
                        ctx,
1✔
724
                        container.Config{
1✔
725
                                Image: utils.Config.Inbucket.Image,
1✔
726
                        },
1✔
727
                        container.HostConfig{
1✔
728
                                PortBindings:  inbucketPortBindings,
1✔
729
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
730
                        },
1✔
731
                        network.NetworkingConfig{
1✔
732
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
733
                                        utils.NetId: {
1✔
734
                                                Aliases: utils.InbucketAliases,
1✔
735
                                        },
1✔
736
                                },
1✔
737
                        },
1✔
738
                        utils.InbucketId,
1✔
739
                ); err != nil {
1✔
740
                        return err
×
741
                }
×
742
                started = append(started, utils.InbucketId)
1✔
743
        }
744

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

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

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

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

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

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

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

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

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

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

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

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