diff --git a/internal/backup/backup.go b/internal/backup/backup.go index 8b302a4..2c37156 100644 --- a/internal/backup/backup.go +++ b/internal/backup/backup.go @@ -121,6 +121,34 @@ func List() ([]Manifest, error) { return out, nil } +func Delete(id string) error { + id = strings.TrimSpace(id) + if !safeID(id) { + return fmt.Errorf("invalid backup id") + } + + archiveName := id + ".tar.gz" + if manifest, err := readManifestSidecar(id); err == nil && strings.TrimSpace(manifest.Archive) != "" { + archiveName = filepath.Base(manifest.Archive) + } + + archivePath := filepath.Join(RootDir, archiveName) + manifestPath := filepath.Join(RootDir, id+".json") + + archiveErr := os.Remove(archivePath) + if archiveErr != nil && !errors.Is(archiveErr, os.ErrNotExist) { + return archiveErr + } + manifestErr := os.Remove(manifestPath) + if manifestErr != nil && !errors.Is(manifestErr, os.ErrNotExist) { + return manifestErr + } + if errors.Is(archiveErr, os.ErrNotExist) && errors.Is(manifestErr, os.ErrNotExist) { + return os.ErrNotExist + } + return nil +} + func Restore(cfg *state.Config, id string) (Manifest, error) { if err := requireMinecraft(cfg); err != nil { return Manifest{}, err diff --git a/internal/handlers/backups.go b/internal/handlers/backups.go index f2a70ac..240cebe 100644 --- a/internal/handlers/backups.go +++ b/internal/handlers/backups.go @@ -2,7 +2,9 @@ package handlers import ( "encoding/json" + "errors" "net/http" + "os" "strings" agentbackup "zlh-agent/internal/backup" @@ -20,6 +22,39 @@ func HandleGameBackups(w http.ResponseWriter, r *http.Request) { } } +func HandleGameBackupByID(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + writeJSONError(w, http.StatusMethodNotAllowed, "DELETE only") + return + } + endOp, ok := beginHandlerOperation(w, "backup_delete", true, "deleting backup") + if !ok { + return + } + defer endOp() + + if _, ok := requireBackupConfig(w); !ok { + return + } + + id := strings.TrimPrefix(r.URL.Path, "/game/backups/") + id = strings.TrimSpace(strings.Trim(id, "/")) + if id == "" || strings.Contains(id, "/") { + writeJSONError(w, http.StatusBadRequest, "backup id required") + return + } + + if err := agentbackup.Delete(id); err != nil { + if errors.Is(err, os.ErrNotExist) { + writeJSONError(w, http.StatusNotFound, "backup not found") + return + } + writeJSONError(w, http.StatusBadRequest, err.Error()) + return + } + writeJSON(w, http.StatusOK, map[string]any{"deleted": true, "id": id}) +} + func handleGameBackupsList(w http.ResponseWriter, r *http.Request) { if _, ok := requireBackupConfig(w); !ok { return diff --git a/internal/http/agent.go b/internal/http/agent.go index d42025c..dc11d6d 100755 --- a/internal/http/agent.go +++ b/internal/http/agent.go @@ -886,6 +886,7 @@ func NewMux() *http.ServeMux { m.HandleFunc("/game/players", handleGamePlayers) m.HandleFunc("/game/backups", agenthandlers.HandleGameBackups) m.HandleFunc("/game/backups/restore", agenthandlers.HandleGameBackupRestore) + m.HandleFunc("/game/backups/", agenthandlers.HandleGameBackupByID) m.HandleFunc("/game/mods", agenthandlers.HandleGameMods) m.HandleFunc("/game/mods/install", agenthandlers.HandleGameModsInstall) m.HandleFunc("/game/mods/", agenthandlers.HandleGameModByID)