diff --git a/cmd/install-utils/backup.go b/cmd/install-utils/backup.go deleted file mode 100644 index 1121535..0000000 --- a/cmd/install-utils/backup.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -func backup() { - panic("Backup not implemented yet!") - // Backup ~/.resh - // Backup xdg_data/resh/history.reshjson - // TODO: figure out history file localtions when using history sync -} - -func rollback() { - panic("Rollback not implemented yet!") - // Rollback ~/.resh - // Rollback history -} diff --git a/cmd/install-utils/main.go b/cmd/install-utils/main.go index 7a5db51..4b72676 100644 --- a/cmd/install-utils/main.go +++ b/cmd/install-utils/main.go @@ -39,16 +39,10 @@ func main() { } command := os.Args[1] switch command { - case "backup": - backup() - case "rollback": - rollback() - case "migrate-config": - migrateConfig(out) - case "migrate-history": - migrateHistory(out) case "setup-device": setupDevice(out) + case "migrate-all": + migrateAll(out) case "help": printUsage(os.Stdout) default: @@ -64,11 +58,8 @@ USAGE: ./install-utils COMMAND Utils used during RESH installation. COMMANDS: - backup backup resh installation and data - rollback restore resh installation and data from backup - migrate-config update config to latest format - migrate-history update history to latest format setup-device setup device name and device ID + migrate-all update config and history to latest format help show this help ` diff --git a/cmd/install-utils/migrate.go b/cmd/install-utils/migrate.go index d2eb2da..17c5eb5 100644 --- a/cmd/install-utils/migrate.go +++ b/cmd/install-utils/migrate.go @@ -12,47 +12,112 @@ import ( "github.com/curusarn/resh/internal/recio" ) -func migrateConfig(out *output.Output) { - err := cfg.Touch() +func printRecoveryInfo(rf *futil.RestorableFile) { + fmt.Printf(" -> Backup is '%s'"+ + " -> Original file location is '%s'\n"+ + " -> Please copy the backup over the file - run: cp -f '%s' '%s'\n\n", + rf.PathBackup, rf.Path, + rf.PathBackup, rf.Path, + ) +} + +func migrateAll(out *output.Output) { + cfgBackup, err := migrateConfig(out) + if err != nil { + // out.InfoE("Failed to update config file format", err) + out.FatalE("ERROR: Failed to update config file format", err) + } + err = migrateHistory(out) + if err != nil { + errHist := err + out.InfoE("Failed to update RESH history", errHist) + out.Info("Restoring config from backup ...") + err = cfgBackup.Restore() + if err != nil { + out.InfoE("FAILED TO RESTORE CONFIG FROM BACKUP!", err) + printRecoveryInfo(cfgBackup) + } else { + out.Info("Config file was restored successfully") + } + out.FatalE("ERROR: Failed to update history", err) + } +} + +func migrateConfig(out *output.Output) (*futil.RestorableFile, error) { + cfgPath, err := cfg.GetPath() if err != nil { - out.FatalE("ERROR: Failed to touch config file", err) + return nil, fmt.Errorf("could not get config file path: %w", err) } + + // Touch config to get rid of edge-cases + created, err := futil.TouchFile(cfgPath) + if err != nil { + return nil, fmt.Errorf("failed to touch config file: %w", err) + } + + // Backup + backup, err := futil.BackupFile(cfgPath) + if err != nil { + return nil, fmt.Errorf("could not backup config file: %w", err) + } + + // Migrate changes, err := cfg.Migrate() if err != nil { - out.FatalE("ERROR: Failed to update config file", err) + // Restore + errMigrate := err + errMigrateWrap := fmt.Errorf("failed to update config file: %w", errMigrate) + out.InfoE("Failed to update config file format", errMigrate) + out.Info("Restoring config from backup ...") + err = backup.Restore() + if err != nil { + out.InfoE("FAILED TO RESTORE CONFIG FROM BACKUP!", err) + printRecoveryInfo(backup) + } else { + out.Info("Config file was restored successfully") + } + // We are returning the root cause - there might be a better solution how to report the errors + return nil, errMigrateWrap } - if changes { + if created { + out.Info(fmt.Sprintf("RESH config created in '%s'", cfgPath)) + } else if changes { out.Info("RESH config file format has changed since last update - your config was updated to reflect the changes.") } + return backup, nil } -func migrateHistory(out *output.Output) { - migrateHistoryLocation(out) - migrateHistoryFormat(out) +func migrateHistory(out *output.Output) error { + err := migrateHistoryLocation(out) + if err != nil { + return fmt.Errorf("failed to move history to new location %w", err) + } + return migrateHistoryFormat(out) } -// find first existing history and use it -// don't bother with merging of history in multiple locations - it could get messy and it shouldn't be necessary -func migrateHistoryLocation(out *output.Output) { +// Find first existing history and use it +// Don't bother with merging of history in multiple locations - it could get messy and it shouldn't be necessary +func migrateHistoryLocation(out *output.Output) error { dataDir, err := datadir.MakePath() if err != nil { - out.FatalE("ERROR: Failed to get data directory", err) + return fmt.Errorf("failed to get data directory: %w", err) } // TODO: de-hardcode this - historyPath := path.Join(dataDir, "resh/history.reshjson") + historyPath := path.Join(dataDir, "history.reshjson") exists, err := futil.FileExists(historyPath) if err != nil { - out.FatalE("ERROR: Failed to check history file", err) + return fmt.Errorf("failed to check history file: %w", err) } if exists { - out.Info(fmt.Sprintf("Found history file in '%s'", historyPath)) - return + // TODO: get rid of this output (later) + out.Info(fmt.Sprintf("Found history file in '%s' - nothing to move", historyPath)) + return nil } homeDir, err := os.UserHomeDir() if err != nil { - out.FatalE("ERROR: Failed to get user home directory", err) + return fmt.Errorf("failed to get user home directory: %w", err) } legacyHistoryPaths := []string{ @@ -62,62 +127,71 @@ func migrateHistoryLocation(out *output.Output) { for _, path := range legacyHistoryPaths { exists, err = futil.FileExists(path) if err != nil { - out.FatalE("ERROR: Failed to check legacy history file", err) + return fmt.Errorf("failed to check existence of legacy history file: %w", err) } if exists { + // TODO: maybe get rid of this output later out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath)) err = futil.CopyFile(path, historyPath) if err != nil { - out.FatalE("ERROR: Failed to copy history file", err) + return fmt.Errorf("failed to copy history file: %w", err) } out.Info("History file copied successfully") - return + return nil } } + // out.Info("WARNING: No RESH history file found (this is normal during new installation)") + return nil } -func migrateHistoryFormat(out *output.Output) { +func migrateHistoryFormat(out *output.Output) error { dataDir, err := datadir.MakePath() if err != nil { - out.FatalE("ERROR: Could not get user data directory", err) + return fmt.Errorf("could not get user data directory: %w", err) } // TODO: de-hardcode this historyPath := path.Join(dataDir, "history.reshjson") - historyPathBak := historyPath + ".bak" exists, err := futil.FileExists(historyPath) if err != nil { - out.FatalE("ERROR: Failed to check existence of history file", err) + return fmt.Errorf("failed to check existence of history file: %w", err) } if !exists { - out.Error("There is no history file - this is normal if you are installing RESH for the first time on this device") - err = futil.TouchFile(historyPath) + out.Error("There is no RESH history file - this is normal if you are installing RESH for the first time on this device") + _, err = futil.TouchFile(historyPath) if err != nil { - out.FatalE("ERROR: Failed to touch history file", err) + return fmt.Errorf("failed to touch history file: %w", err) } - os.Exit(0) + return nil } - err = futil.CopyFile(historyPath, historyPathBak) + backup, err := futil.BackupFile(historyPath) if err != nil { - out.FatalE("ERROR: Could not back up history file", err) + return fmt.Errorf("could not back up history file: %w", err) } rio := recio.New(out.Logger.Sugar()) recs, err := rio.ReadAndFixFile(historyPath, 3) if err != nil { - out.FatalE("ERROR: Could not load history file", err) + return fmt.Errorf("could not load history file: %w", err) } err = rio.OverwriteFile(historyPath, recs) if err != nil { - out.ErrorE("ERROR: Could not update format of history file", err) - - err = futil.CopyFile(historyPathBak, historyPath) + // Restore + errMigrate := err + errMigrateWrap := fmt.Errorf("failed to update format of history file: %w", errMigrate) + out.InfoE("Failed to update RESH history file format", errMigrate) + out.Info("Restoring RESH history from backup ...") + err = backup.Restore() if err != nil { - out.FatalE("ERROR: Could not restore history file from backup!", err) - // TODO: history restoration tutorial + out.InfoE("FAILED TO RESTORE resh HISTORY FROM BACKUP!", err) + printRecoveryInfo(backup) + } else { + out.Info("RESH history file was restored successfully") } - out.Info("History file was restored to the original form") + // We are returning the root cause - there might be a better solution how to report the errors + return errMigrateWrap } + return nil } diff --git a/internal/cfg/cfg.go b/internal/cfg/cfg.go index c41affd..534443d 100644 --- a/internal/cfg/cfg.go +++ b/internal/cfg/cfg.go @@ -183,3 +183,9 @@ func New() (Config, error) { } return config, nil } + +// GetPath returns path to config +// Shouldn't be necessary for basic use +func GetPath() (string, error) { + return getConfigPath() +} diff --git a/internal/cfg/migrate.go b/internal/cfg/migrate.go index 2933e36..bc92af3 100644 --- a/internal/cfg/migrate.go +++ b/internal/cfg/migrate.go @@ -5,22 +5,8 @@ import ( "os" "github.com/BurntSushi/toml" - "github.com/curusarn/resh/internal/futil" ) -// Touch config file -func Touch() error { - fpath, err := getConfigPath() - if err != nil { - return fmt.Errorf("could not get config file path: %w", err) - } - err = futil.TouchFile(fpath) - if err != nil { - return fmt.Errorf("could not touch config file: %w", err) - } - return nil -} - // Migrate old config versions to current config version // returns true if any changes were made to the config func Migrate() (bool, error) { diff --git a/internal/futil/futil.go b/internal/futil/futil.go index c85e98e..4cdbc24 100644 --- a/internal/futil/futil.go +++ b/internal/futil/futil.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "time" ) func CopyFile(source, dest string) error { @@ -19,13 +20,12 @@ func CopyFile(source, dest string) error { if err != nil { return err } - defer to.Close() _, err = io.Copy(to, from) if err != nil { return err } - return nil + return to.Close() } func FileExists(fpath string) (bool, error) { @@ -42,14 +42,72 @@ func FileExists(fpath string) (bool, error) { return false, fmt.Errorf("could not stat file: %w", err) } -func TouchFile(fpath string) error { +// TouchFile touches file +// Returns true if file was created false otherwise +func TouchFile(fpath string) (bool, error) { + exists, err := FileExists(fpath) + if err != nil { + return false, err + } + file, err := os.OpenFile(fpath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { - return fmt.Errorf("could not open/create file: %w", err) + return false, fmt.Errorf("could not open/create file: %w", err) } err = file.Close() if err != nil { - return fmt.Errorf("could not close file: %w", err) + return false, fmt.Errorf("could not close file: %w", err) + } + return !exists, nil +} + +func getBackupPath(fpath string) string { + ext := fmt.Sprintf(".backup-%d", time.Now().Unix()) + return fpath + ext +} + +// BackupFile backups file using unique suffix +// Returns path to backup +func BackupFile(fpath string) (*RestorableFile, error) { + fpathBackup := getBackupPath(fpath) + exists, err := FileExists(fpathBackup) + if err != nil { + return nil, err + } + if exists { + return nil, fmt.Errorf("backup already exists in the determined path") + } + err = CopyFile(fpath, fpathBackup) + if err != nil { + return nil, fmt.Errorf("failed to copy file: %w ", err) + } + rf := RestorableFile{ + Path: fpath, + PathBackup: fpathBackup, + } + return &rf, nil +} + +type RestorableFile struct { + Path string + PathBackup string +} + +func (r RestorableFile) Restore() error { + return restoreFileFromBackup(r.Path, r.PathBackup) +} + +func restoreFileFromBackup(fpath, fpathBak string) error { + exists, err := FileExists(fpathBak) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("backup not found in given path: no such file or directory: %s", fpathBak) + } + err = CopyFile(fpathBak, fpath) + if err != nil { + return fmt.Errorf("failed to copy file: %w ", err) } return nil } diff --git a/internal/output/output.go b/internal/output/output.go index 93f403b..4f7840d 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -60,7 +60,6 @@ func (f *Output) FatalE(msg string, err error) { } var msgDaemonNotRunning = `RESH daemon didn't respond - it's probably not running. - -> Start RESH daemon manually - run: resh-daemon-start -> Or restart this terminal window to bring RESH daemon back up -> You can check logs: ~/.local/share/resh/log.json (or ~/$XDG_DATA_HOME/resh/log.json) @@ -69,14 +68,12 @@ var msgDaemonNotRunning = `RESH daemon didn't respond - it's probably not runnin ` var msgTerminalVersionMismatch = `This terminal session was started with different RESH version than is installed now. It looks like you updated RESH and didn't restart this terminal. - -> Restart this terminal window to fix that ` var msgDaemonVersionMismatch = `RESH daemon is running in different version than is installed now. It looks like something went wrong during RESH update. - -> Kill resh-daemon and then launch a new terminal window to fix that: killall resh-daemon -> You can create an issue at: https://github.com/curusarn/resh/issues