111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
package installer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
var (
|
|
ErrUnsafeInstallLayout = errors.New("unsafe install directory layout")
|
|
ErrStagingMissing = errors.New("staging directory is missing")
|
|
ErrBackupExists = errors.New("backup directory already exists")
|
|
)
|
|
|
|
type appLayout struct {
|
|
root string
|
|
current string
|
|
staging string
|
|
backup string
|
|
transaction string
|
|
transactionBackup string
|
|
}
|
|
|
|
type directoryState struct {
|
|
current bool
|
|
staging bool
|
|
backup bool
|
|
}
|
|
|
|
func inspectAppLayout(root string) (appLayout, error) {
|
|
if root == "" {
|
|
return appLayout{}, fmt.Errorf("%w: empty root", ErrUnsafeInstallLayout)
|
|
}
|
|
absolute, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return appLayout{}, fmt.Errorf("%w: %v", ErrUnsafeInstallLayout, err)
|
|
}
|
|
info, err := os.Lstat(absolute)
|
|
if err != nil {
|
|
return appLayout{}, fmt.Errorf("%w: root: %v", ErrUnsafeInstallLayout, err)
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
|
return appLayout{}, fmt.Errorf("%w: root is not a real directory", ErrUnsafeInstallLayout)
|
|
}
|
|
transaction, transactionBackup := transactionPaths(absolute)
|
|
return appLayout{
|
|
root: absolute,
|
|
current: filepath.Join(absolute, "current"),
|
|
staging: filepath.Join(absolute, "staging"),
|
|
backup: filepath.Join(absolute, "backup"),
|
|
transaction: transaction,
|
|
transactionBackup: transactionBackup,
|
|
}, nil
|
|
}
|
|
|
|
func inspectDirectories(layout appLayout) (directoryState, error) {
|
|
current, err := inspectManagedDirectory(layout.current)
|
|
if err != nil {
|
|
return directoryState{}, err
|
|
}
|
|
staging, err := inspectManagedDirectory(layout.staging)
|
|
if err != nil {
|
|
return directoryState{}, err
|
|
}
|
|
backup, err := inspectManagedDirectory(layout.backup)
|
|
if err != nil {
|
|
return directoryState{}, err
|
|
}
|
|
return directoryState{
|
|
current: current,
|
|
staging: staging,
|
|
backup: backup,
|
|
}, nil
|
|
}
|
|
|
|
func inspectManagedDirectory(path string) (bool, error) {
|
|
info, err := os.Lstat(path)
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, fmt.Errorf("%w: inspect %s: %v", ErrUnsafeInstallLayout, path, err)
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
|
return false, fmt.Errorf("%w: %s is not a real directory", ErrUnsafeInstallLayout, path)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func removeManagedDirectory(layout appLayout, target string) error {
|
|
if filepath.Dir(target) != layout.root {
|
|
return fmt.Errorf("%w: refuse removal outside app root", ErrUnsafeInstallLayout)
|
|
}
|
|
base := filepath.Base(target)
|
|
if base != "current" && base != "staging" && base != "backup" {
|
|
return fmt.Errorf("%w: refuse removal of %s", ErrUnsafeInstallLayout, base)
|
|
}
|
|
exists, err := inspectManagedDirectory(target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return nil
|
|
}
|
|
if err := os.RemoveAll(target); err != nil {
|
|
return fmt.Errorf("remove managed directory %s: %w", base, err)
|
|
}
|
|
return nil
|
|
}
|