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]
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
`

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

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

@ -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) {

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

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

Loading…
Cancel
Save