Skip to content

Commit 54bed82

Browse files
committed
Improved settings, add server name in proxy headers
1 parent 5c69163 commit 54bed82

15 files changed

Lines changed: 1135 additions & 484 deletions

File tree

agent/internal/agent/drift.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (a *Agent) detectChanges(expected *agenthttp.ExpectedState, actual *ActualS
171171

172172
if a.IsProxy {
173173
expectedHttpRoutes := ConvertToHttpRoutes(expected.Traefik.HttpRoutes)
174-
expectedTraefikHash := traefik.HashRoutes(expectedHttpRoutes)
174+
expectedTraefikHash := traefik.HashRoutesWithServerName(expectedHttpRoutes, expected.ServerName)
175175
if expectedTraefikHash != actual.TraefikConfigHash {
176176
changes = append(changes, fmt.Sprintf("UPDATE Traefik HTTP (%d routes)", len(expected.Traefik.HttpRoutes)))
177177
}
@@ -228,7 +228,7 @@ func (a *Agent) hasDrift(expected *agenthttp.ExpectedState, actual *ActualState)
228228

229229
if a.IsProxy {
230230
expectedHttpRoutes := ConvertToHttpRoutes(expected.Traefik.HttpRoutes)
231-
if traefik.HashRoutes(expectedHttpRoutes) != actual.TraefikConfigHash {
231+
if traefik.HashRoutesWithServerName(expectedHttpRoutes, expected.ServerName) != actual.TraefikConfigHash {
232232
return true
233233
}
234234

@@ -435,7 +435,7 @@ func (a *Agent) reconcileOne(actual *ActualState) error {
435435
tcpRoutes := ConvertToTCPRoutes(a.expectedState.Traefik.TCPRoutes)
436436
udpRoutes := ConvertToUDPRoutes(a.expectedState.Traefik.UDPRoutes)
437437

438-
httpDrift := traefik.HashRoutes(expectedHttpRoutes) != actual.TraefikConfigHash
438+
httpDrift := traefik.HashRoutesWithServerName(expectedHttpRoutes, a.expectedState.ServerName) != actual.TraefikConfigHash
439439
expectedL4Hash := traefik.HashTCPRoutes(tcpRoutes) + traefik.HashUDPRoutes(udpRoutes)
440440
l4Drift := expectedL4Hash != actual.L4ConfigHash
441441

@@ -459,7 +459,7 @@ func (a *Agent) reconcileOne(actual *ActualState) error {
459459
}
460460

461461
log.Printf("[reconcile] updating Traefik routes (HTTP: %d, TCP: %d, UDP: %d)", len(expectedHttpRoutes), len(tcpRoutes), len(udpRoutes))
462-
if err := traefik.UpdateHttpRoutesWithL4(expectedHttpRoutes, tcpRoutes, udpRoutes); err != nil {
462+
if err := traefik.UpdateHttpRoutesWithL4(expectedHttpRoutes, tcpRoutes, udpRoutes, a.expectedState.ServerName); err != nil {
463463
return fmt.Errorf("failed to update Traefik: %w", err)
464464
}
465465

agent/internal/http/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ type WireGuardPeer struct {
126126
}
127127

128128
type ExpectedState struct {
129+
ServerName string `json:"serverName"`
129130
Containers []ExpectedContainer `json:"containers"`
130131
Dns struct {
131132
Records []DnsRecord `json:"records"`

agent/internal/traefik/l4.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ func ValidateL4Routes(tcpRoutes []TraefikTCPRoute, udpRoutes []TraefikUDPRoute)
4141
return nil
4242
}
4343

44-
func UpdateHttpRoutesWithL4(httpRoutes []TraefikRoute, tcpRoutes []TraefikTCPRoute, udpRoutes []TraefikUDPRoute) error {
44+
func UpdateHttpRoutesWithL4(httpRoutes []TraefikRoute, tcpRoutes []TraefikTCPRoute, udpRoutes []TraefikUDPRoute, serverName string) error {
4545
if err := ValidateL4Routes(tcpRoutes, udpRoutes); err != nil {
4646
return fmt.Errorf("port validation failed: %w", err)
4747
}
4848

49-
config := traefikFullConfig{
50-
HTTP: httpConfig{
51-
Routers: make(map[string]router),
52-
Services: make(map[string]service),
49+
config := traefikFullConfigWithMiddlewares{
50+
HTTP: httpConfigWithMiddlewares{
51+
Routers: make(map[string]routerWithMiddleware),
52+
Services: make(map[string]service),
53+
Middlewares: make(map[string]middleware),
5354
},
5455
TCP: tcpConfig{
5556
Routers: make(map[string]tcpRouter),
@@ -61,16 +62,29 @@ func UpdateHttpRoutesWithL4(httpRoutes []TraefikRoute, tcpRoutes []TraefikTCPRou
6162
},
6263
}
6364

65+
var middlewareNames []string
66+
if serverName != "" {
67+
config.HTTP.Middlewares["forwarded_server"] = middleware{
68+
Headers: &headersMiddleware{
69+
CustomRequestHeaders: map[string]string{
70+
"X-Forwarded-Server": serverName,
71+
},
72+
},
73+
}
74+
middlewareNames = []string{"forwarded_server@file"}
75+
}
76+
6477
for _, route := range httpRoutes {
6578
if len(route.Upstreams) == 0 {
6679
continue
6780
}
6881

69-
config.HTTP.Routers[route.ServiceId] = router{
82+
config.HTTP.Routers[route.ServiceId] = routerWithMiddleware{
7083
Rule: fmt.Sprintf("Host(`%s`)", route.Domain),
7184
EntryPoints: []string{"websecure"},
7285
Service: route.ServiceId,
7386
TLS: &tlsConfig{},
87+
Middlewares: middlewareNames,
7488
}
7589

7690
servers := make([]server, len(route.Upstreams))
@@ -238,6 +252,7 @@ func GetCurrentL4ConfigHash() string {
238252
for routerName, rtr := range config.TCP.Routers {
239253
var externalPort int
240254
var serviceId string
255+
241256
fmt.Sscanf(routerName, "tcp_%s_%d", &serviceId, &externalPort)
242257

243258
for _, ep := range rtr.EntryPoints {
@@ -302,15 +317,16 @@ func GetCurrentL4ConfigHash() string {
302317
return HashTCPRoutes(tcpRoutes) + HashUDPRoutes(udpRoutes)
303318
}
304319

305-
func readCurrentFullConfig() (*traefikFullConfig, error) {
320+
func readCurrentFullConfig() (*traefikFullConfigWithMiddlewares, error) {
306321
routesPath := filepath.Join(traefikDynamicDir, routesFileName)
307322
data, err := os.ReadFile(routesPath)
308323
if err != nil {
309324
if os.IsNotExist(err) {
310-
return &traefikFullConfig{
311-
HTTP: httpConfig{
312-
Routers: make(map[string]router),
313-
Services: make(map[string]service),
325+
return &traefikFullConfigWithMiddlewares{
326+
HTTP: httpConfigWithMiddlewares{
327+
Routers: make(map[string]routerWithMiddleware),
328+
Services: make(map[string]service),
329+
Middlewares: make(map[string]middleware),
314330
},
315331
TCP: tcpConfig{
316332
Routers: make(map[string]tcpRouter),
@@ -325,17 +341,20 @@ func readCurrentFullConfig() (*traefikFullConfig, error) {
325341
return nil, err
326342
}
327343

328-
var config traefikFullConfig
344+
var config traefikFullConfigWithMiddlewares
329345
if err := yaml.Unmarshal(data, &config); err != nil {
330346
return nil, err
331347
}
332348

333349
if config.HTTP.Routers == nil {
334-
config.HTTP.Routers = make(map[string]router)
350+
config.HTTP.Routers = make(map[string]routerWithMiddleware)
335351
}
336352
if config.HTTP.Services == nil {
337353
config.HTTP.Services = make(map[string]service)
338354
}
355+
if config.HTTP.Middlewares == nil {
356+
config.HTTP.Middlewares = make(map[string]middleware)
357+
}
339358
if config.TCP.Routers == nil {
340359
config.TCP.Routers = make(map[string]tcpRouter)
341360
}

agent/internal/traefik/routes.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,14 @@ func HashRoutes(routes []TraefikRoute) string {
151151
return hex.EncodeToString(hash[:])
152152
}
153153

154+
func HashRoutesWithServerName(routes []TraefikRoute, serverName string) string {
155+
base := HashRoutes(routes)
156+
hash := sha256.Sum256([]byte(base + "|server:" + serverName))
157+
return hex.EncodeToString(hash[:])
158+
}
159+
154160
func GetCurrentConfigHash() string {
155-
config, err := readCurrentConfig()
161+
config, err := readCurrentFullConfig()
156162
if err != nil {
157163
log.Printf("[traefik:hash] failed to read config: %v", err)
158164
return ""
@@ -185,7 +191,18 @@ func GetCurrentConfigHash() string {
185191
})
186192
}
187193

188-
return HashRoutes(routes)
194+
serverName := extractForwardedServerName(config.HTTP.Middlewares)
195+
196+
return HashRoutesWithServerName(routes, serverName)
197+
}
198+
199+
func extractForwardedServerName(middlewares map[string]middleware) string {
200+
if mw, exists := middlewares["forwarded_server"]; exists && mw.Headers != nil {
201+
if value, ok := mw.Headers.CustomRequestHeaders["X-Forwarded-Server"]; ok {
202+
return value
203+
}
204+
}
205+
return ""
189206
}
190207

191208
func extractDomainFromRule(rule string) string {

agent/internal/traefik/types.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ type httpConfigWithMiddlewares struct {
114114
}
115115

116116
type routerWithMiddleware struct {
117-
Rule string `yaml:"rule"`
118-
EntryPoints []string `yaml:"entryPoints"`
119-
Service string `yaml:"service,omitempty"`
120-
Priority int `yaml:"priority,omitempty"`
121-
Middlewares []string `yaml:"middlewares,omitempty"`
117+
Rule string `yaml:"rule"`
118+
EntryPoints []string `yaml:"entryPoints"`
119+
Service string `yaml:"service,omitempty"`
120+
TLS *tlsConfig `yaml:"tls,omitempty"`
121+
Priority int `yaml:"priority,omitempty"`
122+
Middlewares []string `yaml:"middlewares,omitempty"`
122123
}
123124

124125
type challengeConfig struct {
@@ -181,6 +182,12 @@ type traefikFullConfig struct {
181182
UDP udpConfig `yaml:"udp,omitempty"`
182183
}
183184

185+
type traefikFullConfigWithMiddlewares struct {
186+
HTTP httpConfigWithMiddlewares `yaml:"http,omitempty"`
187+
TCP tcpConfig `yaml:"tcp,omitempty"`
188+
UDP udpConfig `yaml:"udp,omitempty"`
189+
}
190+
184191
type staticConfig struct {
185192
EntryPoints map[string]entryPoint `yaml:"entryPoints"`
186193
}

web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default function ArchitecturePage() {
8080
return (
8181
<div className="relative space-y-4">
8282
{service.deployments.length > 0 && (
83-
<div className="absolute top-4 left-4 z-10">
83+
<div className="fixed bottom-4 right-4 z-10 md:absolute md:bottom-auto md:top-4 md:left-4 md:right-auto">
8484
{hasRunningDeployments && (
8585
<ButtonGroup>
8686
<Button

web/app/(dashboard)/dashboard/settings/page.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import { SetBreadcrumbs } from "@/components/core/breadcrumb-data";
22
import { GlobalSettings } from "@/components/global-settings";
33
import { listServers, getGlobalSettings } from "@/db/queries";
44

5-
export default async function SettingsPage() {
6-
const [servers, settings] = await Promise.all([
5+
type Props = {
6+
searchParams: Promise<{ tab?: string }>;
7+
};
8+
9+
export default async function SettingsPage({ searchParams }: Props) {
10+
const [servers, settings, params] = await Promise.all([
711
listServers(),
812
getGlobalSettings(),
13+
searchParams,
914
]);
1015

16+
const initialTab = params.tab || "build";
17+
1118
return (
1219
<>
1320
<SetBreadcrumbs
@@ -24,7 +31,11 @@ export default async function SettingsPage() {
2431
</p>
2532
</div>
2633

27-
<GlobalSettings servers={servers} initialSettings={settings} />
34+
<GlobalSettings
35+
servers={servers}
36+
initialSettings={settings}
37+
initialTab={initialTab}
38+
/>
2839
</div>
2940
</>
3041
);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
3+
export async function GET(request: NextRequest) {
4+
const searchParams = request.nextUrl.searchParams;
5+
const code = searchParams.get("code");
6+
7+
if (!code) {
8+
return NextResponse.redirect(
9+
new URL("/dashboard/settings?github_error=missing_code", request.url),
10+
);
11+
}
12+
13+
try {
14+
const response = await fetch(
15+
`https://api.github.com/app-manifests/${code}/conversions`,
16+
{
17+
method: "POST",
18+
headers: {
19+
Accept: "application/vnd.github+json",
20+
},
21+
},
22+
);
23+
24+
if (!response.ok) {
25+
const error = await response.text();
26+
console.error("GitHub manifest conversion failed:", error);
27+
return NextResponse.redirect(
28+
new URL(
29+
"/dashboard/settings?github_error=conversion_failed",
30+
request.url,
31+
),
32+
);
33+
}
34+
35+
const data = await response.json();
36+
37+
const credentials = {
38+
id: data.id,
39+
slug: data.slug,
40+
pem: Buffer.from(data.pem).toString("base64"),
41+
webhookSecret: data.webhook_secret,
42+
ownerType: data.owner?.type,
43+
ownerLogin: data.owner?.login,
44+
};
45+
46+
const credentialsParam = encodeURIComponent(JSON.stringify(credentials));
47+
48+
return NextResponse.redirect(
49+
new URL(
50+
`/dashboard/settings?github_credentials=${credentialsParam}`,
51+
request.url,
52+
),
53+
);
54+
} catch (error) {
55+
console.error("GitHub manifest callback error:", error);
56+
return NextResponse.redirect(
57+
new URL("/dashboard/settings?github_error=unknown", request.url),
58+
);
59+
}
60+
}

web/app/api/v1/agent/expected-state/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ export async function GET(request: NextRequest) {
304304
}
305305

306306
return NextResponse.json({
307+
serverName: server.name,
307308
containers,
308309
dns: { records: dnsRecords },
309310
traefik: traefikConfig,

0 commit comments

Comments
 (0)