diff --git a/internal/machine/caddyconfig/caddyfile.go b/internal/machine/caddyconfig/caddyfile.go index 9394f801..e8b77942 100644 --- a/internal/machine/caddyconfig/caddyfile.go +++ b/internal/machine/caddyconfig/caddyfile.go @@ -12,14 +12,16 @@ import ( "strconv" "strings" "text/template" + "time" "github.com/psviderski/uncloud/internal/machine/store" "github.com/psviderski/uncloud/pkg/api" ) const ( - caddyfileHeader = `# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. + caddyfileHeaderFmt = `# Caddyfile autogenerated by Uncloud (DO NOT EDIT): %s +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview ` caddyfileTemplate = `# Health check endpoint to verify Caddy reachability on this machine. http:// { @@ -56,7 +58,9 @@ https://{{$hostname}} { }{{end}} ` caddyfileUnavailabeFooter = `# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine -# or the latest generated config is invalid. Please check the Caddy logs if it's running. +# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest +# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy) +# and its logs for more details (uc logs caddy). ` ) @@ -120,6 +124,7 @@ func (g *CaddyfileGenerator) Generate( return "", fmt.Errorf("generate base Caddyfile from service ports: %w", err) } + caddyfileHeader := fmt.Sprintf(caddyfileHeaderFmt, time.Now().UTC().Format(time.RFC3339)) if !includeCustom { return fmt.Sprintf("%s\n%s\n%s", caddyfileHeader, caddyfile, caddyfileUnavailabeFooter), nil } diff --git a/internal/machine/caddyconfig/caddyfile_test.go b/internal/machine/caddyconfig/caddyfile_test.go index 2d02993e..2f97fa72 100644 --- a/internal/machine/caddyconfig/caddyfile_test.go +++ b/internal/machine/caddyconfig/caddyfile_test.go @@ -3,6 +3,7 @@ package caddyconfig import ( "context" "errors" + "regexp" "strings" "testing" "time" @@ -17,8 +18,17 @@ import ( "github.com/stretchr/testify/require" ) -const testCaddyfileHeader = `# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. +// generatedTimestampRegex matches the "(DO NOT EDIT): " part of the header. +var generatedTimestampRegex = regexp.MustCompile(`\(DO NOT EDIT\): \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`) + +// normaliseGeneratedTimestamp replaces dynamic timestamps with a placeholder for test comparison. +func normaliseGeneratedTimestamp(caddyfile string) string { + return generatedTimestampRegex.ReplaceAllString(caddyfile, "(DO NOT EDIT): TIMESTAMP_PLACEHOLDER") +} + +const testCaddyfileHeader = `# Caddyfile autogenerated by Uncloud (DO NOT EDIT): TIMESTAMP_PLACEHOLDER +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # Health check endpoint to verify Caddy reachability on this machine. http:// { @@ -190,7 +200,7 @@ https://secure.example.com { } require.NoError(t, err) - assert.Equal(t, tt.want, config, "Generated Caddyfile doesn't match") + assert.Equal(t, tt.want, normaliseGeneratedTimestamp(config), "Generated Caddyfile doesn't match") }) } } @@ -216,8 +226,9 @@ func TestCaddyfileGeneratorWithCustomConfigs(t *testing.T) { time.Now(), ), }, - want: `# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. + want: `# Caddyfile autogenerated by Uncloud (DO NOT EDIT): TIMESTAMP_PLACEHOLDER +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # User-defined global config from service 'caddy'. # Global Caddy configuration @@ -412,8 +423,9 @@ web.example.com { time.Now(), ), }, - want: `# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. + want: `# Caddyfile autogenerated by Uncloud (DO NOT EDIT): TIMESTAMP_PLACEHOLDER +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # User-defined global config from service 'caddy'. # Global config @@ -649,8 +661,9 @@ badconfig.com { time.Now(), ), }, - want: `# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. + want: `# Caddyfile autogenerated by Uncloud (DO NOT EDIT): TIMESTAMP_PLACEHOLDER +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # User-defined global config from service 'caddy'. # Global config from test machine @@ -831,7 +844,7 @@ valid.example.com { } require.NoError(t, err) - assert.Equal(t, tt.want, config, "Generated Caddyfile doesn't match") + assert.Equal(t, tt.want, normaliseGeneratedTimestamp(config), "Generated Caddyfile doesn't match") }) } } @@ -926,7 +939,9 @@ http://api.example.com { } # NOTE: User-defined configs for services were skipped because Caddy is not running on this machine -# or the latest generated config is invalid. Please check the Caddy logs if it's running. +# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest +# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy) +# and its logs for more details (uc logs caddy). `, }, { @@ -950,7 +965,9 @@ http://api.example.com { } # NOTE: User-defined configs for services were skipped because Caddy is not running on this machine -# or the latest generated config is invalid. Please check the Caddy logs if it's running. +# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest +# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy) +# and its logs for more details (uc logs caddy). `, }, } @@ -964,7 +981,7 @@ http://api.example.com { config, err := generator.Generate(ctx, tt.containers, false) require.NoError(t, err) - assert.Equal(t, tt.want, config, "Generated Caddyfile doesn't match") + assert.Equal(t, tt.want, normaliseGeneratedTimestamp(config), "Generated Caddyfile doesn't match") }) } } diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go index fc1a6dd9..341e553f 100644 --- a/test/e2e/service_test.go +++ b/test/e2e/service_test.go @@ -293,7 +293,7 @@ func TestDeployment(t *testing.T) { config, err := cli.Caddy.GetConfig(ctx, nil) require.NoError(t, err) - assert.Contains(t, config.Caddyfile, "# This file is autogenerated by Uncloud") + assert.Contains(t, config.Caddyfile, "# Caddyfile autogenerated by Uncloud") assert.Contains(t, config.Caddyfile, "handle /.uncloud-verify") }) @@ -423,7 +423,7 @@ myapp.example.com { }, 5*time.Second, 100*time.Millisecond, "Expected both custom configs to be included in the Caddyfile") - assert.Contains(t, config.Caddyfile, "# This file is autogenerated by Uncloud") + assert.Contains(t, config.Caddyfile, "# Caddyfile autogenerated by Uncloud") assert.Contains(t, config.Caddyfile, "handle /.uncloud-verify") assert.Contains(t, config.Caddyfile, caddyCaddyfile, "Expected user-defined global Caddy config to be included in the Caddyfile") diff --git a/website/docs/3-concepts/1-ingress/1-overview.md b/website/docs/3-concepts/1-ingress/1-overview.md index 669ce61e..50189a57 100644 --- a/website/docs/3-concepts/1-ingress/1-overview.md +++ b/website/docs/3-concepts/1-ingress/1-overview.md @@ -10,7 +10,7 @@ with [Let's Encrypt](https://letsencrypt.org/), and route requests to your servi ## How it works By default, Caddy runs as a global service `caddy` on every machine in your cluster, listening on the host ports 80 -(HTTP) and 443 (HTTPS). +(HTTP), 443 (HTTPS), and 443/UDP (HTTP/3). It's deployed during cluster initialisation (`uc machine init`) unless you use the `--no-caddy` flag. See [Managing Caddy](3-managing-caddy.md) for deployment and customisation instructions. diff --git a/website/docs/3-concepts/1-ingress/2-publishing-services.md b/website/docs/3-concepts/1-ingress/2-publishing-services.md index d408989f..267cc431 100644 --- a/website/docs/3-concepts/1-ingress/2-publishing-services.md +++ b/website/docs/3-concepts/1-ingress/2-publishing-services.md @@ -197,8 +197,9 @@ debugging and verifying your `x-caddy` configs. Example output: ```caddyfile -# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. +# Caddyfile autogenerated by Uncloud (DO NOT EDIT): 2025-12-20T22:43:56Z +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # User-defined global config from service 'caddy'. *.example.com { diff --git a/website/docs/3-concepts/1-ingress/3-managing-caddy.md b/website/docs/3-concepts/1-ingress/3-managing-caddy.md index e12b9a35..ebfc793f 100644 --- a/website/docs/3-concepts/1-ingress/3-managing-caddy.md +++ b/website/docs/3-concepts/1-ingress/3-managing-caddy.md @@ -94,6 +94,7 @@ services: x-ports: - 80:80@host - 443:443@host + - 443:443/udp@host x-caddy: Caddyfile deploy: mode: global @@ -138,7 +139,8 @@ internal.example.com { :::info note The specified `command`, `environment`, `volumes`, and `x-ports` properties are essential for Caddy to function -correctly in the Uncloud cluster. +correctly in the Uncloud cluster. Do not change the source paths of the volume mounts as the Uncloud daemon relies on +them to communicate with Caddy and update its configuration. ::: @@ -160,8 +162,9 @@ uc caddy config Example output: ```caddyfile -# This file is autogenerated by Uncloud based on the configuration of running services. -# Do not edit manually. Any manual changes will be overwritten on the next update. +# Caddyfile autogenerated by Uncloud (DO NOT EDIT): 2025-12-22T10:30:12Z +# Automatically updated on service or health status changes. +# Docs: https://uncloud.run/docs/concepts/ingress/overview # User-defined global config from service 'caddy'. # Global options.