    module Propellor.Property.OS (
    	cleanInstallOnce,
    	Confirmation(..),
    	preserveNetwork,
    	preserveResolvConf,
    	preserveRootSshAuthorized,
    	oldOSRemoved,
    ) where

    import Propellor.Base
    import qualified Propellor.Property.Debootstrap as Debootstrap
    import qualified Propellor.Property.Ssh as Ssh
    import qualified Propellor.Property.Network as Network
    import qualified Propellor.Property.User as User
    import qualified Propellor.Property.File as File
    import qualified Propellor.Property.Reboot as Reboot
    import Propellor.Property.Mount
    import Propellor.Property.Chroot.Util (stdPATH)

    import System.Posix.Files (rename, fileExist)
    import Control.Exception (throw)

    -- | Replaces whatever OS was installed before with a clean installation
    -- of the OS that the Host is configured to have.
    --
    -- This is experimental; use with caution!
    --
    -- This can replace one Linux distribution with different one.
    -- But, it can also fail and leave the system in an unbootable state.
    --
    -- To avoid this property being accidentially used, you have to provide
    -- a Confirmation containing the name of the host that you intend to apply
    -- the property to.
    --
    -- This property only runs once. The cleanly installed system will have
    -- a file </etc/propellor-cleaninstall>, which indicates it was cleanly
    -- installed.
    --
    -- The files from the old os will be left in </old-os>
    --
    -- After the OS is installed, and if all properties of the host have
    -- been successfully satisfied, the host will be rebooted to properly load
    -- the new OS.
    --
    -- You will typically want to run some more properties after the clean
    -- install succeeds, to bootstrap from the cleanly installed system to
    -- a fully working system. For example:
    --
    -- > & osDebian Unstable X86_64
    -- > & cleanInstallOnce (Confirmed "foo.example.com")
    -- >    `onChange` propertyList "fixing up after clean install"
    -- >        [ preserveNetwork
    -- >        , preserveResolvConf
    -- >        , preserveRootSshAuthorized
    -- >        , Apt.update
    -- >        -- , Grub.boots "/dev/sda"
    -- >        --   `requires` Grub.installed Grub.PC
    -- >        -- , oldOsRemoved (Confirmed "foo.example.com")
    -- >        ]
    -- > & Hostname.sane
    -- > & Hostname.mailname
    -- > & Apt.installed ["linux-image-amd64"]
    -- > & Apt.installed ["ssh"]
    -- > & User.hasSomePassword "root"
    -- > & User.accountFor "joey"
    -- > & User.hasSomePassword "joey"
    -- > -- rest of system properties here
    cleanInstallOnce :: Confirmation -> Property DebianLike
    cleanInstallOnce :: Confirmation -> Property DebianLike
    cleanInstallOnce confirmation :: Confirmation
    confirmation = IO Bool -> Property DebianLike -> Property DebianLike
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check (Bool -> Bool
    not (Bool -> Bool) -> IO Bool -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO Bool
    doesFileExist FilePath
    flagfile) (Property DebianLike -> Property DebianLike)
    -> Property DebianLike -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $
    	Property DebianLike
    go Property DebianLike
    -> Property UnixLike
    -> CombinedType (Property DebianLike) (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` FilePath -> Confirmation -> Property UnixLike
    confirmed "clean install confirmed" Confirmation
    confirmation
      where
    	go :: CombinedType (Property DebianLike) (Property Linux)
    go =
    		Property UnixLike
    finalized
    			Property UnixLike
    -> Property DebianLike
    -> CombinedType (Property UnixLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires`
    		-- easy to forget and system may not boot without shadow pw!
    		Bool -> Property DebianLike
    User.shadowConfig Bool
    True
    			Property DebianLike
    -> Property Linux
    -> CombinedType (Property DebianLike) (Property Linux)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires`
    		-- reboot at end if the rest of the propellor run succeeds
    		Bool -> (Result -> Bool) -> Property Linux
    Reboot.atEnd Bool
    True (Result -> Result -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Result
    FailedChange)
    			Property DebianLike
    -> Property UnixLike
    -> CombinedType (Property DebianLike) (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires`
    		Property UnixLike
    propellorbootstrapped
    			Property DebianLike
    -> Property Linux
    -> CombinedType (Property DebianLike) (Property Linux)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires`
    		Property Linux
    flipped
    			Property DebianLike
    -> Property Linux
    -> CombinedType (Property DebianLike) (Property Linux)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires`
    		Property Linux
    osbootstrapped

    	osbootstrapped :: Property Linux
    	osbootstrapped :: Property Linux
    osbootstrapped = FilePath
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux]
        -> Maybe System -> Propellor Result)
    -> Property Linux
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath
    -> (OuterMetaTypesWitness metatypes
        -> Maybe System -> Propellor Result)
    -> Property (MetaTypes metatypes)
    withOS (FilePath
    newOSDir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " bootstrapped") ((OuterMetaTypesWitness
        '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux]
      -> Maybe System -> Propellor Result)
     -> Property Linux)
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux]
        -> Maybe System -> Propellor Result)
    -> Property Linux
    forall a b. (a -> b) -> a -> b
    $ \w :: OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    w o :: Maybe System
    o -> case Maybe System
    o of
    		(Just d :: System
    d@(System (Debian _ _) _)) -> OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    -> Property Linux -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    w (Property Linux -> Propellor Result)
    -> Property Linux -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $
    			System -> Property Linux
    debootstrap System
    d
    		(Just u :: System
    u@(System (Buntish _) _)) -> OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    -> Property Linux -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    w (Property Linux -> Propellor Result)
    -> Property Linux -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $
    			System -> Property Linux
    debootstrap System
    u
    		_ -> Propellor Result
    HasCallStack => Propellor Result
    unsupportedOS'

    	debootstrap :: System -> Property Linux
    	debootstrap :: System -> Property Linux
    debootstrap targetos :: System
    targetos =
    		-- Install debootstrap from source, since we don't know
    		-- what OS we're currently running in.
    		Property Linux
    -> FilePath -> System -> DebootstrapConfig -> Property Linux
    Debootstrap.built' Property Linux
    Debootstrap.sourceInstall
    			FilePath
    newOSDir System
    targetos DebootstrapConfig
    Debootstrap.DefaultConfig
    		-- debootstrap, I wish it was faster..
    		-- TODO eatmydata to speed it up
    		-- Problem: Installing eatmydata on some random OS like
    		-- Fedora may be difficult. Maybe configure dpkg to not
    		-- sync instead?

    	-- This is the fun bit.
    	flipped :: Property Linux
    	flipped :: Property Linux
    flipped = FilePath -> Propellor Result -> Property Linux
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property (FilePath
    newOSDir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " moved into place") (Propellor Result -> Property Linux)
    -> Propellor Result -> Property Linux
    forall a b. (a -> b) -> a -> b
    $ IO Result -> Propellor Result
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO Result -> Propellor Result) -> IO Result -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ do
    		-- First, unmount most mount points, lazily, so
    		-- they don't interfere with moving things around.
    		FilePath
    devfstype <- FilePath -> Maybe FilePath -> FilePath
    forall a. a -> Maybe a -> a
    fromMaybe "devtmpfs" (Maybe FilePath -> FilePath) -> IO (Maybe FilePath) -> IO FilePath
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO (Maybe FilePath)
    getFsType "/dev"
    		[FilePath]
    mnts <- (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (FilePath -> [FilePath] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` ("/"FilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    : [FilePath]
    trickydirs)) ([FilePath] -> [FilePath]) -> IO [FilePath] -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> IO [FilePath]
    mountPoints
    		-- reverse so that deeper mount points come first
    		[FilePath] -> (FilePath -> IO ()) -> IO ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    t a -> (a -> m b) -> m ()
    forM_ ([FilePath] -> [FilePath]
    forall a. [a] -> [a]
    reverse [FilePath]
    mnts) FilePath -> IO ()
    umountLazy

    		[(FilePath, FilePath, IO Bool)]
    renamesout <- (FilePath -> (FilePath, FilePath, IO Bool))
    -> [FilePath] -> [(FilePath, FilePath, IO Bool)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\d :: FilePath
    d -> (FilePath
    d, FilePath
    oldOSDir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    d, Bool -> IO Bool
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Bool -> IO Bool) -> Bool -> IO Bool
    forall a b. (a -> b) -> a -> b
    $ FilePath
    d FilePath -> [FilePath] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` (FilePath
    oldOSDirFilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    :FilePath
    newOSDirFilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    :[FilePath]
    trickydirs)))
    			([FilePath] -> [(FilePath, FilePath, IO Bool)])
    -> IO [FilePath] -> IO [(FilePath, FilePath, IO Bool)]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO [FilePath]
    dirContents "/"
    		[(FilePath, FilePath, IO Bool)]
    renamesin <- (FilePath -> (FilePath, FilePath, IO Bool))
    -> [FilePath] -> [(FilePath, FilePath, IO Bool)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\d :: FilePath
    d -> let dest :: FilePath
    dest = "/" FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> FilePath
    takeFileName FilePath
    d in (FilePath
    d, FilePath
    dest, Bool -> Bool
    not (Bool -> Bool) -> IO Bool -> IO Bool
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO Bool
    fileExist FilePath
    dest))
    			([FilePath] -> [(FilePath, FilePath, IO Bool)])
    -> IO [FilePath] -> IO [(FilePath, FilePath, IO Bool)]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO [FilePath]
    dirContents FilePath
    newOSDir
    		Bool -> FilePath -> IO ()
    createDirectoryIfMissing Bool
    True FilePath
    oldOSDir
    		[(FilePath, FilePath, IO Bool)] -> IO ()
    massRename ([(FilePath, FilePath, IO Bool)]
    renamesout [(FilePath, FilePath, IO Bool)]
    -> [(FilePath, FilePath, IO Bool)]
    -> [(FilePath, FilePath, IO Bool)]
    forall a. [a] -> [a] -> [a]
    ++ [(FilePath, FilePath, IO Bool)]
    renamesin)
    		FilePath -> IO ()
    removeDirectoryRecursive FilePath
    newOSDir

    		-- Prepare environment for running additional properties,
    		-- overriding old OS's environment.
    		IO () -> IO ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath -> Bool -> IO ()
    setEnv "PATH" FilePath
    stdPATH Bool
    True
    		IO () -> IO ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> IO ()
    unsetEnv "LANG"

    		-- Remount /dev, so that block devices etc are
    		-- available for other properties to use.
    		IO Bool -> IO () -> IO ()
    forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
    unlessM (FilePath -> FilePath -> FilePath -> MountOpts -> IO Bool
    mount FilePath
    devfstype FilePath
    devfstype "/dev" MountOpts
    forall a. Monoid a => a
    mempty) (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ do
    			FilePath -> IO ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage (FilePath -> IO ()) -> FilePath -> IO ()
    forall a b. (a -> b) -> a -> b
    $ "failed mounting /dev using " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    devfstype FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ "; falling back to MAKEDEV generic"
    			IO Bool -> IO ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void (IO Bool -> IO ()) -> IO Bool -> IO ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> [CommandParam] -> IO Bool
    boolSystem "sh" [FilePath -> CommandParam
    Param "-c", FilePath -> CommandParam
    Param "cd /dev && /sbin/MAKEDEV generic"]

    		-- Mount /sys too, needed by eg, grub-mkconfig.
    		IO Bool -> IO () -> IO ()
    forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
    unlessM (FilePath -> FilePath -> FilePath -> MountOpts -> IO Bool
    mount "sysfs" "sysfs" "/sys" MountOpts
    forall a. Monoid a => a
    mempty) (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $
    			FilePath -> IO ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage "failed mounting /sys"

    		-- And /dev/pts, used by apt.
    		IO Bool -> IO () -> IO ()
    forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
    unlessM (FilePath -> FilePath -> FilePath -> MountOpts -> IO Bool
    mount "devpts" "devpts" "/dev/pts" MountOpts
    forall a. Monoid a => a
    mempty) (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $
    			FilePath -> IO ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage "failed mounting /dev/pts"

    		Result -> IO Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    MadeChange

    	propellorbootstrapped :: Property UnixLike
    	propellorbootstrapped :: Property UnixLike
    propellorbootstrapped = FilePath -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property "propellor re-debootstrapped in new os" (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $
    		Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    NoChange
    		-- re-bootstrap propellor in /usr/local/propellor,
    		--   (using git repo bundle, privdata file, and possibly
    		--   git repo url, which all need to be arranged to
    		--   be present in /old-os's /usr/local/propellor)
    		-- TODO

    	finalized :: Property UnixLike
    	finalized :: Property UnixLike
    finalized = FilePath -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property "clean OS installed" (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ do
    		IO () -> Propellor ()
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO () -> Propellor ()) -> IO () -> Propellor ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> FilePath -> IO ()
    writeFile FilePath
    flagfile ""
    		Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    MadeChange

    	flagfile :: FilePath
    flagfile = "/etc/propellor-cleaninstall"

    	trickydirs :: [FilePath]
    trickydirs =
    		-- /tmp can contain X's sockets, which prevent moving it
    		-- so it's left as-is.
    		[ "/tmp"
    		-- /proc is left mounted
    		, "/proc"
    		]

    -- Performs all the renames. If any rename fails, rolls back all
    -- previous renames. Thus, this either successfully performs all
    -- the renames, or does not change the system state at all.
    massRename :: [(FilePath, FilePath, IO Bool)] -> IO ()
    massRename :: [(FilePath, FilePath, IO Bool)] -> IO ()
    massRename = [(FilePath, FilePath)] -> [(FilePath, FilePath, IO Bool)] -> IO ()
    go []
      where
    	go :: [(FilePath, FilePath)] -> [(FilePath, FilePath, IO Bool)] -> IO ()
    go _ [] = () -> IO ()
    forall (m :: * -> *) a. Monad m => a -> m a
    return ()
    	go undo :: [(FilePath, FilePath)]
    undo ((from :: FilePath
    from, to :: FilePath
    to, test :: IO Bool
    test):rest :: [(FilePath, FilePath, IO Bool)]
    rest) = IO Bool -> (IO (), IO ()) -> IO ()
    forall (m :: * -> *) a. Monad m => m Bool -> (m a, m a) -> m a
    ifM IO Bool
    test
    		( IO () -> IO (Either SomeException ())
    forall (m :: * -> *) a.
    MonadCatch m =>
    m a -> m (Either SomeException a)
    tryNonAsync (FilePath -> FilePath -> IO ()
    rename FilePath
    from FilePath
    to)
    			IO (Either SomeException ())
    -> (Either SomeException () -> IO ()) -> IO ()
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= (SomeException -> IO ())
    -> (() -> IO ()) -> Either SomeException () -> IO ()
    forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
    either
    				([(FilePath, FilePath)] -> SomeException -> IO ()
    forall (t :: * -> *) e b.
    (Foldable t, Exception e) =>
    t (FilePath, FilePath) -> e -> IO b
    rollback [(FilePath, FilePath)]
    undo)
    				(IO () -> () -> IO ()
    forall a b. a -> b -> a
    const (IO () -> () -> IO ()) -> IO () -> () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ [(FilePath, FilePath)] -> [(FilePath, FilePath, IO Bool)] -> IO ()
    go ((FilePath
    to, FilePath
    from)(FilePath, FilePath)
    -> [(FilePath, FilePath)] -> [(FilePath, FilePath)]
    forall a. a -> [a] -> [a]
    :[(FilePath, FilePath)]
    undo) [(FilePath, FilePath, IO Bool)]
    rest)
    		, [(FilePath, FilePath)] -> [(FilePath, FilePath, IO Bool)] -> IO ()
    go [(FilePath, FilePath)]
    undo [(FilePath, FilePath, IO Bool)]
    rest
    		)
    	rollback :: t (FilePath, FilePath) -> e -> IO b
    rollback undo :: t (FilePath, FilePath)
    undo e :: e
    e = do
    		((FilePath, FilePath) -> IO ()) -> t (FilePath, FilePath) -> IO ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ ((FilePath -> FilePath -> IO ()) -> (FilePath, FilePath) -> IO ()
    forall a b c. (a -> b -> c) -> (a, b) -> c
    uncurry FilePath -> FilePath -> IO ()
    rename) t (FilePath, FilePath)
    undo
    		e -> IO b
    forall a e. Exception e => e -> a
    throw e
    e

    data Confirmation = Confirmed HostName

    confirmed :: Desc -> Confirmation -> Property UnixLike
    confirmed :: FilePath -> Confirmation -> Property UnixLike
    confirmed desc :: FilePath
    desc (Confirmed c :: FilePath
    c) = FilePath -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property FilePath
    desc (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ do
    	FilePath
    hostname <- (Host -> FilePath) -> Propellor FilePath
    forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
    asks Host -> FilePath
    hostName
    	if FilePath
    hostname FilePath -> FilePath -> Bool
    forall a. Eq a => a -> a -> Bool
    /= FilePath
    c
    		then do
    			FilePath -> Propellor ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage "Run with a bad confirmation, not matching hostname."
    			Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    FailedChange
    		else Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    NoChange

    -- | </etc/network/interfaces> is configured to bring up the network
    -- interface that currently has a default route configured, using
    -- the same (static) IP address.
    preserveNetwork :: Property DebianLike
    preserveNetwork :: Property DebianLike
    preserveNetwork = Property DebianLike
    go Property DebianLike
    -> Property DebianLike
    -> CombinedType (Property DebianLike) (Property DebianLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` Property DebianLike
    Network.cleanInterfacesFile
      where
    	go :: Property DebianLike
    	go :: Property DebianLike
    go = FilePath
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
        -> Propellor Result)
    -> Property DebianLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath
    -> (OuterMetaTypesWitness metatypes -> Propellor Result)
    -> Property (MetaTypes metatypes)
    property' "preserve network configuration" ((OuterMetaTypesWitness
        '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
      -> Propellor Result)
     -> Property DebianLike)
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
        -> Propellor Result)
    -> Property DebianLike
    forall a b. (a -> b) -> a -> b
    $ \w :: OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    w -> do
    		[FilePath]
    ls <- IO [FilePath] -> Propellor [FilePath]
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO [FilePath] -> Propellor [FilePath])
    -> IO [FilePath] -> Propellor [FilePath]
    forall a b. (a -> b) -> a -> b
    $ FilePath -> [FilePath]
    lines (FilePath -> [FilePath]) -> IO FilePath -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> [FilePath] -> IO FilePath
    readProcess "ip"
    			["route", "list", "scope", "global"]
    		case FilePath -> [FilePath]
    words (FilePath -> [FilePath]) -> Maybe FilePath -> Maybe [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> [FilePath] -> Maybe FilePath
    forall a. [a] -> Maybe a
    headMaybe [FilePath]
    ls of
    			Just ("default":"via":_:"dev":iface :: FilePath
    iface:_) ->
    				OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    -> Property DebianLike -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish]
    w (Property DebianLike -> Propellor Result)
    -> Property DebianLike -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ FilePath -> Property DebianLike
    Network.preserveStatic FilePath
    iface
    			_ -> do
    				FilePath -> Propellor ()
    forall (m :: * -> *). MonadIO m => FilePath -> m ()
    warningMessage "did not find any default ipv4 route"
    				Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    FailedChange

    -- | </etc/resolv.conf> is copied from the old OS
    preserveResolvConf :: Property Linux
    preserveResolvConf :: Property Linux
    preserveResolvConf = IO Bool -> Property Linux -> Property Linux
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check (FilePath -> IO Bool
    fileExist FilePath
    oldloc) (Property Linux -> Property Linux)
    -> Property Linux -> Property Linux
    forall a b. (a -> b) -> a -> b
    $
    	FilePath
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux]
        -> Propellor Result)
    -> Property Linux
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath
    -> (OuterMetaTypesWitness metatypes -> Propellor Result)
    -> Property (MetaTypes metatypes)
    property' (FilePath
    newloc FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " copied from old OS") ((OuterMetaTypesWitness
        '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux]
      -> Propellor Result)
     -> Property Linux)
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux]
        -> Propellor Result)
    -> Property Linux
    forall a b. (a -> b) -> a -> b
    $ \w :: OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    w -> do
    		[FilePath]
    ls <- IO [FilePath] -> Propellor [FilePath]
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO [FilePath] -> Propellor [FilePath])
    -> IO [FilePath] -> Propellor [FilePath]
    forall a b. (a -> b) -> a -> b
    $ FilePath -> [FilePath]
    lines (FilePath -> [FilePath]) -> IO FilePath -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO FilePath
    readFile FilePath
    oldloc
    		OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    -> Property UnixLike -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux]
    w (Property UnixLike -> Propellor Result)
    -> Property UnixLike -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ FilePath
    newloc FilePath -> [FilePath] -> Property UnixLike
    `File.hasContent` [FilePath]
    ls
      where
    	newloc :: FilePath
    newloc = "/etc/resolv.conf"
    	oldloc :: FilePath
    oldloc = FilePath
    oldOSDir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    newloc

    -- | </root/.ssh/authorized_keys> has added to it any ssh keys that
    -- were authorized in the old OS. Any other contents of the file are
    -- retained.
    preserveRootSshAuthorized :: Property UnixLike
    preserveRootSshAuthorized :: Property UnixLike
    preserveRootSshAuthorized = IO Bool -> Property UnixLike -> Property UnixLike
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check (FilePath -> IO Bool
    fileExist FilePath
    oldloc) (Property UnixLike -> Property UnixLike)
    -> Property UnixLike -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $
    	FilePath
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
        -> Propellor Result)
    -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath
    -> (OuterMetaTypesWitness metatypes -> Propellor Result)
    -> Property (MetaTypes metatypes)
    property' FilePath
    desc ((OuterMetaTypesWitness
        '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
           'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
      -> Propellor Result)
     -> Property UnixLike)
    -> (OuterMetaTypesWitness
          '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
             'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
        -> Propellor Result)
    -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ \w :: OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
    w -> do
    		[FilePath]
    ks <- IO [FilePath] -> Propellor [FilePath]
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO [FilePath] -> Propellor [FilePath])
    -> IO [FilePath] -> Propellor [FilePath]
    forall a b. (a -> b) -> a -> b
    $ FilePath -> [FilePath]
    lines (FilePath -> [FilePath]) -> IO FilePath -> IO [FilePath]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> FilePath -> IO FilePath
    readFile FilePath
    oldloc
    		OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
    -> Property UnixLike -> Propellor Result
    forall (inner :: [MetaType]) (outer :: [MetaType]).
    EnsurePropertyAllowed inner outer =>
    OuterMetaTypesWitness outer
    -> Property (MetaTypes inner) -> Propellor Result
    ensureProperty OuterMetaTypesWitness
      '[ 'Targeting 'OSDebian, 'Targeting 'OSBuntish,
         'Targeting 'OSArchLinux, 'Targeting 'OSFreeBSD]
    w (Property UnixLike -> Propellor Result)
    -> Property UnixLike -> Propellor Result
    forall a b. (a -> b) -> a -> b
    $ FilePath -> Props UnixLike -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath
    -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
    combineProperties FilePath
    desc (Props UnixLike -> Property UnixLike)
    -> Props UnixLike -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $
    			[Property UnixLike] -> Props UnixLike
    forall k (metatypes :: k).
    [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
    toProps ([Property UnixLike] -> Props UnixLike)
    -> [Property UnixLike] -> Props UnixLike
    forall a b. (a -> b) -> a -> b
    $ (FilePath -> Property UnixLike)
    -> [FilePath] -> [Property UnixLike]
    forall a b. (a -> b) -> [a] -> [b]
    map (RevertableProperty UnixLike UnixLike -> Property UnixLike
    forall setupmetatypes undometatypes.
    RevertableProperty setupmetatypes undometatypes
    -> Property setupmetatypes
    setupRevertableProperty (RevertableProperty UnixLike UnixLike -> Property UnixLike)
    -> (FilePath -> RevertableProperty UnixLike UnixLike)
    -> FilePath
    -> Property UnixLike
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . User -> FilePath -> RevertableProperty UnixLike UnixLike
    Ssh.authorizedKey (FilePath -> User
    User "root")) [FilePath]
    ks
      where
    	desc :: FilePath
    desc = FilePath
    newloc FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " copied from old OS"
    	newloc :: FilePath
    newloc = "/root/.ssh/authorized_keys"
    	oldloc :: FilePath
    oldloc = FilePath
    oldOSDir FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    newloc

    -- Removes the old OS's backup from </old-os>
    oldOSRemoved :: Confirmation -> Property UnixLike
    oldOSRemoved :: Confirmation -> Property UnixLike
    oldOSRemoved confirmation :: Confirmation
    confirmation = IO Bool -> Property UnixLike -> Property UnixLike
    forall (p :: * -> *) i (m :: * -> *).
    (Checkable p i, LiftPropellor m) =>
    m Bool -> p i -> Property i
    check (FilePath -> IO Bool
    doesDirectoryExist FilePath
    oldOSDir) (Property UnixLike -> Property UnixLike)
    -> Property UnixLike -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $
    	Property UnixLike
    go Property UnixLike
    -> Property UnixLike
    -> CombinedType (Property UnixLike) (Property UnixLike)
    forall x y. Combines x y => x -> y -> CombinedType x y
    `requires` FilePath -> Confirmation -> Property UnixLike
    confirmed "old OS backup removal confirmed" Confirmation
    confirmation
      where
    	go :: Property UnixLike
    	go :: Property UnixLike
    go = FilePath -> Propellor Result -> Property UnixLike
    forall k (metatypes :: k).
    SingI metatypes =>
    FilePath -> Propellor Result -> Property (MetaTypes metatypes)
    property "old OS backup removed" (Propellor Result -> Property UnixLike)
    -> Propellor Result -> Property UnixLike
    forall a b. (a -> b) -> a -> b
    $ do
    		IO () -> Propellor ()
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO () -> Propellor ()) -> IO () -> Propellor ()
    forall a b. (a -> b) -> a -> b
    $ FilePath -> IO ()
    removeDirectoryRecursive FilePath
    oldOSDir
    		Result -> Propellor Result
    forall (m :: * -> *) a. Monad m => a -> m a
    return Result
    MadeChange

    oldOSDir :: FilePath
    oldOSDir :: FilePath
    oldOSDir = "/old-os"

    newOSDir :: FilePath
    newOSDir :: FilePath
    newOSDir = "/new-os"