// Backups returns a slice of all app backup timestamps. func (a *App) Backups() ([]string, error) { dir := a.BackupDirectoryPath() // If the backup folder does not exists, then return nil. e, err := utils.Exists(dir) if err != nil { return nil, err } else if !e { return nil, nil } // Get all files in the apps directory. files, err := ioutil.ReadDir(dir) if err != nil { return nil, err } backups := make([]string, len(files)) // Get all the backup timestampts. for i, f := range files { // Skip if not a directory. if !f.IsDir() { continue } backups[i] = f.Name() } return backups, nil }
// hostFingerprintExists checks whenever a host fingerprint exists. func hostFingerprintExists(host string) (bool, error) { // Get the know_hosts file path. path := config.Config.KnownHostsFilePath() // Check if file exists. e, err := utils.Exists(path) if err != nil { return false, err } else if !e { return false, nil } // Open the file. file, err := os.Open(path) if err != nil { return false, err } defer file.Close() // Read the file line by line. scanner := bufio.NewScanner(file) for scanner.Scan() { // Check if the host exists. if strings.HasPrefix(strings.TrimSpace(scanner.Text()), host) { return true, nil } } // Check if an error occurred. if err = scanner.Err(); err != nil { return false, err } return false, nil }
// Snapshot create a btrfs snapshot of a subvolume. func Snapshot(subvolumeDir string, snapshotDir string, readonly bool) error { // Check if the passed subvolume directory is a btrfs subvolume. if !IsSubvolume(subvolumeDir) { return fmt.Errorf("failed to create btrfs snapshot: the subvolume directory '%s' is not a btrfs subvolume!", subvolumeDir) } // The destination snapshot directory should not exist! e, err := utils.Exists(snapshotDir) if err != nil { return err } else if e { return fmt.Errorf("failed to create btrfs snapshot: the snapshot directory '%s' already exists!", snapshotDir) } // Create the snapshot directory. if readonly { err = utils.RunCommand("btrfs", "subvolume", "snapshot", "-r", subvolumeDir, snapshotDir) } else { err = utils.RunCommand("btrfs", "subvolume", "snapshot", subvolumeDir, snapshotDir) } if err != nil { return fmt.Errorf("failed to create btrfs snapshot '%s': %v", snapshotDir, err) } // Force changed blocks to disk, update the super block. err = utils.RunCommand("sync") if err != nil { return fmt.Errorf("failed to force changed blocks to disk, update the super block: %v", err) } return nil }
// restoreState loads the turtle state file if present and // restores the previous running apps. func restoreState() error { var s state // Set the turtle state file path. statePath := config.Config.StateFilePath() // Skip if it does not exists. e, err := utils.Exists(statePath) if err != nil { return err } else if !e { return nil } // Load and decode the file. _, err = toml.DecodeFile(statePath, &s) if err != nil { return fmt.Errorf("failed to load turtle state file '%s': %v", statePath, err) } // Get all apps. curApps := apps.Apps() // Collect all errors during startup. var allErr string // Start the apps which where running during the last turtle daemon shutdown. for _, rApp := range s.RunningApps { for _, app := range curApps { // Skip if this is not our searched app or if it is already running. if rApp != app.Name() || app.IsRunning() { continue } // Start the app. err = app.Start() if err != nil { allErr += err.Error() + "\n" } } } // Trim the new linew at the end. allErr = strings.TrimSpace(allErr) // Return the error(s) if present. if len(allErr) > 0 { return fmt.Errorf(allErr) } return nil }
// Remove the app. func (a *App) Remove(removeBackups bool) error { // Lock the task mutex. // The app should not be started during a remove process. a.taskMutex.Lock() defer a.taskMutex.Unlock() // Abort if any app task is running. if a.IsTaskRunning() { return fmt.Errorf("the app is running!") } if !removeBackups { // Create a backup first. // Call the private method, because we locked the taskMutex already. err := a.backup() if err != nil { return err } } // Remove the app subvolume if it exists. e, err := utils.Exists(a.path) if err != nil { return err } else if e { if err = btrfs.DeleteSubvolume(a.path); err != nil { return err } } func() { // Lock the apps mutex. appsMutex.Lock() defer appsMutex.Unlock() // Remove the app from the apps map. delete(apps, a.name) }() // Remove all backups if requested. if removeBackups { if err = a.RemoveAllBackups(); err != nil { return err } } return nil }
// Turtlefile returns the app's turtlefile. func (a *App) Turtlefile() (*turtlefile.Turtlefile, error) { // Lock the mutex. a.turtlefileMutex.Lock() defer a.turtlefileMutex.Unlock() // Only load the turtlefile if not already loaded. if a.turtlefile != nil { return a.turtlefile, nil } // Obtain the turtlefile path. turtlefilePath := a.SourceDirectoryPath() + "/" + turtlefile.TurtlefileFilename tStat, err := os.Stat(turtlefilePath) if os.IsNotExist(err) { return nil, fmt.Errorf("Turtlefile is missing in source directory!") } else if err != nil { return nil, fmt.Errorf("failed to obtain state of file '%s': %v", turtlefilePath, err) } // If the path is a directory, then check if the turtlefile exists in it. if tStat.IsDir() { turtlefilePath += "/" + turtlefile.TurtlefileFilename e, err := utils.Exists(turtlefilePath) if err != nil { return nil, fmt.Errorf("failed to check if file exists '%s': %v", turtlefilePath, err) } else if !e { return nil, fmt.Errorf("Turtlefile is missing in source directory!") } } // Load the turtlefile. t, err := turtlefile.Load(turtlefilePath) if err != nil { return nil, err } // Check if the turtlefile is valid. if err = t.IsValid(); err != nil { return nil, fmt.Errorf("the turtlefile is invalid: %v", err) } // Set the app's turtlefile pointer. a.turtlefile = t return t, nil }
// Add and register a new turtle App. // A turtle App name should not not contain any whitespaces. func Add(name, sourceURL, branch string) (err error) { var a *App // Cleanup on any error. defer func() { if err == nil || a == nil { return } // Remove the app subvolume if it exists. e, errC := utils.Exists(a.path) if errC != nil { log.Errorf("failed to cleanup failed add app action: %v", errC) } else if e { if errC = btrfs.DeleteSubvolume(a.path); errC != nil { log.Errorf("failed to cleanup failed add app action: %v", errC) } } }() // Lock the mutex. appsMutex.Lock() defer appsMutex.Unlock() // Check if an app with the same name already exists. _, ok := apps[name] if ok { return fmt.Errorf("an App with the name '%s' already exists!", name) } // Create a new app value. a, err = newApp(name) if err != nil { return fmt.Errorf("failed to create add: %v", err) } // Set the app's settings. a.settings.SourceURL = sourceURL a.settings.Branch = branch // Create the app's subvolume. if err = btrfs.CreateSubvolume(a.path); err != nil { return fmt.Errorf("failed to prepare app's environment: %v", err) } // Save the app's settings to the app settings file. if err = a.saveSettings(); err != nil { return fmt.Errorf("failed to prepare app's environment: %v", err) } // Create the source directory. err = utils.MkDirIfNotExists(a.SourceDirectoryPath()) if err != nil { return fmt.Errorf("failed to prepare app's environment: %v", err) } // Clone the source in a new task. if err = a.cloneSource(); err != nil { return err } // Finally add the app to the map. apps[name] = a return nil }