Repository: nektos/act (language: go)

Issue:
@actions/cache with external cache server connecting wrong IP (0.0.0.0)
### Bug report info

```plain text
no
```

### Command used with act

```sh
no
```

### Describe issue

This issue is from https://gitea.com/gitea/act_runner/issues/437. You can check the detail there.
It seems no one has reported it....

### Link to GitHub repository

_No response_

### Workflow content

```yml
no
```

### Relevant log output

```sh
no
```

### Additional information

_No response_
@actions/cache with external cache server connecting wrong IP (0.0.0.0)
### Bug report info

```plain text
no
```

### Command used with act

```sh
no
```

### Describe issue

This issue is from https://gitea.com/gitea/act_runner/issues/437. You can check the detail there.
It seems no one has reported it....

### Link to GitHub repository

_No response_

### Workflow content

```yml
no
```

### Relevant log output

```sh
no
```

### Additional information

_No response_


Discussion / hints:
There is already a [pull request](https://gitea.com/gitea/act/pulls/111/files) from the downstream, so maybe we can move it here.

There is already a [pull request](https://gitea.com/gitea/act/pulls/111/files) from the downstream, so maybe we can move it here.


Fix patch:
diff --git a/cmd/input.go b/cmd/input.go
index 74b5653b969..a7ccae84eac 100644
--- a/cmd/input.go
+++ b/cmd/input.go
@@ -46,6 +46,7 @@ type Input struct {
 	artifactServerPort                 string
 	noCacheServer                      bool
 	cacheServerPath                    string
+	cacheServerExternalURL             string
 	cacheServerAddr                    string
 	cacheServerPort                    uint16
 	jsonLogger                         bool
diff --git a/cmd/root.go b/cmd/root.go
index 7cc98a33e7b..3dbc83cefd1 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -118,6 +118,7 @@ func createRootCommand(ctx context.Context, input *Input, version string) *cobra
 	rootCmd.PersistentFlags().BoolVarP(&input.noSkipCheckout, "no-skip-checkout", "", false, "Use actions/checkout instead of copying local files into container")
 	rootCmd.PersistentFlags().BoolVarP(&input.noCacheServer, "no-cache-server", "", false, "Disable cache server")
 	rootCmd.PersistentFlags().StringVarP(&input.cacheServerPath, "cache-server-path", "", filepath.Join(CacheHomeDir, "actcache"), "Defines the path where the cache server stores caches.")
+	rootCmd.PersistentFlags().StringVarP(&input.cacheServerExternalURL, "cache-server-external-url", "", "", "Defines the external URL for if the cache server is behind a proxy. e.g.: https://act-cache-server.example.com. Be careful that there is no trailing slash.")
 	rootCmd.PersistentFlags().StringVarP(&input.cacheServerAddr, "cache-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the cache server binds.")
 	rootCmd.PersistentFlags().Uint16VarP(&input.cacheServerPort, "cache-server-port", "", 0, "Defines the port where the artifact server listens. 0 means a randomly available port.")
 	rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
@@ -667,7 +668,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
 		var cacheHandler *artifactcache.Handler
 		if !input.noCacheServer && envs[cacheURLKey] == "" {
 			var err error
-			cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerAddr, input.cacheServerPort, common.Logger(ctx))
+			cacheHandler, err = artifactcache.StartHandler(input.cacheServerPath, input.cacheServerExternalURL, input.cacheServerAddr, input.cacheServerPort, common.Logger(ctx))
 			if err != nil {
 				return err
 			}
diff --git a/pkg/artifactcache/handler.go b/pkg/artifactcache/handler.go
index cd0daaa0021..90d4218f29b 100644
--- a/pkg/artifactcache/handler.go
+++ b/pkg/artifactcache/handler.go
@@ -38,10 +38,11 @@ type Handler struct {
 	gcing atomic.Bool
 	gcAt  time.Time
 
-	outboundIP string
+	outboundIP        string
+	customExternalURL string
 }
 
-func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger) (*Handler, error) {
+func StartHandler(dir, customExternalURL string, outboundIP string, port uint16, logger logrus.FieldLogger) (*Handler, error) {
 	h := &Handler{}
 
 	if logger == nil {
@@ -71,6 +72,10 @@ func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger
 	}
 	h.storage = storage
 
+	if customExternalURL != "" {
+		h.customExternalURL = customExternalURL
+	}
+
 	if outboundIP != "" {
 		h.outboundIP = outboundIP
 	} else if ip := common.GetOutboundIP(); ip == nil {
@@ -95,6 +100,7 @@ func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger
 	if err != nil {
 		return nil, err
 	}
+
 	server := &http.Server{
 		ReadHeaderTimeout: 2 * time.Second,
 		Handler:           router,
@@ -110,11 +116,15 @@ func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger
 	return h, nil
 }
 
+func (h *Handler) GetActualPort() int {
+	return h.listener.Addr().(*net.TCPAddr).Port
+}
+
 func (h *Handler) ExternalURL() string {
-	// TODO: make the external url configurable if necessary
-	return fmt.Sprintf("http://%s:%d",
-		h.outboundIP,
-		h.listener.Addr().(*net.TCPAddr).Port)
+	if h.customExternalURL != "" {
+		return h.customExternalURL
+	}
+	return fmt.Sprintf("http://%s:%d", h.outboundIP, h.GetActualPort())
 }
 
 func (h *Handler) Close() error {


Test patch:
diff --git a/pkg/artifactcache/handler_test.go b/pkg/artifactcache/handler_test.go
index 252c420957e..88f6dabdb70 100644
--- a/pkg/artifactcache/handler_test.go
+++ b/pkg/artifactcache/handler_test.go
@@ -20,7 +20,7 @@ import (
 
 func TestHandler(t *testing.T) {
 	dir := filepath.Join(t.TempDir(), "artifactcache")
-	handler, err := StartHandler(dir, "", 0, nil)
+	handler, err := StartHandler(dir, "", "", 0, nil)
 	require.NoError(t, err)
 
 	base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase)
@@ -587,9 +587,43 @@ func uploadCacheNormally(t *testing.T, base, key, version string, content []byte
 	}
 }
 
+func TestHandler_CustomExternalURL(t *testing.T) {
+	dir := filepath.Join(t.TempDir(), "artifactcache")
+	handler, err := StartHandler(dir, "", "", 0, nil)
+	require.NoError(t, err)
+
+	defer func() {
+		require.NoError(t, handler.Close())
+	}()
+
+	handler.customExternalURL = fmt.Sprintf("http://%s:%d", "127.0.0.1", handler.GetActualPort())
+
+	assert.Equal(t, fmt.Sprintf("http://%s:%d", "127.0.0.1", handler.GetActualPort()), handler.ExternalURL())
+
+	base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase)
+
+	t.Run("advertise url set wrong", func(t *testing.T) {
+		original := handler.customExternalURL
+		defer func() {
+			handler.customExternalURL = original
+		}()
+		handler.customExternalURL = "http://127.0.0.999:1234"
+		assert.Equal(t, "http://127.0.0.999:1234", handler.ExternalURL())
+	})
+
+	t.Run("reserve and upload", func(t *testing.T) {
+		key := strings.ToLower(t.Name())
+		version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
+		content := make([]byte, 100)
+		_, err := rand.Read(content)
+		require.NoError(t, err)
+		uploadCacheNormally(t, base, key, version, content)
+	})
+}
+
 func TestHandler_gcCache(t *testing.T) {
 	dir := filepath.Join(t.TempDir(), "artifactcache")
-	handler, err := StartHandler(dir, "", 0, nil)
+	handler, err := StartHandler(dir, "", "", 0, nil)
 	require.NoError(t, err)
 
 	defer func() {