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

supabase / cli / 12212288177

07 Dec 2024 10:53AM UTC coverage: 59.644% (-0.003%) from 59.647%
12212288177

Pull #2952

github

sweatybridge
fix: account for remote config when pushing
Pull Request #2952: fix: account for remote config when pushing

38 of 58 new or added lines in 10 files covered. (65.52%)

8 existing lines in 3 files now uncovered.

6404 of 10737 relevant lines covered (59.64%)

6.07 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

149
var serviceTimeout = 30 * time.Second
150

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

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

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

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

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

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

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

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

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

521
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
522
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
523
                }
×
524
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
525
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
526
                }
×
527

528
                for id, tmpl := range utils.Config.Auth.Email.Template {
7✔
529
                        if len(tmpl.ContentPath) > 0 {
6✔
530
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
531
                                        strings.ToUpper(id),
×
532
                                        utils.KongId,
×
533
                                        nginxTemplateServerPort,
×
534
                                        id+filepath.Ext(tmpl.ContentPath),
×
535
                                ))
×
536
                        }
×
537
                        if tmpl.Subject != nil {
6✔
538
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
539
                                        strings.ToUpper(id),
×
540
                                        *tmpl.Subject,
×
541
                                ))
×
542
                        }
×
543
                }
544

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

586
                if utils.Config.Auth.Hook.MFAVerificationAttempt.Enabled {
1✔
587
                        env = append(
×
588
                                env,
×
589
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
590
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.MFAVerificationAttempt.URI,
×
591
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets,
×
592
                        )
×
593
                }
×
594
                if utils.Config.Auth.Hook.PasswordVerificationAttempt.Enabled {
1✔
595
                        env = append(
×
596
                                env,
×
597
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
598
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.PasswordVerificationAttempt.URI,
×
599
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets,
×
600
                        )
×
601
                }
×
602
                if utils.Config.Auth.Hook.CustomAccessToken.Enabled {
1✔
603
                        env = append(
×
604
                                env,
×
605
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
606
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+utils.Config.Auth.Hook.CustomAccessToken.URI,
×
607
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+utils.Config.Auth.Hook.CustomAccessToken.Secrets,
×
608
                        )
×
609
                }
×
610
                if utils.Config.Auth.Hook.SendSMS.Enabled {
1✔
611
                        env = append(
×
612
                                env,
×
613
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
614
                                "GOTRUE_HOOK_SEND_SMS_URI="+utils.Config.Auth.Hook.SendSMS.URI,
×
615
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+utils.Config.Auth.Hook.SendSMS.Secrets,
×
616
                        )
×
617
                }
×
618
                if utils.Config.Auth.Hook.SendEmail.Enabled {
1✔
619
                        env = append(
×
620
                                env,
×
621
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
622
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+utils.Config.Auth.Hook.SendEmail.URI,
×
623
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets,
×
624
                        )
×
625
                }
×
626

627
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
628
                        env = append(
×
629
                                env,
×
630
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
631
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
632
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
633
                        )
×
634
                }
×
635

636
                for name, config := range utils.Config.Auth.External {
20✔
637
                        env = append(
19✔
638
                                env,
19✔
639
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
19✔
640
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
19✔
641
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret),
19✔
642
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
19✔
643
                        )
19✔
644

19✔
645
                        redirectUri := config.RedirectUri
19✔
646
                        if redirectUri == "" {
38✔
647
                                redirectUri = utils.GetApiUrl("/auth/v1/callback")
19✔
648
                        }
19✔
649
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
19✔
650

19✔
651
                        if config.Url != "" {
19✔
652
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
653
                        }
×
654
                }
655

