check / check (push) Failing after 20m56s
A backup of a directory that holds the local index stored a torn copy of it, its -wal and -shm side files and vaultik.pid, all being written while they were read. The scan now treats these files as excluded, whatever the config says. Their paths come from index_path and the lock directory, with symlinks in their directories resolved the way source paths are. If index_path is itself a symlink, SQLite writes the file it points to, so that file and its side files are skipped too. pidlock now owns the lock directory and the PID file name, so the CLI and the scanner use the same path. database delete now removes the side files database.IndexFiles lists, the -journal file included. Model: opus-5-5
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/spf13/cobra"
|
|
"sneak.berlin/go/vaultik/internal/config"
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// NewDatabaseCommand creates the database command group
|
|
func NewDatabaseCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "database",
|
|
Short: "Manage the local state database",
|
|
Long: `Commands for managing the local SQLite state database.`,
|
|
// cobra checks Args only on a command that has a Run: without
|
|
// one, a mistyped subcommand printed help and exited 0.
|
|
Args: cobra.NoArgs,
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
_ = cmd.Help()
|
|
},
|
|
}
|
|
|
|
cmd.AddCommand(
|
|
newDatabaseDeleteCommand(),
|
|
)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newDatabaseDeleteCommand creates the database delete command.
|
|
// (Renamed from "purge"; the operation removes the SQLite file
|
|
// entirely, which is a delete, not a purge of content.)
|
|
func newDatabaseDeleteCommand() *cobra.Command {
|
|
var force bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "delete",
|
|
Short: "Delete the local state database file",
|
|
Long: `Completely removes the local SQLite state database.
|
|
|
|
This will erase all local tracking of:
|
|
- File metadata and change detection state
|
|
- Chunk and blob mappings
|
|
- Local snapshot records
|
|
- The storage-binding record
|
|
|
|
The remote storage is NOT affected. After deletion, the next backup
|
|
will perform a full scan and re-deduplicate against existing remote
|
|
blobs, and the local index will re-bind to the currently configured
|
|
storage destination on that run.
|
|
|
|
Use --force to skip the confirmation prompt.`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
// Resolve config path
|
|
configPath, err := ResolveConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Load config to get database path
|
|
cfg, err := config.Load(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
dbPath := cfg.IndexPath
|
|
out := commandUI(cmd)
|
|
|
|
// Check if database exists
|
|
_, err = os.Stat(dbPath)
|
|
if os.IsNotExist(err) {
|
|
out.Infof("Local state database does not exist: %s.", dbPath)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Confirm unless --force. The prompt and its immediate result
|
|
// are an interactive exchange the operator must see, so they go
|
|
// straight to stdout rather than through the UI and --quiet does
|
|
// not silence them.
|
|
if !force {
|
|
w := cmd.OutOrStdout()
|
|
_, _ = fmt.Fprintf(w,
|
|
"This will delete the local state database at:\n %s\n\n", dbPath)
|
|
_, _ = fmt.Fprint(w, "Are you sure? Type 'yes' to confirm: ")
|
|
|
|
var confirm string
|
|
|
|
// A failure to read the answer means no confirmation.
|
|
_, err = fmt.Fscanln(cmd.InOrStdin(), &confirm)
|
|
if err != nil || confirm != "yes" {
|
|
_, _ = fmt.Fprintln(w, "Aborted.")
|
|
|
|
return vaultik.ErrNotConfirmed
|
|
}
|
|
}
|
|
|
|
// Delete the database file and its side files. A side file
|
|
// may not exist, so only failing to delete the database file
|
|
// is an error.
|
|
for _, indexFile := range database.IndexFiles(dbPath) {
|
|
err = os.Remove(indexFile)
|
|
if err != nil && indexFile == dbPath {
|
|
return fmt.Errorf("failed to delete database: %w", err)
|
|
}
|
|
}
|
|
|
|
out.Infof("Local state database deleted: %s.", dbPath)
|
|
log.Info("Local state database deleted", "path", dbPath)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&force, "force", false, "Skip confirmation prompt")
|
|
|
|
return cmd
|
|
}
|