detached 3daa036

feat(config): unified [auto] section with preview config and backward compat

Add preview fields to Config struct and load them from viper under the new
[auto.preview] TOML section. Backward compatibility maintained: auto_route.enabled
and auto_route_allowed_domains old keys are still read as fallback. Add
GetPreviewConfig, IsPreviewEnabled, IsAutoEnabled, GetPreviewTagPatterns, and
GetAllowedDomains methods to ConfigService interface with implementations and
updated mockery mock.
commit
3daa0361db832af5deb35347f3b75aefbed37f53
author
brice · · 2026-03-19 04:53 UTC
committer
brice · · 2026-03-19 05:13 UTC
parent
07aac7d
signature
unsigned
3 files · +314 -4
M internal/boundaries/in/config.go +7 -0
@@ -84,4 +84,11 @@ type ConfigService interface {
8484 GetAutoRouteAllowedDomains(ctx context.Context) ([]string, error)
8585 AddAutoRouteAllowedDomain(ctx context.Context, pattern string) error
8686 RemoveAutoRouteAllowedDomain(ctx context.Context, pattern string) error
87+
88+ // Preview and auto config
89+ GetPreviewConfig() domain.PreviewConfig
90+ IsPreviewEnabled() bool
91+ IsAutoEnabled() bool
92+ GetPreviewTagPatterns() []string
93+ GetAllowedDomains() []string
8794 }
@@ -84,4 +84,11 @@ type ConfigService interface {
84 GetAutoRouteAllowedDomains(ctx context.Context) ([]string, error)84 GetAutoRouteAllowedDomains(ctx context.Context) ([]string, error)
85 AddAutoRouteAllowedDomain(ctx context.Context, pattern string) error85 AddAutoRouteAllowedDomain(ctx context.Context, pattern string) error
86 RemoveAutoRouteAllowedDomain(ctx context.Context, pattern string) error86 RemoveAutoRouteAllowedDomain(ctx context.Context, pattern string) error
87+
88+ // Preview and auto config
89+ GetPreviewConfig() domain.PreviewConfig
90+ IsPreviewEnabled() bool
91+ IsAutoEnabled() bool
92+ GetPreviewTagPatterns() []string
93+ GetAllowedDomains() []string
87 }94 }
M internal/boundaries/in/mocks/mock_config_service.go +224 -0
@@ -1378,6 +1378,230 @@ func (_c *MockConfigService_UpdateRoute_Call) RunAndReturn(run func(ctx context.
13781378 return _c
13791379 }
13801380
1381+// GetPreviewConfig provides a mock function for the type MockConfigService
1382+func (_mock *MockConfigService) GetPreviewConfig() domain.PreviewConfig {
1383+ ret := _mock.Called()
1384+
1385+ if len(ret) == 0 {
1386+ panic("no return value specified for GetPreviewConfig")
1387+ }
1388+
1389+ var r0 domain.PreviewConfig
1390+ if returnFunc, ok := ret.Get(0).(func() domain.PreviewConfig); ok {
1391+ r0 = returnFunc()
1392+ } else {
1393+ r0 = ret.Get(0).(domain.PreviewConfig)
1394+ }
1395+ return r0
1396+}
1397+
1398+// MockConfigService_GetPreviewConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviewConfig'
1399+type MockConfigService_GetPreviewConfig_Call struct {
1400+ *mock.Call
1401+}
1402+
1403+// GetPreviewConfig is a helper method to define mock.On call
1404+func (_e *MockConfigService_Expecter) GetPreviewConfig() *MockConfigService_GetPreviewConfig_Call {
1405+ return &MockConfigService_GetPreviewConfig_Call{Call: _e.mock.On("GetPreviewConfig")}
1406+}
1407+
1408+func (_c *MockConfigService_GetPreviewConfig_Call) Run(run func()) *MockConfigService_GetPreviewConfig_Call {
1409+ _c.Call.Run(func(args mock.Arguments) {
1410+ run()
1411+ })
1412+ return _c
1413+}
1414+
1415+func (_c *MockConfigService_GetPreviewConfig_Call) Return(cfg domain.PreviewConfig) *MockConfigService_GetPreviewConfig_Call {
1416+ _c.Call.Return(cfg)
1417+ return _c
1418+}
1419+
1420+func (_c *MockConfigService_GetPreviewConfig_Call) RunAndReturn(run func() domain.PreviewConfig) *MockConfigService_GetPreviewConfig_Call {
1421+ _c.Call.Return(run)
1422+ return _c
1423+}
1424+
1425+// IsPreviewEnabled provides a mock function for the type MockConfigService
1426+func (_mock *MockConfigService) IsPreviewEnabled() bool {
1427+ ret := _mock.Called()
1428+
1429+ if len(ret) == 0 {
1430+ panic("no return value specified for IsPreviewEnabled")
1431+ }
1432+
1433+ var r0 bool
1434+ if returnFunc, ok := ret.Get(0).(func() bool); ok {
1435+ r0 = returnFunc()
1436+ } else {
1437+ r0 = ret.Get(0).(bool)
1438+ }
1439+ return r0
1440+}
1441+
1442+// MockConfigService_IsPreviewEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsPreviewEnabled'
1443+type MockConfigService_IsPreviewEnabled_Call struct {
1444+ *mock.Call
1445+}
1446+
1447+// IsPreviewEnabled is a helper method to define mock.On call
1448+func (_e *MockConfigService_Expecter) IsPreviewEnabled() *MockConfigService_IsPreviewEnabled_Call {
1449+ return &MockConfigService_IsPreviewEnabled_Call{Call: _e.mock.On("IsPreviewEnabled")}
1450+}
1451+
1452+func (_c *MockConfigService_IsPreviewEnabled_Call) Run(run func()) *MockConfigService_IsPreviewEnabled_Call {
1453+ _c.Call.Run(func(args mock.Arguments) {
1454+ run()
1455+ })
1456+ return _c
1457+}
1458+
1459+func (_c *MockConfigService_IsPreviewEnabled_Call) Return(b bool) *MockConfigService_IsPreviewEnabled_Call {
1460+ _c.Call.Return(b)
1461+ return _c
1462+}
1463+
1464+func (_c *MockConfigService_IsPreviewEnabled_Call) RunAndReturn(run func() bool) *MockConfigService_IsPreviewEnabled_Call {
1465+ _c.Call.Return(run)
1466+ return _c
1467+}
1468+
1469+// IsAutoEnabled provides a mock function for the type MockConfigService
1470+func (_mock *MockConfigService) IsAutoEnabled() bool {
1471+ ret := _mock.Called()
1472+
1473+ if len(ret) == 0 {
1474+ panic("no return value specified for IsAutoEnabled")
1475+ }
1476+
1477+ var r0 bool
1478+ if returnFunc, ok := ret.Get(0).(func() bool); ok {
1479+ r0 = returnFunc()
1480+ } else {
1481+ r0 = ret.Get(0).(bool)
1482+ }
1483+ return r0
1484+}
1485+
1486+// MockConfigService_IsAutoEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAutoEnabled'
1487+type MockConfigService_IsAutoEnabled_Call struct {
1488+ *mock.Call
1489+}
1490+
1491+// IsAutoEnabled is a helper method to define mock.On call
1492+func (_e *MockConfigService_Expecter) IsAutoEnabled() *MockConfigService_IsAutoEnabled_Call {
1493+ return &MockConfigService_IsAutoEnabled_Call{Call: _e.mock.On("IsAutoEnabled")}
1494+}
1495+
1496+func (_c *MockConfigService_IsAutoEnabled_Call) Run(run func()) *MockConfigService_IsAutoEnabled_Call {
1497+ _c.Call.Run(func(args mock.Arguments) {
1498+ run()
1499+ })
1500+ return _c
1501+}
1502+
1503+func (_c *MockConfigService_IsAutoEnabled_Call) Return(b bool) *MockConfigService_IsAutoEnabled_Call {
1504+ _c.Call.Return(b)
1505+ return _c
1506+}
1507+
1508+func (_c *MockConfigService_IsAutoEnabled_Call) RunAndReturn(run func() bool) *MockConfigService_IsAutoEnabled_Call {
1509+ _c.Call.Return(run)
1510+ return _c
1511+}
1512+
1513+// GetPreviewTagPatterns provides a mock function for the type MockConfigService
1514+func (_mock *MockConfigService) GetPreviewTagPatterns() []string {
1515+ ret := _mock.Called()
1516+
1517+ if len(ret) == 0 {
1518+ panic("no return value specified for GetPreviewTagPatterns")
1519+ }
1520+
1521+ var r0 []string
1522+ if returnFunc, ok := ret.Get(0).(func() []string); ok {
1523+ r0 = returnFunc()
1524+ } else {
1525+ if ret.Get(0) != nil {
1526+ r0 = ret.Get(0).([]string)
1527+ }
1528+ }
1529+ return r0
1530+}
1531+
1532+// MockConfigService_GetPreviewTagPatterns_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviewTagPatterns'
1533+type MockConfigService_GetPreviewTagPatterns_Call struct {
1534+ *mock.Call
1535+}
1536+
1537+// GetPreviewTagPatterns is a helper method to define mock.On call
1538+func (_e *MockConfigService_Expecter) GetPreviewTagPatterns() *MockConfigService_GetPreviewTagPatterns_Call {
1539+ return &MockConfigService_GetPreviewTagPatterns_Call{Call: _e.mock.On("GetPreviewTagPatterns")}
1540+}
1541+
1542+func (_c *MockConfigService_GetPreviewTagPatterns_Call) Run(run func()) *MockConfigService_GetPreviewTagPatterns_Call {
1543+ _c.Call.Run(func(args mock.Arguments) {
1544+ run()
1545+ })
1546+ return _c
1547+}
1548+
1549+func (_c *MockConfigService_GetPreviewTagPatterns_Call) Return(patterns []string) *MockConfigService_GetPreviewTagPatterns_Call {
1550+ _c.Call.Return(patterns)
1551+ return _c
1552+}
1553+
1554+func (_c *MockConfigService_GetPreviewTagPatterns_Call) RunAndReturn(run func() []string) *MockConfigService_GetPreviewTagPatterns_Call {
1555+ _c.Call.Return(run)
1556+ return _c
1557+}
1558+
1559+// GetAllowedDomains provides a mock function for the type MockConfigService
1560+func (_mock *MockConfigService) GetAllowedDomains() []string {
1561+ ret := _mock.Called()
1562+
1563+ if len(ret) == 0 {
1564+ panic("no return value specified for GetAllowedDomains")
1565+ }
1566+
1567+ var r0 []string
1568+ if returnFunc, ok := ret.Get(0).(func() []string); ok {
1569+ r0 = returnFunc()
1570+ } else {
1571+ if ret.Get(0) != nil {
1572+ r0 = ret.Get(0).([]string)
1573+ }
1574+ }
1575+ return r0
1576+}
1577+
1578+// MockConfigService_GetAllowedDomains_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllowedDomains'
1579+type MockConfigService_GetAllowedDomains_Call struct {
1580+ *mock.Call
1581+}
1582+
1583+// GetAllowedDomains is a helper method to define mock.On call
1584+func (_e *MockConfigService_Expecter) GetAllowedDomains() *MockConfigService_GetAllowedDomains_Call {
1585+ return &MockConfigService_GetAllowedDomains_Call{Call: _e.mock.On("GetAllowedDomains")}
1586+}
1587+
1588+func (_c *MockConfigService_GetAllowedDomains_Call) Run(run func()) *MockConfigService_GetAllowedDomains_Call {
1589+ _c.Call.Run(func(args mock.Arguments) {
1590+ run()
1591+ })
1592+ return _c
1593+}
1594+
1595+func (_c *MockConfigService_GetAllowedDomains_Call) Return(domains []string) *MockConfigService_GetAllowedDomains_Call {
1596+ _c.Call.Return(domains)
1597+ return _c
1598+}
1599+
1600+func (_c *MockConfigService_GetAllowedDomains_Call) RunAndReturn(run func() []string) *MockConfigService_GetAllowedDomains_Call {
1601+ _c.Call.Return(run)
1602+ return _c
1603+}
1604+
13811605 // Watch provides a mock function for the type MockConfigService
13821606 func (_mock *MockConfigService) Watch(ctx context.Context, onChange func()) error {
13831607 ret := _mock.Called(ctx, onChange)
@@ -1378,6 +1378,230 @@ func (_c *MockConfigService_UpdateRoute_Call) RunAndReturn(run func(ctx context.
1378 return _c1378 return _c
1379 }1379 }
1380 1380
1381+// GetPreviewConfig provides a mock function for the type MockConfigService
1382+func (_mock *MockConfigService) GetPreviewConfig() domain.PreviewConfig {
1383+ ret := _mock.Called()
1384+
1385+ if len(ret) == 0 {
1386+ panic("no return value specified for GetPreviewConfig")
1387+ }
1388+
1389+ var r0 domain.PreviewConfig
1390+ if returnFunc, ok := ret.Get(0).(func() domain.PreviewConfig); ok {
1391+ r0 = returnFunc()
1392+ } else {
1393+ r0 = ret.Get(0).(domain.PreviewConfig)
1394+ }
1395+ return r0
1396+}
1397+
1398+// MockConfigService_GetPreviewConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviewConfig'
1399+type MockConfigService_GetPreviewConfig_Call struct {
1400+ *mock.Call
1401+}
1402+
1403+// GetPreviewConfig is a helper method to define mock.On call
1404+func (_e *MockConfigService_Expecter) GetPreviewConfig() *MockConfigService_GetPreviewConfig_Call {
1405+ return &MockConfigService_GetPreviewConfig_Call{Call: _e.mock.On("GetPreviewConfig")}
1406+}
1407+
1408+func (_c *MockConfigService_GetPreviewConfig_Call) Run(run func()) *MockConfigService_GetPreviewConfig_Call {
1409+ _c.Call.Run(func(args mock.Arguments) {
1410+ run()
1411+ })
1412+ return _c
1413+}
1414+
1415+func (_c *MockConfigService_GetPreviewConfig_Call) Return(cfg domain.PreviewConfig) *MockConfigService_GetPreviewConfig_Call {
1416+ _c.Call.Return(cfg)
1417+ return _c
1418+}
1419+
1420+func (_c *MockConfigService_GetPreviewConfig_Call) RunAndReturn(run func() domain.PreviewConfig) *MockConfigService_GetPreviewConfig_Call {
1421+ _c.Call.Return(run)
1422+ return _c
1423+}
1424+
1425+// IsPreviewEnabled provides a mock function for the type MockConfigService
1426+func (_mock *MockConfigService) IsPreviewEnabled() bool {
1427+ ret := _mock.Called()
1428+
1429+ if len(ret) == 0 {
1430+ panic("no return value specified for IsPreviewEnabled")
1431+ }
1432+
1433+ var r0 bool
1434+ if returnFunc, ok := ret.Get(0).(func() bool); ok {
1435+ r0 = returnFunc()
1436+ } else {
1437+ r0 = ret.Get(0).(bool)
1438+ }
1439+ return r0
1440+}
1441+
1442+// MockConfigService_IsPreviewEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsPreviewEnabled'
1443+type MockConfigService_IsPreviewEnabled_Call struct {
1444+ *mock.Call
1445+}
1446+
1447+// IsPreviewEnabled is a helper method to define mock.On call
1448+func (_e *MockConfigService_Expecter) IsPreviewEnabled() *MockConfigService_IsPreviewEnabled_Call {
1449+ return &MockConfigService_IsPreviewEnabled_Call{Call: _e.mock.On("IsPreviewEnabled")}
1450+}
1451+
1452+func (_c *MockConfigService_IsPreviewEnabled_Call) Run(run func()) *MockConfigService_IsPreviewEnabled_Call {
1453+ _c.Call.Run(func(args mock.Arguments) {
1454+ run()
1455+ })
1456+ return _c
1457+}
1458+
1459+func (_c *MockConfigService_IsPreviewEnabled_Call) Return(b bool) *MockConfigService_IsPreviewEnabled_Call {
1460+ _c.Call.Return(b)
1461+ return _c
1462+}
1463+
1464+func (_c *MockConfigService_IsPreviewEnabled_Call) RunAndReturn(run func() bool) *MockConfigService_IsPreviewEnabled_Call {
1465+ _c.Call.Return(run)
1466+ return _c
1467+}
1468+
1469+// IsAutoEnabled provides a mock function for the type MockConfigService
1470+func (_mock *MockConfigService) IsAutoEnabled() bool {
1471+ ret := _mock.Called()
1472+
1473+ if len(ret) == 0 {
1474+ panic("no return value specified for IsAutoEnabled")
1475+ }
1476+
1477+ var r0 bool
1478+ if returnFunc, ok := ret.Get(0).(func() bool); ok {
1479+ r0 = returnFunc()
1480+ } else {
1481+ r0 = ret.Get(0).(bool)
1482+ }
1483+ return r0
1484+}
1485+
1486+// MockConfigService_IsAutoEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAutoEnabled'
1487+type MockConfigService_IsAutoEnabled_Call struct {
1488+ *mock.Call
1489+}
1490+
1491+// IsAutoEnabled is a helper method to define mock.On call
1492+func (_e *MockConfigService_Expecter) IsAutoEnabled() *MockConfigService_IsAutoEnabled_Call {
1493+ return &MockConfigService_IsAutoEnabled_Call{Call: _e.mock.On("IsAutoEnabled")}
1494+}
1495+
1496+func (_c *MockConfigService_IsAutoEnabled_Call) Run(run func()) *MockConfigService_IsAutoEnabled_Call {
1497+ _c.Call.Run(func(args mock.Arguments) {
1498+ run()
1499+ })
1500+ return _c
1501+}
1502+
1503+func (_c *MockConfigService_IsAutoEnabled_Call) Return(b bool) *MockConfigService_IsAutoEnabled_Call {
1504+ _c.Call.Return(b)
1505+ return _c
1506+}
1507+
1508+func (_c *MockConfigService_IsAutoEnabled_Call) RunAndReturn(run func() bool) *MockConfigService_IsAutoEnabled_Call {
1509+ _c.Call.Return(run)
1510+ return _c
1511+}
1512+
1513+// GetPreviewTagPatterns provides a mock function for the type MockConfigService
1514+func (_mock *MockConfigService) GetPreviewTagPatterns() []string {
1515+ ret := _mock.Called()
1516+
1517+ if len(ret) == 0 {
1518+ panic("no return value specified for GetPreviewTagPatterns")
1519+ }
1520+
1521+ var r0 []string
1522+ if returnFunc, ok := ret.Get(0).(func() []string); ok {
1523+ r0 = returnFunc()
1524+ } else {
1525+ if ret.Get(0) != nil {
1526+ r0 = ret.Get(0).([]string)
1527+ }
1528+ }
1529+ return r0
1530+}
1531+
1532+// MockConfigService_GetPreviewTagPatterns_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviewTagPatterns'
1533+type MockConfigService_GetPreviewTagPatterns_Call struct {
1534+ *mock.Call
1535+}
1536+
1537+// GetPreviewTagPatterns is a helper method to define mock.On call
1538+func (_e *MockConfigService_Expecter) GetPreviewTagPatterns() *MockConfigService_GetPreviewTagPatterns_Call {
1539+ return &MockConfigService_GetPreviewTagPatterns_Call{Call: _e.mock.On("GetPreviewTagPatterns")}
1540+}
1541+
1542+func (_c *MockConfigService_GetPreviewTagPatterns_Call) Run(run func()) *MockConfigService_GetPreviewTagPatterns_Call {
1543+ _c.Call.Run(func(args mock.Arguments) {
1544+ run()
1545+ })
1546+ return _c
1547+}
1548+
1549+func (_c *MockConfigService_GetPreviewTagPatterns_Call) Return(patterns []string) *MockConfigService_GetPreviewTagPatterns_Call {
1550+ _c.Call.Return(patterns)
1551+ return _c
1552+}
1553+
1554+func (_c *MockConfigService_GetPreviewTagPatterns_Call) RunAndReturn(run func() []string) *MockConfigService_GetPreviewTagPatterns_Call {
1555+ _c.Call.Return(run)
1556+ return _c
1557+}
1558+
1559+// GetAllowedDomains provides a mock function for the type MockConfigService
1560+func (_mock *MockConfigService) GetAllowedDomains() []string {
1561+ ret := _mock.Called()
1562+
1563+ if len(ret) == 0 {
1564+ panic("no return value specified for GetAllowedDomains")
1565+ }
1566+
1567+ var r0 []string
1568+ if returnFunc, ok := ret.Get(0).(func() []string); ok {
1569+ r0 = returnFunc()
1570+ } else {
1571+ if ret.Get(0) != nil {
1572+ r0 = ret.Get(0).([]string)
1573+ }
1574+ }
1575+ return r0
1576+}
1577+
1578+// MockConfigService_GetAllowedDomains_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllowedDomains'
1579+type MockConfigService_GetAllowedDomains_Call struct {
1580+ *mock.Call
1581+}
1582+
1583+// GetAllowedDomains is a helper method to define mock.On call
1584+func (_e *MockConfigService_Expecter) GetAllowedDomains() *MockConfigService_GetAllowedDomains_Call {
1585+ return &MockConfigService_GetAllowedDomains_Call{Call: _e.mock.On("GetAllowedDomains")}
1586+}
1587+
1588+func (_c *MockConfigService_GetAllowedDomains_Call) Run(run func()) *MockConfigService_GetAllowedDomains_Call {
1589+ _c.Call.Run(func(args mock.Arguments) {
1590+ run()
1591+ })
1592+ return _c
1593+}
1594+
1595+func (_c *MockConfigService_GetAllowedDomains_Call) Return(domains []string) *MockConfigService_GetAllowedDomains_Call {
1596+ _c.Call.Return(domains)
1597+ return _c
1598+}
1599+
1600+func (_c *MockConfigService_GetAllowedDomains_Call) RunAndReturn(run func() []string) *MockConfigService_GetAllowedDomains_Call {
1601+ _c.Call.Return(run)
1602+ return _c
1603+}
1604+
1381 // Watch provides a mock function for the type MockConfigService1605 // Watch provides a mock function for the type MockConfigService
1382 func (_mock *MockConfigService) Watch(ctx context.Context, onChange func()) error {1606 func (_mock *MockConfigService) Watch(ctx context.Context, onChange func()) error {
1383 ret := _mock.Called(ctx, onChange)1607 ret := _mock.Called(ctx, onChange)
M internal/usecase/config/service.go +83 -4
@@ -33,6 +33,11 @@ type Config struct {
3333 DataDir string
3434 AutoRouteEnabled bool
3535 AutoRouteAllowedDomains []string `mapstructure:"auto_route_allowed_domains" json:"auto_route_allowed_domains,omitempty"`
36+ PreviewEnabled bool
37+ PreviewTTL time.Duration
38+ PreviewSeparator string
39+ PreviewTagPatterns []string
40+ PreviewDataCopy bool
3641 NetworkIsolation bool
3742 NetworkPrefix string
3843 Routes map[string]string
@@ -122,13 +127,39 @@ func (s *Service) loadConfigValues() Config {
122127 registryDomain = s.viper.GetString("server.registry_domain")
123128 }
124129
130+ // Backward compat: try new keys first, fall back to old
131+ autoEnabled := s.viper.GetBool("auto.enabled")
132+ if !autoEnabled {
133+ autoEnabled = s.viper.GetBool("auto_route.enabled")
134+ }
135+ allowedDomains := s.viper.GetStringSlice("auto.allowed_domains")
136+ if len(allowedDomains) == 0 {
137+ allowedDomains = s.viper.GetStringSlice("auto_route_allowed_domains")
138+ }
139+
140+ // Preview config
141+ previewTTLStr := s.viper.GetString("auto.preview.ttl")
142+ previewTTL, _ := time.ParseDuration(previewTTLStr)
143+ if previewTTL == 0 {
144+ previewTTL = 48 * time.Hour
145+ }
146+ previewSep := s.viper.GetString("auto.preview.separator")
147+ if previewSep == "" {
148+ previewSep = "--"
149+ }
150+
125151 return Config{
126152 ServerPort: s.viper.GetInt("server.port"),
127153 RegistryPort: s.viper.GetInt("server.registry_port"),
128154 RegistryDomain: registryDomain,
129155 DataDir: s.viper.GetString("server.data_dir"),
130- AutoRouteEnabled: s.viper.GetBool("auto_route.enabled"),
131- AutoRouteAllowedDomains: append([]string{}, s.viper.GetStringSlice("auto_route_allowed_domains")...),
156+ AutoRouteEnabled: autoEnabled,
157+ AutoRouteAllowedDomains: append([]string{}, allowedDomains...),
158+ PreviewEnabled: s.viper.GetBool("auto.preview.enabled"),
159+ PreviewTTL: previewTTL,
160+ PreviewSeparator: previewSep,
161+ PreviewTagPatterns: append([]string{}, s.viper.GetStringSlice("auto.preview.tag_patterns")...),
162+ PreviewDataCopy: s.viper.GetBool("auto.preview.data_copy"),
132163 NetworkIsolation: s.viper.GetBool("network_isolation.enabled"),
133164 NetworkPrefix: s.viper.GetString("network_isolation.network_prefix"),
134165 RegistryAuthEnabled: s.viper.GetBool("auth.enabled"),
@@ -519,7 +550,7 @@ type configSnapshot struct {
519550 }
520551
521552 // mutableSections lists config sections that are intentionally modified by service operations.
522-var mutableSections = []string{"routes.", "external_routes.", "attachments.", "network_groups.", "auto_route_allowed_domains"}
553+var mutableSections = []string{"routes.", "external_routes.", "attachments.", "network_groups.", "auto_route_allowed_domains", "auto."}
523554
524555 func isMutableKey(key string) bool {
525556 for _, prefix := range mutableSections {
@@ -529,7 +560,7 @@ func isMutableKey(key string) bool {
529560 }
530561
531562 switch key {
532- case "routes", "external_routes", "attachments", "network_groups", "auto_route_allowed_domains":
563+ case "routes", "external_routes", "attachments", "network_groups", "auto_route_allowed_domains", "auto":
533564 return true
534565 }
535566
@@ -570,6 +601,7 @@ func (s *Service) writeConfigSurgical(configFile string) error {
570601 s.viper.Set("attachments", s.config.Attachments)
571602 s.viper.Set("network_groups", s.config.NetworkGroups)
572603 s.viper.Set("auto_route_allowed_domains", s.config.AutoRouteAllowedDomains)
604+ s.viper.Set("auto.allowed_domains", s.config.AutoRouteAllowedDomains)
573605 return s.viper.WriteConfigAs(configFile)
574606 }
575607 return fmt.Errorf("failed to read config file: %w", err)
@@ -589,7 +621,15 @@ func (s *Service) writeConfigSurgical(configFile string) error {
589621 config["external_routes"] = s.config.ExternalRoutes
590622 config["attachments"] = s.config.Attachments
591623 config["network_groups"] = s.config.NetworkGroups
624+ // Write to both old and new keys for backward compat
592625 config["auto_route_allowed_domains"] = s.config.AutoRouteAllowedDomains
626+ if autoSection, ok := config["auto"].(map[string]any); ok {
627+ autoSection["allowed_domains"] = s.config.AutoRouteAllowedDomains
628+ } else {
629+ config["auto"] = map[string]any{
630+ "allowed_domains": s.config.AutoRouteAllowedDomains,
631+ }
632+ }
593633
594634 out, err := toml.Marshal(config)
595635 if err != nil {
@@ -1050,6 +1090,45 @@ func (s *Service) RemoveAttachment(ctx context.Context, domainOrGroup, image str
10501090 return nil
10511091 }
10521092
1093+// GetPreviewConfig returns the preview environment configuration.
1094+func (s *Service) GetPreviewConfig() domain.PreviewConfig {
1095+ s.mu.RLock()
1096+ defer s.mu.RUnlock()
1097+ return domain.PreviewConfig{
1098+ Enabled: s.config.PreviewEnabled,
1099+ TTL: s.config.PreviewTTL,
1100+ Separator: s.config.PreviewSeparator,
1101+ TagPatterns: s.config.PreviewTagPatterns,
1102+ DataCopy: s.config.PreviewDataCopy,
1103+ }
1104+}
1105+
1106+// IsPreviewEnabled returns whether preview environments are enabled.
1107+func (s *Service) IsPreviewEnabled() bool {
1108+ s.mu.RLock()
1109+ defer s.mu.RUnlock()
1110+ return s.config.PreviewEnabled
1111+}
1112+
1113+// IsAutoEnabled returns whether the auto feature is enabled (alias for IsAutoRouteEnabled).
1114+func (s *Service) IsAutoEnabled() bool {
1115+ return s.IsAutoRouteEnabled()
1116+}
1117+
1118+// GetPreviewTagPatterns returns the configured tag patterns for preview environments.
1119+func (s *Service) GetPreviewTagPatterns() []string {
1120+ s.mu.RLock()
1121+ defer s.mu.RUnlock()
1122+ return append([]string{}, s.config.PreviewTagPatterns...)
1123+}
1124+
1125+// GetAllowedDomains returns the auto-route allowed domain patterns.
1126+func (s *Service) GetAllowedDomains() []string {
1127+ s.mu.RLock()
1128+ defer s.mu.RUnlock()
1129+ return append([]string{}, s.config.AutoRouteAllowedDomains...)
1130+}
1131+
10531132 // GetAutoRouteAllowedDomains returns the configured auto-route domain allowlist.
10541133 func (s *Service) GetAutoRouteAllowedDomains(_ context.Context) ([]string, error) {
10551134 s.mu.RLock()
@@ -33,6 +33,11 @@ type Config struct {
33 DataDir string33 DataDir string
34 AutoRouteEnabled bool34 AutoRouteEnabled bool
35 AutoRouteAllowedDomains []string `mapstructure:"auto_route_allowed_domains" json:"auto_route_allowed_domains,omitempty"`35 AutoRouteAllowedDomains []string `mapstructure:"auto_route_allowed_domains" json:"auto_route_allowed_domains,omitempty"`
36+ PreviewEnabled bool
37+ PreviewTTL time.Duration
38+ PreviewSeparator string
39+ PreviewTagPatterns []string
40+ PreviewDataCopy bool
36 NetworkIsolation bool41 NetworkIsolation bool
37 NetworkPrefix string42 NetworkPrefix string
38 Routes map[string]string43 Routes map[string]string
@@ -122,13 +127,39 @@ func (s *Service) loadConfigValues() Config {
122 registryDomain = s.viper.GetString("server.registry_domain")127 registryDomain = s.viper.GetString("server.registry_domain")
123 }128 }
124 129
130+ // Backward compat: try new keys first, fall back to old
131+ autoEnabled := s.viper.GetBool("auto.enabled")
132+ if !autoEnabled {
133+ autoEnabled = s.viper.GetBool("auto_route.enabled")
134+ }
135+ allowedDomains := s.viper.GetStringSlice("auto.allowed_domains")
136+ if len(allowedDomains) == 0 {
137+ allowedDomains = s.viper.GetStringSlice("auto_route_allowed_domains")
138+ }
139+
140+ // Preview config
141+ previewTTLStr := s.viper.GetString("auto.preview.ttl")
142+ previewTTL, _ := time.ParseDuration(previewTTLStr)
143+ if previewTTL == 0 {
144+ previewTTL = 48 * time.Hour
145+ }
146+ previewSep := s.viper.GetString("auto.preview.separator")
147+ if previewSep == "" {
148+ previewSep = "--"
149+ }
150+
125 return Config{151 return Config{
126 ServerPort: s.viper.GetInt("server.port"),152 ServerPort: s.viper.GetInt("server.port"),
127 RegistryPort: s.viper.GetInt("server.registry_port"),153 RegistryPort: s.viper.GetInt("server.registry_port"),
128 RegistryDomain: registryDomain,154 RegistryDomain: registryDomain,
129 DataDir: s.viper.GetString("server.data_dir"),155 DataDir: s.viper.GetString("server.data_dir"),
130- AutoRouteEnabled: s.viper.GetBool("auto_route.enabled"),156+ AutoRouteEnabled: autoEnabled,
131- AutoRouteAllowedDomains: append([]string{}, s.viper.GetStringSlice("auto_route_allowed_domains")...),157+ AutoRouteAllowedDomains: append([]string{}, allowedDomains...),
158+ PreviewEnabled: s.viper.GetBool("auto.preview.enabled"),
159+ PreviewTTL: previewTTL,
160+ PreviewSeparator: previewSep,
161+ PreviewTagPatterns: append([]string{}, s.viper.GetStringSlice("auto.preview.tag_patterns")...),
162+ PreviewDataCopy: s.viper.GetBool("auto.preview.data_copy"),
132 NetworkIsolation: s.viper.GetBool("network_isolation.enabled"),163 NetworkIsolation: s.viper.GetBool("network_isolation.enabled"),
133 NetworkPrefix: s.viper.GetString("network_isolation.network_prefix"),164 NetworkPrefix: s.viper.GetString("network_isolation.network_prefix"),
134 RegistryAuthEnabled: s.viper.GetBool("auth.enabled"),165 RegistryAuthEnabled: s.viper.GetBool("auth.enabled"),
@@ -519,7 +550,7 @@ type configSnapshot struct {
519 }550 }
520 551
521 // mutableSections lists config sections that are intentionally modified by service operations.552 // mutableSections lists config sections that are intentionally modified by service operations.
522-var mutableSections = []string{"routes.", "external_routes.", "attachments.", "network_groups.", "auto_route_allowed_domains"}553+var mutableSections = []string{"routes.", "external_routes.", "attachments.", "network_groups.", "auto_route_allowed_domains", "auto."}
523 554
524 func isMutableKey(key string) bool {555 func isMutableKey(key string) bool {
525 for _, prefix := range mutableSections {556 for _, prefix := range mutableSections {
@@ -529,7 +560,7 @@ func isMutableKey(key string) bool {
529 }560 }
530 561
531 switch key {562 switch key {
532- case "routes", "external_routes", "attachments", "network_groups", "auto_route_allowed_domains":563+ case "routes", "external_routes", "attachments", "network_groups", "auto_route_allowed_domains", "auto":
533 return true564 return true
534 }565 }
535 566
@@ -570,6 +601,7 @@ func (s *Service) writeConfigSurgical(configFile string) error {
570 s.viper.Set("attachments", s.config.Attachments)601 s.viper.Set("attachments", s.config.Attachments)
571 s.viper.Set("network_groups", s.config.NetworkGroups)602 s.viper.Set("network_groups", s.config.NetworkGroups)
572 s.viper.Set("auto_route_allowed_domains", s.config.AutoRouteAllowedDomains)603 s.viper.Set("auto_route_allowed_domains", s.config.AutoRouteAllowedDomains)
604+ s.viper.Set("auto.allowed_domains", s.config.AutoRouteAllowedDomains)
573 return s.viper.WriteConfigAs(configFile)605 return s.viper.WriteConfigAs(configFile)
574 }606 }
575 return fmt.Errorf("failed to read config file: %w", err)607 return fmt.Errorf("failed to read config file: %w", err)
@@ -589,7 +621,15 @@ func (s *Service) writeConfigSurgical(configFile string) error {
589 config["external_routes"] = s.config.ExternalRoutes621 config["external_routes"] = s.config.ExternalRoutes
590 config["attachments"] = s.config.Attachments622 config["attachments"] = s.config.Attachments
591 config["network_groups"] = s.config.NetworkGroups623 config["network_groups"] = s.config.NetworkGroups
624+ // Write to both old and new keys for backward compat
592 config["auto_route_allowed_domains"] = s.config.AutoRouteAllowedDomains625 config["auto_route_allowed_domains"] = s.config.AutoRouteAllowedDomains
626+ if autoSection, ok := config["auto"].(map[string]any); ok {
627+ autoSection["allowed_domains"] = s.config.AutoRouteAllowedDomains
628+ } else {
629+ config["auto"] = map[string]any{
630+ "allowed_domains": s.config.AutoRouteAllowedDomains,
631+ }
632+ }
593 633
594 out, err := toml.Marshal(config)634 out, err := toml.Marshal(config)
595 if err != nil {635 if err != nil {
@@ -1050,6 +1090,45 @@ func (s *Service) RemoveAttachment(ctx context.Context, domainOrGroup, image str
1050 return nil1090 return nil
1051 }1091 }
1052 1092
1093+// GetPreviewConfig returns the preview environment configuration.
1094+func (s *Service) GetPreviewConfig() domain.PreviewConfig {
1095+ s.mu.RLock()
1096+ defer s.mu.RUnlock()
1097+ return domain.PreviewConfig{
1098+ Enabled: s.config.PreviewEnabled,
1099+ TTL: s.config.PreviewTTL,
1100+ Separator: s.config.PreviewSeparator,
1101+ TagPatterns: s.config.PreviewTagPatterns,
1102+ DataCopy: s.config.PreviewDataCopy,
1103+ }
1104+}
1105+
1106+// IsPreviewEnabled returns whether preview environments are enabled.
1107+func (s *Service) IsPreviewEnabled() bool {
1108+ s.mu.RLock()
1109+ defer s.mu.RUnlock()
1110+ return s.config.PreviewEnabled
1111+}
1112+
1113+// IsAutoEnabled returns whether the auto feature is enabled (alias for IsAutoRouteEnabled).
1114+func (s *Service) IsAutoEnabled() bool {
1115+ return s.IsAutoRouteEnabled()
1116+}
1117+
1118+// GetPreviewTagPatterns returns the configured tag patterns for preview environments.
1119+func (s *Service) GetPreviewTagPatterns() []string {
1120+ s.mu.RLock()
1121+ defer s.mu.RUnlock()
1122+ return append([]string{}, s.config.PreviewTagPatterns...)
1123+}
1124+
1125+// GetAllowedDomains returns the auto-route allowed domain patterns.
1126+func (s *Service) GetAllowedDomains() []string {
1127+ s.mu.RLock()
1128+ defer s.mu.RUnlock()
1129+ return append([]string{}, s.config.AutoRouteAllowedDomains...)
1130+}
1131+
1053 // GetAutoRouteAllowedDomains returns the configured auto-route domain allowlist.1132 // GetAutoRouteAllowedDomains returns the configured auto-route domain allowlist.
1054 func (s *Service) GetAutoRouteAllowedDomains(_ context.Context) ([]string, error) {1133 func (s *Service) GetAutoRouteAllowedDomains(_ context.Context) ([]string, error) {
1055 s.mu.RLock()1134 s.mu.RLock()

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close