656
                if _, err := utils.DockerStart(
1✔
657
                        ctx,
1✔
658
                        container.Config{
1✔
659
                                Image:        utils.Config.Auth.Image,
1✔
660
                                Env:          env,
1✔
661
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
662
                                Healthcheck: &container.HealthConfig{
1✔
663
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
664
                                                "http://127.0.0.1:9999/health",
1✔
665
                                        },
1✔
666
                                        Interval: 10 * time.Second,
1✔
667
                                        Timeout:  2 * time.Second,
1✔
668
                                        Retries:  3,
1✔
669
                                },
1✔
670
                        },
1✔
671
                        container.HostConfig{
1✔
672
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
673
                        },
1✔
674
                        network.NetworkingConfig{
1✔
675
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
676
                                        utils.NetId: {
1✔
677
                                                Aliases: utils.GotrueAliases,
1✔
678
                                        },
1✔
679
                                },
1✔
680
                        },
1✔
681
                        utils.GotrueId,
1✔
682
                ); err != nil {
1✔
683
                        return err
×
684
                }
×
685
                started = append(started, utils.GotrueId)
1✔
686
        }
687

688
        // Start Inbucket.
689
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
690
                inbucketPortBindings := nat.PortMap{"9000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
691
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
692
                        inbucketPortBindings["2500/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
693
                }
×
694
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
695
                        inbucketPortBindings["1100/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
696
                }
×
697
                if _, err := utils.DockerStart(
1✔
698
                        ctx,
1✔
699
                        container.Config{
1✔
700
                                Image: utils.Config.Inbucket.Image,
1✔
701
                        },
1✔
702
                        container.HostConfig{
1✔
703
                                Binds: []string{
1✔
704
                                        // Override default mount points to avoid creating multiple anonymous volumes
1✔
705
                                        // Ref: https://github.com/inbucket/inbucket/blob/v3.0.4/Dockerfile#L52
1✔
706
                                        utils.InbucketId + ":/config",
1✔
707
                                        utils.InbucketId + ":/storage",
1✔
708
                                },
1✔
709
                                PortBindings:  inbucketPortBindings,
1✔
710
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
711
                        },
1✔
712
                        network.NetworkingConfig{
1✔
713
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
714
                                        utils.NetId: {
1✔
715
                                                Aliases: utils.InbucketAliases,
1✔
716
                                        },
1✔
717
                                },
1✔
718
                        },
1✔
719
                        utils.InbucketId,
1✔
720
                ); err != nil {
1✔
721
                        return err
×
722
                }
×
723
                started = append(started, utils.InbucketId)
1✔
724
        }
725

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

782
        // Start PostgREST.
783
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
784
                if _, err := utils.DockerStart(
1✔
785
                        ctx,
1✔
786
                        container.Config{
1✔
787
                                Image: utils.Config.Api.Image,
1✔
788
                                Env: []string{
1✔
789
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
790
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
791
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
792
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
793
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
794
                                        fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
1✔
795
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
796
                                },
1✔
797
                                // PostgREST does not expose a shell for health check
1✔
798
                        },
1✔
799
                        container.HostConfig{
1✔
800
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
801
                        },
1✔
802
                        network.NetworkingConfig{
1✔
803
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
804
                                        utils.NetId: {
1✔
805
                                                Aliases: utils.RestAliases,
1✔
806
                                        },
1✔
807
                                },
1✔
808
                        },
1✔
809
                        utils.RestId,
1✔
810
                ); err != nil {
1✔
811
                        return err
×
812
                }
×
813
                started = append(started, utils.RestId)
1✔
814
        }
815

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

874
        // Start Storage ImgProxy.
