2
0

feat: Add build cache cleanup and CLI cleanup command
Some checks failed
CI / build-and-test (push) Failing after 30s

- Add cleanup for common build tool caches (Go, npm, NuGet, Gradle, Maven, pip, Cargo)
- Build caches cleaned for files older than 7 days
- Add gitcaddy-runner cleanup CLI command for manual cleanup trigger
- Fixes disk space issues from accumulated CI build artifacts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
GitCaddy
2026-01-14 09:26:21 +00:00
parent 0ba2e0c3d5
commit f5b22c4149
2 changed files with 117 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.com/gitea/act_runner/internal/pkg/cleanup"
"gitea.com/gitea/act_runner/internal/pkg/config" "gitea.com/gitea/act_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/ver" "gitea.com/gitea/act_runner/internal/pkg/ver"
) )
@@ -79,6 +80,32 @@ func Execute(ctx context.Context) {
cacheCmd.Flags().Uint16VarP(&cacheArgs.Port, "port", "p", 0, "Port of the cache server") cacheCmd.Flags().Uint16VarP(&cacheArgs.Port, "port", "p", 0, "Port of the cache server")
rootCmd.AddCommand(cacheCmd) rootCmd.AddCommand(cacheCmd)
// ./gitcaddy-runner cleanup
cleanupCmd := &cobra.Command{
Use: "cleanup",
Short: "Manually trigger cleanup to free disk space",
Args: cobra.MaximumNArgs(0),
RunE: func(_ *cobra.Command, _ []string) error {
cfg, err := config.LoadDefault(configFile)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
result, err := cleanup.RunCleanup(ctx, cfg)
if err != nil {
return fmt.Errorf("cleanup failed: %w", err)
}
fmt.Printf("Cleanup completed: freed %d bytes, deleted %d files in %s\n", result.BytesFreed, result.FilesDeleted, result.Duration)
if len(result.Errors) > 0 {
fmt.Printf("Warnings: %d errors occurred\n", len(result.Errors))
for _, e := range result.Errors {
fmt.Printf(" - %s\n", e)
}
}
return nil
},
}
rootCmd.AddCommand(cleanupCmd)
// hide completion command // hide completion command
rootCmd.CompletionOptions.HiddenDefaultCmd = true rootCmd.CompletionOptions.HiddenDefaultCmd = true

View File

@@ -73,6 +73,16 @@ func RunCleanup(ctx context.Context, cfg *config.Config) (*CleanupResult, error)
log.Infof("Cleaned temp: freed %d bytes, deleted %d files", bytes, files) log.Infof("Cleaned temp: freed %d bytes, deleted %d files", bytes, files)
} }
// 5. Clean build tool caches (older than 7 days)
// These can grow very large from Go, npm, nuget, gradle, maven builds
if bytes, files, err := cleanBuildCaches(7 * 24 * time.Hour); err != nil {
result.Errors = append(result.Errors, fmt.Errorf("build cache cleanup: %w", err))
} else {
result.BytesFreed += bytes
result.FilesDeleted += files
log.Infof("Cleaned build caches: freed %d bytes, deleted %d files", bytes, files)
}
result.Duration = time.Since(start) result.Duration = time.Since(start)
log.Infof("Cleanup completed: freed %s in %s", formatBytes(result.BytesFreed), result.Duration) log.Infof("Cleanup completed: freed %s in %s", formatBytes(result.BytesFreed), result.Duration)
@@ -251,6 +261,86 @@ func dirSize(path string) int64 {
return size return size
} }
// cleanBuildCaches removes old build tool caches that accumulate from CI jobs
// These are cleaned more aggressively (files older than 7 days) since they can grow very large
func cleanBuildCaches(maxAge time.Duration) (int64, int, error) {
home := os.Getenv("HOME")
if home == "" {
home = "/root" // fallback for runners typically running as root
}
var totalBytesFreed int64
var totalFilesDeleted int
// Build cache directories to clean
// Format: {path, description}
cacheDirs := []struct {
path string
desc string
}{
{filepath.Join(home, ".cache", "go-build"), "Go build cache"},
{filepath.Join(home, ".cache", "golangci-lint"), "golangci-lint cache"},
{filepath.Join(home, ".npm", "_cacache"), "npm cache"},
{filepath.Join(home, ".cache", "pnpm"), "pnpm cache"},
{filepath.Join(home, ".cache", "yarn"), "yarn cache"},
{filepath.Join(home, ".nuget", "packages"), "NuGet cache"},
{filepath.Join(home, ".gradle", "caches"), "Gradle cache"},
{filepath.Join(home, ".m2", "repository"), "Maven cache"},
{filepath.Join(home, ".cache", "pip"), "pip cache"},
{filepath.Join(home, ".cargo", "registry", "cache"), "Cargo cache"},
{filepath.Join(home, ".rustup", "tmp"), "Rustup temp"},
}
cutoff := time.Now().Add(-maxAge)
for _, cache := range cacheDirs {
if _, err := os.Stat(cache.path); os.IsNotExist(err) {
continue
}
var bytesFreed int64
var filesDeleted int
err := filepath.Walk(cache.path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // Skip errors
}
if info.IsDir() {
return nil
}
if info.ModTime().Before(cutoff) {
size := info.Size()
if err := os.Remove(path); err == nil {
bytesFreed += size
filesDeleted++
}
}
return nil
})
if err == nil && (bytesFreed > 0 || filesDeleted > 0) {
log.Infof("Cleaned %s: freed %s, deleted %d files", cache.desc, formatBytes(bytesFreed), filesDeleted)
totalBytesFreed += bytesFreed
totalFilesDeleted += filesDeleted
}
// Also remove empty directories
filepath.Walk(cache.path, func(path string, info os.FileInfo, err error) error {
if err != nil || !info.IsDir() || path == cache.path {
return nil
}
entries, _ := os.ReadDir(path)
if len(entries) == 0 {
os.Remove(path)
}
return nil
})
}
return totalBytesFreed, totalFilesDeleted, nil
}
// formatBytes formats bytes into human readable string // formatBytes formats bytes into human readable string
func formatBytes(bytes int64) string { func formatBytes(bytes int64) string {
const unit = 1024 const unit = 1024