run Run{..} = do
    let fullSyncDelay = fromIntegral (fullSyncInterval * 60)
    let cleanupDelay = fromIntegral (cleanupInterval * 60)
    let lockName = ".privatecloud.lock"

    -- GUI can register custom handler for eventLoggerName and display notifications
    -- Here we just write everything to stderr
    updateGlobalLogger eventLoggerName (setLevel NOTICE)
    -- Also print debugging loggers at level requested
    updateGlobalLogger "PrivateCloud" (setLevel loglevel)

    let conflictPattern = "*" ++  conflictSuffix
    patterns <- forM (conflictPattern : lockName : dbName : exclPatterns) $ \pat -> do
        case simplify <$> tryCompileWith compPosix pat of
            Left errmsg -> do
                logEventError $ "INVALID_PATTERN #pattern " ++ show pat
                    ++ " #msg " ++ errmsg
                exitFailure
            Right pattern -> do
                infoM appLoggerName $ "#EXCLUSION #pattern " ++ show pat
                pure pattern

    let getCred uniqueId =
            let credName = "privatecloud-" ++ T.unpack uniqueId
             in withCredentialStore $ \store ->
                getCredential store credName :: IO (Maybe ScrubbedBytes)

    let lockOrDie = tryLockFile (root </> lockName) Exclusive >>= \case
            Nothing -> do
                putStrLn $ "Service already running in " ++ root
                exitFailure
            Just lock -> pure lock

    bracket lockOrDie unlockFile $ const $ runAwsPrivateCloud root patterns getCred $ do
        instanceId <- Cloud.cloudId
        logEventNotice $ "START #root " ++ root ++ " #instance " ++ T.unpack instanceId

        let loop lastFullSyncTime lastCleanupTime = do
                (lfst, lct) <- catchAny (step lastFullSyncTime lastCleanupTime) $
                    \e -> do
                        logEventError $ "#EXCEPTION #msg " ++ show e
                        -- Keep full sync time, but delay database cleanup.
                        -- This way after cloud outage all clients will not
                        -- try to do database cleanup at once.
                        currentTime <- liftIO getCurrentTime
                        pure (lastFullSyncTime, currentTime)
                liftIO $ threadDelay (1000000 * fromIntegral syncInterval)
                loop lfst lct

            step lastFullSyncTime lastCleanupTime = do
                currentTime <- liftIO getCurrentTime

                let sinceLastFullSync = diffUTCTime currentTime lastFullSyncTime
                lfst <- if sinceLastFullSync > fullSyncDelay
                    then do
                        syncAllChanges
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextFullSync " ++ show (fullSyncDelay - sinceLastFullSync)
                        syncRecentChanges
                        pure lastFullSyncTime

                let sinceLastCleanup = diffUTCTime currentTime lastCleanupTime
                lct <- if sinceLastCleanup > cleanupDelay
                    then do
                        runAction cleanupCloud
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextCleanup " ++ show (cleanupDelay - sinceLastCleanup)
                        pure lastCleanupTime

                pure (lfst, lct)

        -- Force first sync to be full, but delay cleanup.
        startTime <- liftIO getCurrentTime
        loop (posixSecondsToUTCTime 0) startTime
run Run{..} = do
    let fullSyncDelay = fromIntegral (fullSyncInterval * 60)
    let cleanupDelay = fromIntegral (cleanupInterval * 60)
    let lockName = ".privatecloud.lock"

    -- GUI can register custom handler for eventLoggerName and display notifications
    -- Here we just write everything to stderr
    updateGlobalLogger eventLoggerName (setLevel NOTICE)
    -- Also print debugging loggers at level requested
    updateGlobalLogger "PrivateCloud" (setLevel loglevel)

    let conflictPattern = "*" ++  conflictSuffix
    patterns <- forM (conflictPattern : lockName : dbName : exclPatterns) $ \pat -> do
        case simplify <$> tryCompileWith compPosix pat of
            Left errmsg -> do
                logEventError $ "INVALID_PATTERN #pattern " ++ show pat
                    ++ " #msg " ++ errmsg
                exitFailure
            Right pattern -> do
                infoM appLoggerName $ "#EXCLUSION #pattern " ++ show pat
                pure pattern

    let getCred uniqueId =
            let credName = "privatecloud-" ++ T.unpack uniqueId
             in withCredentialStore $ \store ->
                getCredential store credName :: IO (Maybe ScrubbedBytes)

    let lockOrDie = tryLockFile (root </> lockName) Exclusive >>= \case
            Nothing -> do
                putStrLn $ "Service already running in " ++ root
                exitFailure
            Just lock -> pure lock

    bracket lockOrDie unlockFile $ const $ runAwsPrivateCloud root patterns getCred $ do
        instanceId <- Cloud.cloudId
        logEventNotice $ "START #root " ++ root ++ " #instance " ++ T.unpack instanceId

        let loop lastFullSyncTime lastCleanupTime = do
                (lfst, lct) <- catchAny (step lastFullSyncTime lastCleanupTime) $
                    \e -> do
                        logEventError $ "#EXCEPTION #msg " ++ show e
                        -- Keep full sync time, but delay database cleanup.
                        -- This way after cloud outage all clients will not
                        -- try to do database cleanup at once.
                        currentTime <- liftIO getCurrentTime
                        pure (lastFullSyncTime, currentTime)
                liftIO $ threadDelay (1000000 * fromIntegral syncInterval)
                loop lfst lct

            step lastFullSyncTime lastCleanupTime = do
                currentTime <- liftIO getCurrentTime

                let sinceLastFullSync = diffUTCTime currentTime lastFullSyncTime
                lfst <- if sinceLastFullSync > fullSyncDelay
                    then do
                        syncAllChanges
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextFullSync " ++ show (fullSyncDelay - sinceLastFullSync)
                        syncRecentChanges
                        pure lastFullSyncTime

                let sinceLastCleanup = diffUTCTime currentTime lastCleanupTime
                lct <- if sinceLastCleanup > cleanupDelay
                    then do
                        runAction cleanupCloud
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextCleanup " ++ show (cleanupDelay - sinceLastCleanup)
                        pure lastCleanupTime

                pure (lfst, lct)

        -- Force first sync to be full, but delay cleanup.
        startTime <- liftIO getCurrentTime
        loop (posixSecondsToUTCTime 0) startTime
