Commit 3004275

Eric Bower  ·  2026-05-10 20:54:24 -0400 EDT
parent d634672
chore: script to find orphaned buckets
1 files changed,  +114, -0
+114, -0
......@@ -0,0 +1,114 @@
1+package main
2+
3+import (
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+
15+func 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+ st, err := storage.NewStorage(logger, "fs")
31+ if err != nil {
32+ logger.Error("failed to create storage", "err", err)
33+ os.Exit(1)
34+ }
35+
36+ // Collect all valid user IDs from the database
37+ logger.Info("fetching all users from database")
38+ users, err := dbpool.FindUsers()
39+ if err != nil {
40+ logger.Error("failed to fetch users", "err", err)
41+ os.Exit(1)
42+ }
43+
44+ validUserIDs := make(map[string]struct{}, len(users))
45+ for _, user := range users {
46+ validUserIDs[user.ID] = struct{}{}
47+ logger.Info("found user", "id", user.ID, "name", user.Name)
48+ }
49+ logger.Info("total users", "count", len(validUserIDs))
50+
51+ // List all buckets in the storage directory
52+ logger.Info("listing all buckets in storage")
53+ buckets, err := st.ListBuckets()
54+ if err != nil {
55+ logger.Error("failed to list buckets", "err", err)
56+ os.Exit(1)
57+ }
58+ logger.Info("total buckets", "count", len(buckets))
59+
60+ // Find orphaned buckets (no associated user)
61+ orphaned := []string{}
62+ for _, bucket := range buckets {
63+ // Buckets are either the user ID directly (e.g., images)
64+ // or prefixed with "static-" (e.g., static-{userID} for assets)
65+ userID := bucket
66+ if strings.HasPrefix(bucket, "static-") {
67+ userID = strings.TrimPrefix(bucket, "static-")
68+ }
69+
70+ if _, exists := validUserIDs[userID]; !exists {
71+ orphaned = append(orphaned, bucket)
72+ }
73+ }
74+
75+ if len(orphaned) == 0 {
76+ logger.Info("no orphaned buckets found")
77+ return
78+ }
79+
80+ logger.Info("orphaned buckets found", "count", len(orphaned))
81+ for _, bucket := range orphaned {
82+ fmt.Printf(" - %s\n", bucket)
83+ }
84+
85+ if !*deleteFlag {
86+ fmt.Printf("\nFound %d orphaned bucket(s). Use --delete to remove them.\n", len(orphaned))
87+ return
88+ }
89+
90+ // Delete orphaned buckets
91+ logger.Info("deleting orphaned buckets")
92+ deleted := 0
93+ failed := 0
94+ for _, bucket := range orphaned {
95+ b, err := st.GetBucket(bucket)
96+ if err != nil {
97+ logger.Error("failed to get bucket", "bucket", bucket, "err", err)
98+ failed++
99+ continue
100+ }
101+
102+ err = st.DeleteBucket(b)
103+ if err != nil {
104+ logger.Error("failed to delete bucket", "bucket", bucket, "err", err)
105+ failed++
106+ continue
107+ }
108+
109+ logger.Info("deleted bucket", "bucket", bucket)
110+ deleted++
111+ }
112+
113+ fmt.Printf("\nDeleted %d bucket(s), %d failed\n", deleted, failed)
114+}