Installation improvements, add backup and restore

Add backup and restore for config and history
during install
Visual changes
pull/184/head
Simon Let 3 years ago
parent dc58ceb403
commit e3d8d9f05a
  1. 14
      cmd/install-utils/backup.go
  2. 15
      cmd/install-utils/main.go
  3. 148
      cmd/install-utils/migrate.go
  4. 6
      internal/cfg/cfg.go
  5. 14
      internal/cfg/migrate.go
  6. 68
      internal/futil/futil.go
  7. 3
      internal/output/output.go

@ -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
}

@ -39,16 +39,10 @@ func main() {
} }
command := os.Args[1] command := os.Args[1]
switch command { switch command {
case "backup":
backup()
case "rollback":
rollback()
case "migrate-config":
migrateConfig(out)
case "migrate-history":
migrateHistory(out)
case "setup-device": case "setup-device":
setupDevice(out) setupDevice(out)
case "migrate-all":
migrateAll(out)
case "help": case "help":
printUsage(os.Stdout) printUsage(os.Stdout)
default: default:
@ -64,11 +58,8 @@ USAGE: ./install-utils COMMAND
Utils used during RESH installation. Utils used during RESH installation.
COMMANDS: 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 setup-device setup device name and device ID
migrate-all update config and history to latest format
help show this help help show this help
` `

@ -12,47 +12,112 @@ import (
"github.com/curusarn/resh/internal/recio" "github.com/curusarn/resh/internal/recio"
) )
func migrateConfig(out *output.Output) { func printRecoveryInfo(rf *futil.RestorableFile) {
err := cfg.Touch() 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 { 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() changes, err := cfg.Migrate()
if err != nil { 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")
} }
if changes { // We are returning the root cause - there might be a better solution how to report the errors
return nil, errMigrateWrap
}
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.") 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) { func migrateHistory(out *output.Output) error {
migrateHistoryLocation(out) err := migrateHistoryLocation(out)
migrateHistoryFormat(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 // 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 // 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) { func migrateHistoryLocation(out *output.Output) error {
dataDir, err := datadir.MakePath() dataDir, err := datadir.MakePath()
if err != nil { 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 // TODO: de-hardcode this
historyPath := path.Join(dataDir, "resh/history.reshjson") historyPath := path.Join(dataDir, "history.reshjson")
exists, err := futil.FileExists(historyPath) exists, err := futil.FileExists(historyPath)
if err != nil { if err != nil {
out.FatalE("ERROR: Failed to check history file", err) return fmt.Errorf("failed to check history file: %w", err)
} }
if exists { if exists {
out.Info(fmt.Sprintf("Found history file in '%s'", historyPath)) // TODO: get rid of this output (later)
return out.Info(fmt.Sprintf("Found history file in '%s' - nothing to move", historyPath))
return nil
} }
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { 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{ legacyHistoryPaths := []string{
@ -62,62 +127,71 @@ func migrateHistoryLocation(out *output.Output) {
for _, path := range legacyHistoryPaths { for _, path := range legacyHistoryPaths {
exists, err = futil.FileExists(path) exists, err = futil.FileExists(path)
if err != nil { 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 { if exists {
// TODO: maybe get rid of this output later
out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath)) out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath))
err = futil.CopyFile(path, historyPath) err = futil.CopyFile(path, historyPath)
if err != nil { 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") 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() dataDir, err := datadir.MakePath()
if err != nil { 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 // TODO: de-hardcode this
historyPath := path.Join(dataDir, "history.reshjson") historyPath := path.Join(dataDir, "history.reshjson")
historyPathBak := historyPath + ".bak"
exists, err := futil.FileExists(historyPath) exists, err := futil.FileExists(historyPath)
if err != nil { 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 { if !exists {
out.Error("There is no history file - this is normal if you are installing RESH for the first time on this device") 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) _, err = futil.TouchFile(historyPath)
if err != nil { 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 { 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()) rio := recio.New(out.Logger.Sugar())
recs, err := rio.ReadAndFixFile(historyPath, 3) recs, err := rio.ReadAndFixFile(historyPath, 3)
if err != nil { 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) err = rio.OverwriteFile(historyPath, recs)
if err != nil { if err != nil {
out.ErrorE("ERROR: Could not update format of history file", err) // Restore
errMigrate := err
err = futil.CopyFile(historyPathBak, historyPath) 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 { if err != nil {
out.FatalE("ERROR: Could not restore history file from backup!", err) out.InfoE("FAILED TO RESTORE resh HISTORY FROM BACKUP!", err)
// TODO: history restoration tutorial 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
} }

@ -183,3 +183,9 @@ func New() (Config, error) {
} }
return config, nil return config, nil
} }
// GetPath returns path to config
// Shouldn't be necessary for basic use
func GetPath() (string, error) {
return getConfigPath()
}

@ -5,22 +5,8 @@ import (
"os" "os"
"github.com/BurntSushi/toml" "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 // Migrate old config versions to current config version
// returns true if any changes were made to the config // returns true if any changes were made to the config
func Migrate() (bool, error) { func Migrate() (bool, error) {

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"time"
) )
func CopyFile(source, dest string) error { func CopyFile(source, dest string) error {
@ -19,13 +20,12 @@ func CopyFile(source, dest string) error {
if err != nil { if err != nil {
return err return err
} }
defer to.Close()
_, err = io.Copy(to, from) _, err = io.Copy(to, from)
if err != nil { if err != nil {
return err return err
} }
return nil return to.Close()
} }
func FileExists(fpath string) (bool, error) { 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) 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) file, err := os.OpenFile(fpath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err != nil { 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() err = file.Close()
if err != nil { 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 return nil
} }

@ -60,7 +60,6 @@ func (f *Output) FatalE(msg string, err error) {
} }
var msgDaemonNotRunning = `RESH daemon didn't respond - it's probably not running. var msgDaemonNotRunning = `RESH daemon didn't respond - it's probably not running.
-> Start RESH daemon manually - run: resh-daemon-start -> Start RESH daemon manually - run: resh-daemon-start
-> Or restart this terminal window to bring RESH daemon back up -> 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) -> 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. 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. It looks like you updated RESH and didn't restart this terminal.
-> Restart this terminal window to fix that -> Restart this terminal window to fix that
` `
var msgDaemonVersionMismatch = `RESH daemon is running in different version than is installed now. var msgDaemonVersionMismatch = `RESH daemon is running in different version than is installed now.
It looks like something went wrong during RESH update. 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 -> 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 -> You can create an issue at: https://github.com/curusarn/resh/issues

Loading…
Cancel
Save