run Run{..} = do
    let fullSyncDelay = fromIntegral (fullSyncInterval * 60)
    let cleanupDelay = fromIntegral (cleanupInterval * 60)
    let lockName = ".privatecloud.lock"

    -- GUI can register custom handler for eventLoggerName and display notifications
    -- Here we just write everything to stderr
    updateGlobalLogger eventLoggerName (setLevel NOTICE)
    -- Also print debugging loggers at level requested
    updateGlobalLogger "PrivateCloud" (setLevel loglevel)

    let conflictPattern = "*" ++  conflictSuffix
    patterns <- forM (conflictPattern : lockName : dbName : exclPatterns) $ \pat -> do
        case simplify <$> tryCompileWith compPosix pat of
            Left errmsg -> do
                logEventError $ "INVALID_PATTERN #pattern " ++ show pat
                    ++ " #msg " ++ errmsg
                exitFailure
            Right pattern -> do
                infoM appLoggerName $ "#EXCLUSION #pattern " ++ show pat
                pure pattern

    let getCred uniqueId =
            let credName = "privatecloud-" ++ T.unpack uniqueId
             in withCredentialStore $ \store ->
                getCredential store credName :: IO (Maybe ScrubbedBytes)

    let lockOrDie = tryLockFile (root </> lockName) Exclusive >>= \case
            Nothing -> do
                putStrLn $ "Service already running in " ++ root
                exitFailure
            Just lock -> pure lock

    bracket lockOrDie unlockFile $ const $ runAwsPrivateCloud root patterns getCred $ do
        instanceId <- Cloud.cloudId
        logEventNotice $ "START #root " ++ root ++ " #instance " ++ T.unpack instanceId

        let loop lastFullSyncTime lastCleanupTime = do
                (lfst, lct) <- catchAny (step lastFullSyncTime lastCleanupTime) $
                    \e -> do
                        logEventError $ "#EXCEPTION #msg " ++ show e
                        -- Keep full sync time, but delay database cleanup.
                        -- This way after cloud outage all clients will not
                        -- try to do database cleanup at once.
                        currentTime <- liftIO getCurrentTime
                        pure (lastFullSyncTime, currentTime)
                liftIO $ threadDelay (1000000 * fromIntegral syncInterval)
                loop lfst lct

            step lastFullSyncTime lastCleanupTime = do
                currentTime <- liftIO getCurrentTime

                let sinceLastFullSync = diffUTCTime currentTime lastFullSyncTime
                lfst <- if sinceLastFullSync > fullSyncDelay
                    then do
                        syncAllChanges
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextFullSync " ++ show (fullSyncDelay - sinceLastFullSync)
                        syncRecentChanges
                        pure lastFullSyncTime

                let sinceLastCleanup = diffUTCTime currentTime lastCleanupTime
                lct <- if sinceLastCleanup > cleanupDelay
                    then do
                        runAction cleanupCloud
                        pure currentTime
                    else do
                        liftIO $ noticeM appLoggerName $ "#TIMER #tillNextCleanup " ++ show (cleanupDelay - sinceLastCleanup)
                        pure lastCleanupTime

                pure (lfst, lct)

        -- Force first sync to be full, but delay cleanup.
        startTime <- liftIO getCurrentTime
        loop (posixSecondsToUTCTime 0) startTime