Eric Bower
·
2026-05-10
1package main
2
3import (
4 "flag"
5 "fmt"
6 "log/slog"
7 "os"
8 "strings"
9
10 "github.com/picosh/pico/pkg/db/postgres"
11 "github.com/picosh/pico/pkg/shared"
12 "github.com/picosh/pico/pkg/storage"
13)
14
15func main() {
16 deleteFlag := flag.Bool("delete", false, "delete orphaned bucket folders")
17 flag.Parse()
18
19 logger := slog.Default()
20
21 dbURL := shared.GetEnv("DATABASE_URL", "")
22 if dbURL == "" {
23 logger.Error("DATABASE_URL is required")
24 os.Exit(1)
25 }
26
27 dbpool := postgres.NewDB(dbURL, logger)
28 defer func() { _ = dbpool.Close() }()
29
30 adapter := storage.GetStorageTypeFromEnv()
31 st, err := storage.NewStorage(logger, adapter)
32 if err != nil {
33 logger.Error("failed to create storage", "err", err)
34 os.Exit(1)
35 }
36
37 // Collect all valid user IDs from the database
38 logger.Info("fetching all users from database")
39 users, err := dbpool.FindUsers()
40 if err != nil {
41 logger.Error("failed to fetch users", "err", err)
42 os.Exit(1)
43 }
44
45 validUserIDs := make(map[string]struct{}, len(users))
46 for _, user := range users {
47 validUserIDs[user.ID] = struct{}{}
48 logger.Info("found user", "id", user.ID, "name", user.Name)
49 }
50 logger.Info("total users", "count", len(validUserIDs))
51
52 // List all buckets in the storage directory
53 logger.Info("listing all buckets in storage")
54 buckets, err := st.ListBuckets()
55 if err != nil {
56 logger.Error("failed to list buckets", "err", err)
57 os.Exit(1)
58 }
59 logger.Info("total buckets", "count", len(buckets))
60
61 // Find orphaned buckets (no associated user)
62 orphaned := []string{}
63 for _, bucket := range buckets {
64 // Buckets are either the user ID directly (e.g., images)
65 // or prefixed with "static-" (e.g., static-{userID} for assets)
66 userID := bucket
67 if strings.HasPrefix(bucket, "static-") {
68 userID = strings.TrimPrefix(bucket, "static-")
69 }
70
71 if _, exists := validUserIDs[userID]; !exists {
72 orphaned = append(orphaned, bucket)
73 }
74 }
75
76 if len(orphaned) == 0 {
77 logger.Info("no orphaned buckets found")
78 return
79 }
80
81 logger.Info("orphaned buckets found", "count", len(orphaned))
82 for _, bucket := range orphaned {
83 fmt.Printf(" - %s\n", bucket)
84 }
85
86 if !*deleteFlag {
87 fmt.Printf("\nFound %d orphaned bucket(s). Use --delete to remove them.\n", len(orphaned))
88 return
89 }
90
91 // Delete orphaned buckets
92 logger.Info("deleting orphaned buckets")
93 deleted := 0
94 failed := 0
95 for _, bucket := range orphaned {
96 b, err := st.GetBucket(bucket)
97 if err != nil {
98 logger.Error("failed to get bucket", "bucket", bucket, "err", err)
99 failed++
100 continue
101 }
102
103 err = st.DeleteBucket(b)
104 if err != nil {
105 logger.Error("failed to delete bucket", "bucket", bucket, "err", err)
106 failed++
107 continue
108 }
109
110 logger.Info("deleted bucket", "bucket", bucket)
111 deleted++
112 }
113
114 fmt.Printf("\nDeleted %d bucket(s), %d failed\n", deleted, failed)
115}