-- SPDX-License-Identifier: BSD-3-Clause module Main (main) where import Control.Monad.Extra (unless, void, when, whenJust) import Data.List (isPrefixOf, isInfixOf) import Data.Maybe (isNothing) import SimpleCmd (cmd, cmd_, cmdStdErr, error', grep_, pipeBool, (+-+)) import SimplePrompt (prompt_, yesno) import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, removeFile, renameFile) import System.Environment.XDG.BaseDir (getUserCacheDir) import System.Exit (ExitCode(ExitSuccess)) import System.FilePath (()) import System.IO (BufferMode(..), hSetBuffering, openFile, stdout, IOMode(WriteMode)) import System.Process (runProcess,waitForProcess) rpmostree :: String rpmostree = "/usr/bin/rpm-ostree" data Mode = Update | Changelog deriving Eq instance Show Mode where show Update = "update" show Changelog = "changelog" modeArgs :: Mode -> [String] modeArgs Update = ["update", "--preview"] modeArgs Changelog = ["db", "diff", "-c"] main :: IO () main = do hSetBuffering stdout NoBuffering let sysrootDir = "/sysroot" sysroot <- doesDirectoryExist sysrootDir unless sysroot $ error' $ "no" +-+ sysrootDir +-+ "found: not a rpm-ostree system?" exists <- doesFileExist rpmostree unless exists $ error' $ rpmostree +-+ ": not found" cachedir <- getUserCacheDir "rpmostree-updates" createDirectoryIfMissing True cachedir -- whether latest is a staged deployment staged <- ("true" `isInfixOf`) <$> cmd rpmostree ["status", "-J", "$.deployments[0].staged"] unless staged $ putStrLn "current latest deployment is live" changed <- cachedRpmOstree staged cachedir Update when changed $ do prompt_ "Press Enter to update" cmd_ rpmostree ["update"] showChangelog <- yesno (Just changed) "Show changelog" when showChangelog $ void $ cachedRpmOstree staged cachedir Changelog cachedRpmOstree :: Bool -> FilePath -> Mode -> IO Bool cachedRpmOstree staged cachedir mode = do let latest = cachedir "latest-" ++ show mode mprevious <- cacheFile latest ok <- if isNothing mprevious then pipeBool (rpmostree, modeArgs mode) ("tee", [latest]) else cmdToFile (rpmostree, modeArgs mode) latest if not ok then do whenJust mprevious $ \previous -> renameFile previous latest return False else case mprevious of Just previous -> do (diff,_err) <- cmdStdErr "diff" ["-u0", previous, latest] let diffs = lines diff when (length (filter (not . noise) diffs) > 3) $ putStrLn $ "Diff" +-+ (if mode == Update then "preview" else "") +-+ "with last rpm-ostree update:\n" ++ unlines diffs if length diffs <= 9 -- FIXME print old timestamp then do putStrLn "no new changes" return False else return True Nothing -> not <$> grep_ "No updates available." latest where cacheFile :: FilePath -> IO (Maybe FilePath) cacheFile latestCache = do let previousCache = cachedir "previous-" ++ show mode haveLatest <- doesFileExist latestCache if haveLatest then do useCache <- case mode of Update -> return staged Changelog -> do -- ostree diff commit from: booted deployment (81ad560ed850559259e46d5739e8d9ac8714fbcbbd0d24257a8934db7730edab) cachedBootedDeployment <- init . tail . last . words <$> cmd "head" ["-1", latestCache] bootedChecksum <- cmd rpmostree ["status", "-b", "-J", "$.deployments[0].checksum"] return $ cachedBootedDeployment `isInfixOf` bootedChecksum if useCache then do renameFile latestCache previousCache return $ Just previousCache else do removeFile latestCache return Nothing else return Nothing noise :: String -> Bool noise cs = "@@ " `isPrefixOf` cs || " 0 content objects fetched; " `isInfixOf` cs cmdToFile :: (String, [String]) -> FilePath -> IO Bool cmdToFile (c,args) file = do h <- openFile file WriteMode p <- runProcess c args Nothing Nothing Nothing (Just h) Nothing ret <- waitForProcess p -- FIXME replace with boolWrapper return $ ret == ExitSuccess