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