fix: fallback to system cache for sowerd service

This commit is contained in:
wweir
2026-03-31 07:46:32 +08:00
parent 8bbf5ca03d
commit 549ab2a990
4 changed files with 68 additions and 6 deletions
+1
View File
@@ -92,6 +92,7 @@
- Upstream TLS behavior is configured only on the client side; `sowerd` remains a normal TLS server and does not need uTLS-specific logic.
- Rule loading supports local files and remote gzip-compressed HTTP sources.
- Fake site directory mode is loopback-only on port `80` to avoid exposing local static assets directly to the public internet.
- `sowerd` prefers the user cache directory for ACME state, but falls back to `/var/cache/sower` so systemd services can start without `HOME`/`XDG_CACHE_HOME` or a config file.
## Operational Notes
+2
View File
@@ -24,6 +24,8 @@ On the server-side, sowerd runs just like a web server proxy. It will bind two p
`sowerd` validates its configuration at startup, redacts secrets from logs, and exits immediately if `80` / `443` listeners cannot be established.
When `sowerd` runs as a system service without `HOME` or `XDG_CACHE_HOME`, it falls back to `/var/cache/sower` for certificate cache state so environment-only startup still works without a config file.
You can use your own certificate or the certificate automatically applied for by sowerd from [`Let's Encrypt`](https://letsencrypt.org/).
When `fake_site` points to a local directory, `sowerd` serves that directory only through loopback fallback traffic on `127.0.0.1:80`, while public HTTP traffic is still redirected to HTTPS.
+15 -6
View File
@@ -33,6 +33,7 @@ import (
const (
httpShutdownTimeout = 5 * time.Second
probeTimeout = 10 * time.Second
systemCacheDir = "/var/cache/sower"
)
var (
@@ -174,18 +175,26 @@ func run(ctx context.Context, conf config.SowerdConfig) error {
}
func cacheDir() (string, error) {
base, err := os.UserCacheDir()
if err != nil {
return "", fmt.Errorf("get user cache dir: %w", err)
dir, fallbackErr := resolveCacheDir(os.UserCacheDir, systemCacheDir)
if fallbackErr != nil {
slog.Warn("user cache dir unavailable, fallback to system cache dir",
"error", fallbackErr,
"dir", dir)
}
dir := filepath.Join(base, "sower")
if err := os.MkdirAll(dir, 0o700); err != nil {
return "", fmt.Errorf("create cache dir: %w", err)
return "", fmt.Errorf("create cache dir %s: %w", dir, err)
}
return dir, nil
}
func resolveCacheDir(userCacheDir func() (string, error), fallbackDir string) (string, error) {
base, err := userCacheDir()
if err != nil {
return fallbackDir, err
}
return filepath.Join(base, "sower"), nil
}
func buildTLSConfig(cacheDir string, cfg config.SowerdConfig) (*autocert.Manager, *tls.Config, error) {
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
+50
View File
@@ -1,7 +1,9 @@
package main
import (
"errors"
"log/slog"
"path/filepath"
"testing"
"github.com/sower-proxy/sower/config"
@@ -76,3 +78,51 @@ func TestHasInstallFlag(t *testing.T) {
})
}
}
func TestResolveCacheDir(t *testing.T) {
t.Parallel()
tests := []struct {
name string
userCache func() (string, error)
fallbackDir string
wantDir string
wantErr bool
}{
{
name: "user cache dir available",
userCache: func() (string, error) {
return "/tmp/cache", nil
},
fallbackDir: "/var/cache/sower",
wantDir: filepath.Join("/tmp/cache", "sower"),
},
{
name: "fallback to system cache dir",
userCache: func() (string, error) {
return "", errors.New("missing home")
},
fallbackDir: "/var/cache/sower",
wantDir: "/var/cache/sower",
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotDir, err := resolveCacheDir(tt.userCache, tt.fallbackDir)
if tt.wantErr && err == nil {
t.Fatal("expected error")
}
if !tt.wantErr && err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gotDir != tt.wantDir {
t.Fatalf("resolveCacheDir() = %q, want %q", gotDir, tt.wantDir)
}
})
}
}