875
        if isStorageEnabled && utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImageTransformation.Image, excluded) {
3✔
876
                if _, err := utils.DockerStart(
1✔
877
                        ctx,
1✔
878
                        container.Config{
1✔
879
                                Image: utils.Config.Storage.ImageTransformation.Image,
1✔
880
                                Env: []string{
1✔
881
                                        "IMGPROXY_BIND=:5001",
1✔
882
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
1✔
883
                                        "IMGPROXY_USE_ETAG=/",
1✔
884
                                },
1✔
885
                                Healthcheck: &container.HealthConfig{
1✔
886
                                        Test:     []string{"CMD", "imgproxy", "health"},
1✔
887
                                        Interval: 10 * time.Second,
1✔
888
                                        Timeout:  2 * time.Second,
1✔
889
                                        Retries:  3,
1✔
890
                                },
1✔
891
                        },
1✔
892
                        container.HostConfig{
1✔
893
                                VolumesFrom:   []string{utils.StorageId},
1✔
894
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
895
                        },
1✔
896
                        network.NetworkingConfig{
1✔
897
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
898
                                        utils.NetId: {
1✔
899
                                                Aliases: utils.ImgProxyAliases,
1✔
900
                                        },
1✔
901
                                },
1✔
902
                        },
1✔
903
                        utils.ImgProxyId,
1✔
904
                ); err != nil {
1✔
905
                        return err
×
906
                }
×
907
                started = append(started, utils.ImgProxyId)
1✔
908
        }
909

910
        // Start all functions.
911
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.Config.EdgeRuntime.Image, excluded) {
3✔
912
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
913
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, fsys); err != nil {
1✔
914
                        return err
×
915
                }
×
916
                started = append(started, utils.EdgeRuntimeId)
1✔
917
        }
918

919
        // Start pg-meta.
920
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
921
                if _, err := utils.DockerStart(
1✔
922
                        ctx,
1✔
923
                        container.Config{
1✔
924
                                Image: utils.Config.Studio.PgmetaImage,
1✔
925
                                Env: []string{
1✔
926
                                        "PG_META_PORT=8080",
1✔
927
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
928
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
929
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
930
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
931
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
932
                                },
1✔
933
                                Healthcheck: &container.HealthConfig{
1✔
934
                                        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✔
935
                                        Interval: 10 * time.Second,
1✔
936
                                        Timeout:  2 * time.Second,
1✔
937
                                        Retries:  3,
1✔
938
                                },
1✔
939
                        },
1✔
940
                        container.HostConfig{
1✔
941
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
942
                        },
1✔
943
                        network.NetworkingConfig{
1✔
944
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
945
                                        utils.NetId: {
1✔
946
                                                Aliases: utils.PgmetaAliases,
1✔
947
                                        },
1✔
948
                                },
1✔
949
                        },
1✔
950
                        utils.PgmetaId,
1✔
951
                ); err != nil {
1✔
952
                        return err
×
953
                }
×
954
                started = append(started, utils.PgmetaId)
1✔
955
        }
956

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

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

1080
        p.Send(utils.StatusMsg("Waiting for health checks..."))
2✔
1081
        if utils.NoBackupVolume && utils.SliceContains(started, utils.StorageId) {
3✔
1082
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1083
                        return err
×
1084
                }
×
1085
                // Disable prompts when seeding
1086
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
1087
                        return err
×
1088
                }
×
1089
        }
1090
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1091
}
1092

1093
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
23✔
1094
        short := utils.ShortContainerImageName(imageName)
23✔
1095
        val, ok := excluded[short]
23✔
1096
        return ok && val
23✔
1097
}
23✔
1098

1099
func ExcludableContainers() []string {
2✔
1100
        names := []string{}
2✔
1101
        for _, image := range config.ServiceImages {
28✔
1102
                names = append(names, utils.ShortContainerImageName(image))
26✔
1103
        }
26✔
1104
        return names
2✔
1105
}
1106

1107
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1108
        numOfKeyPairs := len(input)
5✔
1109
        i := 0
5✔
1110
        for k, v := range input {
15✔
1111
                output.WriteString(k)
10✔
1112
                output.WriteString(":")
10✔
1113
                output.WriteString(v)
10✔
1114
                i++
10✔
1115
                if i < numOfKeyPairs {
16✔
1116
                        output.WriteString(",")
6✔
1117
                }
6✔
1118
        }
1119
}
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