mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
optional lan
This commit is contained in:
@@ -7,29 +7,29 @@
|
|||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
module Handler.Apps where
|
module Handler.Apps where
|
||||||
|
|
||||||
import Startlude hiding ( modify
|
import Startlude hiding ( Reader
|
||||||
, execState
|
|
||||||
, asks
|
, asks
|
||||||
, Reader
|
|
||||||
, runReader
|
|
||||||
, catchError
|
, catchError
|
||||||
, forkFinally
|
|
||||||
, empty
|
, empty
|
||||||
|
, execState
|
||||||
|
, forkFinally
|
||||||
|
, modify
|
||||||
|
, runReader
|
||||||
)
|
)
|
||||||
|
|
||||||
import Control.Carrier.Reader
|
|
||||||
import Control.Carrier.Error.Church
|
import Control.Carrier.Error.Church
|
||||||
import Control.Carrier.Lift
|
import Control.Carrier.Lift
|
||||||
|
import Control.Carrier.Reader
|
||||||
import qualified Control.Concurrent.Async.Lifted
|
import qualified Control.Concurrent.Async.Lifted
|
||||||
as LAsync
|
as LAsync
|
||||||
import qualified Control.Concurrent.Lifted as Lifted
|
import qualified Control.Concurrent.Lifted as Lifted
|
||||||
import qualified Control.Exception.Lifted as Lifted
|
|
||||||
import Control.Concurrent.STM.TVar
|
import Control.Concurrent.STM.TVar
|
||||||
import Control.Effect.Empty hiding ( guard )
|
import Control.Effect.Empty hiding ( guard )
|
||||||
import Control.Effect.Labelled ( HasLabelled
|
import Control.Effect.Labelled ( HasLabelled
|
||||||
, Labelled
|
, Labelled
|
||||||
, runLabelled
|
, runLabelled
|
||||||
)
|
)
|
||||||
|
import qualified Control.Exception.Lifted as Lifted
|
||||||
import Control.Lens hiding ( (??) )
|
import Control.Lens hiding ( (??) )
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
import Control.Monad.Trans.Control ( MonadBaseControl )
|
import Control.Monad.Trans.Control ( MonadBaseControl )
|
||||||
@@ -38,13 +38,13 @@ import Data.Aeson
|
|||||||
import Data.Aeson.Lens
|
import Data.Aeson.Lens
|
||||||
import Data.Aeson.Types ( parseMaybe )
|
import Data.Aeson.Types ( parseMaybe )
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
import Data.IORef
|
|
||||||
import qualified Data.HashMap.Lazy as HML
|
import qualified Data.HashMap.Lazy as HML
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import Data.IORef
|
||||||
import qualified Data.List.NonEmpty as NE
|
import qualified Data.List.NonEmpty as NE
|
||||||
import Data.Singletons
|
import Data.Singletons
|
||||||
import Data.Singletons.Prelude.Bool ( SBool(..)
|
import Data.Singletons.Prelude.Bool ( If
|
||||||
, If
|
, SBool(..)
|
||||||
)
|
)
|
||||||
import Data.Singletons.Prelude.List ( Elem )
|
import Data.Singletons.Prelude.List ( Elem )
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
@@ -55,8 +55,8 @@ import Exinst
|
|||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import qualified Network.JSONRPC as JSONRPC
|
import qualified Network.JSONRPC as JSONRPC
|
||||||
import Yesod.Core.Content
|
import Yesod.Core.Content
|
||||||
import Yesod.Core.Json
|
|
||||||
import Yesod.Core.Handler hiding ( cached )
|
import Yesod.Core.Handler hiding ( cached )
|
||||||
|
import Yesod.Core.Json
|
||||||
import Yesod.Core.Types ( JSONResponse(..) )
|
import Yesod.Core.Types ( JSONResponse(..) )
|
||||||
import Yesod.Persist.Core
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ import qualified Lib.Algebra.Domain.AppMgr as AppMgr2
|
|||||||
import Lib.Algebra.State.RegistryUrl
|
import Lib.Algebra.State.RegistryUrl
|
||||||
import Lib.Background
|
import Lib.Background
|
||||||
import Lib.Error
|
import Lib.Error
|
||||||
|
import qualified Lib.External.AppManifest as AppManifest
|
||||||
import qualified Lib.External.AppMgr as AppMgr
|
import qualified Lib.External.AppMgr as AppMgr
|
||||||
import qualified Lib.External.Registry as Reg
|
import qualified Lib.External.Registry as Reg
|
||||||
import qualified Lib.External.AppManifest as AppManifest
|
|
||||||
import Lib.IconCache
|
import Lib.IconCache
|
||||||
import qualified Lib.Notifications as Notifications
|
import qualified Lib.Notifications as Notifications
|
||||||
import Lib.SystemPaths
|
import Lib.SystemPaths
|
||||||
@@ -249,19 +249,28 @@ getInstalledAppsLogic = do
|
|||||||
, appInstalledPreviewVersionInstalled = storeAppVersionInfoVersion
|
, appInstalledPreviewVersionInstalled = storeAppVersionInfoVersion
|
||||||
, appInstalledPreviewTorAddress = Nothing
|
, appInstalledPreviewTorAddress = Nothing
|
||||||
, appInstalledPreviewLanAddress = Nothing
|
, appInstalledPreviewLanAddress = Nothing
|
||||||
, appInstalledPreviewUi = False
|
, appInstalledPreviewTorUi = False
|
||||||
|
, appInstalledPreviewLanUi = False
|
||||||
}
|
}
|
||||||
installedPreviews = flip
|
installedPreviews = flip
|
||||||
HML.mapWithKey
|
HML.mapWithKey
|
||||||
remapped
|
remapped
|
||||||
\appId (s, v, AppMgr2.InfoRes {..}) ->
|
\appId (s, v, AppMgr2.InfoRes {..}) ->
|
||||||
let lanAddress = LanAddress . (".onion" `Text.replace` ".local") . unTorAddress <$> infoResTorAddress
|
let
|
||||||
|
mLanAddress = do -- Maybe
|
||||||
|
addrBase <- infoResTorAddress
|
||||||
|
let
|
||||||
|
lanConfs = mapMaybe AppManifest.portMapEntryLan
|
||||||
|
$ AppManifest.appManifestPortMapping infoResManifest
|
||||||
|
guard (not . null $ lanConfs)
|
||||||
|
pure $ LanAddress . (".onion" `Text.replace` ".local") . unTorAddress $ addrBase
|
||||||
in AppInstalledPreview { appInstalledPreviewBase = AppBase appId infoResTitle (iconUrl appId v)
|
in AppInstalledPreview { appInstalledPreviewBase = AppBase appId infoResTitle (iconUrl appId v)
|
||||||
, appInstalledPreviewStatus = s
|
, appInstalledPreviewStatus = s
|
||||||
, appInstalledPreviewVersionInstalled = v
|
, appInstalledPreviewVersionInstalled = v
|
||||||
, appInstalledPreviewTorAddress = infoResTorAddress
|
, appInstalledPreviewTorAddress = infoResTorAddress
|
||||||
, appInstalledPreviewLanAddress = lanAddress
|
, appInstalledPreviewLanAddress = mLanAddress
|
||||||
, appInstalledPreviewUi = AppManifest.uiAvailable infoResManifest
|
, appInstalledPreviewTorUi = AppManifest.torUiAvailable infoResManifest
|
||||||
|
, appInstalledPreviewLanUi = AppManifest.lanUiAvailable infoResManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
pure $ HML.elems $ HML.union installingPreviews installedPreviews
|
pure $ HML.elems $ HML.union installingPreviews installedPreviews
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ data AppInstalledPreview = AppInstalledPreview
|
|||||||
, appInstalledPreviewVersionInstalled :: Version
|
, appInstalledPreviewVersionInstalled :: Version
|
||||||
, appInstalledPreviewTorAddress :: Maybe TorAddress
|
, appInstalledPreviewTorAddress :: Maybe TorAddress
|
||||||
, appInstalledPreviewLanAddress :: Maybe LanAddress
|
, appInstalledPreviewLanAddress :: Maybe LanAddress
|
||||||
, appInstalledPreviewUi :: Bool
|
, appInstalledPreviewTorUi :: Bool
|
||||||
|
, appInstalledPreviewLanUi :: Bool
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
instance ToJSON AppInstalledPreview where
|
instance ToJSON AppInstalledPreview where
|
||||||
@@ -56,7 +57,8 @@ instance ToJSON AppInstalledPreview where
|
|||||||
, "versionInstalled" .= appInstalledPreviewVersionInstalled
|
, "versionInstalled" .= appInstalledPreviewVersionInstalled
|
||||||
, "torAddress" .= (unTorAddress <$> appInstalledPreviewTorAddress)
|
, "torAddress" .= (unTorAddress <$> appInstalledPreviewTorAddress)
|
||||||
, "lanAddress" .= (unLanAddress <$> appInstalledPreviewLanAddress)
|
, "lanAddress" .= (unLanAddress <$> appInstalledPreviewLanAddress)
|
||||||
, "ui" .= appInstalledPreviewUi
|
, "torUi" .= appInstalledPreviewTorUi
|
||||||
|
, "lanUi" .= appInstalledPreviewLanUi
|
||||||
]
|
]
|
||||||
|
|
||||||
data InstallNewAppReq = InstallNewAppReq
|
data InstallNewAppReq = InstallNewAppReq
|
||||||
|
|||||||
43
agent/src/Lib/External/AppManifest.hs
vendored
43
agent/src/Lib/External/AppManifest.hs
vendored
@@ -9,12 +9,12 @@ import Data.Aeson
|
|||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Yaml as Yaml
|
import qualified Data.Yaml as Yaml
|
||||||
|
|
||||||
|
import Control.Monad.Fail ( MonadFail(fail) )
|
||||||
import Lib.Error
|
import Lib.Error
|
||||||
import Lib.SystemPaths
|
import Lib.SystemPaths
|
||||||
import Lib.Types.Core
|
import Lib.Types.Core
|
||||||
import Lib.Types.Emver
|
import Lib.Types.Emver
|
||||||
import Lib.Types.Emver.Orphans ( )
|
import Lib.Types.Emver.Orphans ( )
|
||||||
import Control.Monad.Fail ( MonadFail(fail) )
|
|
||||||
|
|
||||||
data ImageType = ImageTypeTar
|
data ImageType = ImageTypeTar
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
@@ -54,6 +54,7 @@ data Action = Action
|
|||||||
, actionWarning :: Maybe Text
|
, actionWarning :: Maybe Text
|
||||||
, actionAllowedStatuses :: [AppContainerStatus]
|
, actionAllowedStatuses :: [AppContainerStatus]
|
||||||
}
|
}
|
||||||
|
deriving Show
|
||||||
instance FromJSON Action where
|
instance FromJSON Action where
|
||||||
parseJSON = withObject "AppAction" $ \o -> do
|
parseJSON = withObject "AppAction" $ \o -> do
|
||||||
actionId <- o .: "id"
|
actionId <- o .: "id"
|
||||||
@@ -80,7 +81,7 @@ data AppManifest where
|
|||||||
, appManifestDescShort :: Text
|
, appManifestDescShort :: Text
|
||||||
, appManifestDescLong :: Text
|
, appManifestDescLong :: Text
|
||||||
, appManifestReleaseNotes :: Text
|
, appManifestReleaseNotes :: Text
|
||||||
, appManifestPortMapping :: HM.HashMap Word16 Word16
|
, appManifestPortMapping :: [PortMapEntry]
|
||||||
, appManifestImageType :: ImageType
|
, appManifestImageType :: ImageType
|
||||||
, appManifestMount :: FilePath
|
, appManifestMount :: FilePath
|
||||||
, appManifestAssets :: [AssetMapping]
|
, appManifestAssets :: [AssetMapping]
|
||||||
@@ -91,9 +92,16 @@ data AppManifest where
|
|||||||
, appManifestStartAlert :: Maybe Text
|
, appManifestStartAlert :: Maybe Text
|
||||||
, appManifestActions :: [Action]
|
, appManifestActions :: [Action]
|
||||||
} -> AppManifest
|
} -> AppManifest
|
||||||
|
deriving instance Show AppManifest
|
||||||
|
|
||||||
uiAvailable :: AppManifest -> Bool
|
torUiAvailable :: AppManifest -> Bool
|
||||||
uiAvailable AppManifest {..} = isJust $ HM.lookup 80 appManifestPortMapping
|
torUiAvailable AppManifest {..} = any (== 80) $ portMapEntryTor <$> appManifestPortMapping
|
||||||
|
|
||||||
|
lanUiAvailable :: AppManifest -> Bool
|
||||||
|
lanUiAvailable AppManifest {..} = any id $ fmap portMapEntryLan appManifestPortMapping <&> \case
|
||||||
|
Just Standard -> True
|
||||||
|
Just (Custom 443) -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
instance FromJSON AppManifest where
|
instance FromJSON AppManifest where
|
||||||
parseJSON = withObject "App Manifest " $ \o -> do
|
parseJSON = withObject "App Manifest " $ \o -> do
|
||||||
@@ -103,7 +111,7 @@ instance FromJSON AppManifest where
|
|||||||
appManifestDescShort <- o .: "description" >>= (.: "short")
|
appManifestDescShort <- o .: "description" >>= (.: "short")
|
||||||
appManifestDescLong <- o .: "description" >>= (.: "long")
|
appManifestDescLong <- o .: "description" >>= (.: "long")
|
||||||
appManifestReleaseNotes <- o .: "release-notes"
|
appManifestReleaseNotes <- o .: "release-notes"
|
||||||
appManifestPortMapping <- o .: "ports" >>= fmap HM.fromList . traverse parsePortMapping
|
appManifestPortMapping <- o .: "ports"
|
||||||
appManifestImageType <- o .: "image" >>= (.: "type")
|
appManifestImageType <- o .: "image" >>= (.: "type")
|
||||||
appManifestMount <- o .: "mount"
|
appManifestMount <- o .: "mount"
|
||||||
appManifestAssets <- o .: "assets" >>= traverse parseJSON
|
appManifestAssets <- o .: "assets" >>= traverse parseJSON
|
||||||
@@ -114,12 +122,31 @@ instance FromJSON AppManifest where
|
|||||||
appManifestStartAlert <- o .:? "start-alert"
|
appManifestStartAlert <- o .:? "start-alert"
|
||||||
appManifestActions <- o .: "actions"
|
appManifestActions <- o .: "actions"
|
||||||
pure $ AppManifest { .. }
|
pure $ AppManifest { .. }
|
||||||
where
|
where parseDepInfo = withObject "Dep Info" $ (.: "version")
|
||||||
parsePortMapping = withObject "Port Mapping" $ \o -> liftA2 (,) (o .: "tor") (o .: "internal")
|
|
||||||
parseDepInfo = withObject "Dep Info" $ (.: "version")
|
|
||||||
|
|
||||||
getAppManifest :: (MonadIO m, HasFilesystemBase sig m) => AppId -> S9ErrT m (Maybe AppManifest)
|
getAppManifest :: (MonadIO m, HasFilesystemBase sig m) => AppId -> S9ErrT m (Maybe AppManifest)
|
||||||
getAppManifest appId = do
|
getAppManifest appId = do
|
||||||
base <- ask @"filesystemBase"
|
base <- ask @"filesystemBase"
|
||||||
ExceptT $ first (ManifestParseE appId) <$> liftIO
|
ExceptT $ first (ManifestParseE appId) <$> liftIO
|
||||||
(Yaml.decodeFileEither . toS $ (appMgrAppPath appId <> "manifest.yaml") `relativeTo` base)
|
(Yaml.decodeFileEither . toS $ (appMgrAppPath appId <> "manifest.yaml") `relativeTo` base)
|
||||||
|
|
||||||
|
data LanConfiguration = Standard | Custom Word16 deriving (Eq, Show)
|
||||||
|
instance FromJSON LanConfiguration where
|
||||||
|
parseJSON = liftA2 (<|>) standard custom
|
||||||
|
where
|
||||||
|
standard =
|
||||||
|
withText "Standard Lan" \t -> if t == "standard" then pure Standard else fail "Not Standard Lan Conf"
|
||||||
|
custom = withObject "Custom Lan" $ \o -> do
|
||||||
|
Custom <$> o .: "port"
|
||||||
|
data PortMapEntry = PortMapEntry
|
||||||
|
{ portMapEntryInternal :: Word16
|
||||||
|
, portMapEntryTor :: Word16
|
||||||
|
, portMapEntryLan :: Maybe LanConfiguration
|
||||||
|
}
|
||||||
|
deriving (Eq, Show)
|
||||||
|
instance FromJSON PortMapEntry where
|
||||||
|
parseJSON = withObject "Port Map Entry" $ \o -> do
|
||||||
|
portMapEntryInternal <- o .: "internal"
|
||||||
|
portMapEntryTor <- o .: "tor"
|
||||||
|
portMapEntryLan <- o .:? "lan"
|
||||||
|
pure PortMapEntry { .. }
|
||||||
|
|||||||
60
agent/test/Lib/External/AppManifestSpec.hs
vendored
60
agent/test/Lib/External/AppManifestSpec.hs
vendored
@@ -66,12 +66,60 @@ assets:
|
|||||||
hidden-service-version: v3
|
hidden-service-version: v3
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
mastodon330Manifest :: ByteString
|
||||||
|
mastodon330Manifest = [i|
|
||||||
|
---
|
||||||
|
id: mastodon
|
||||||
|
version: 3.3.0.1
|
||||||
|
title: Mastodon
|
||||||
|
description:
|
||||||
|
short: "A free, open-source social network server."
|
||||||
|
long: "Mastodon is a free, open-source social network server based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub)!"
|
||||||
|
release-notes: Added an acation to reset the admin password
|
||||||
|
install-alert: "After starting mastodon for the first time, it can take a long time (several minutes) to be ready.\nPlease be patient. On future starts of the service, it will be faster, but still takes longer than other services.\nMake sure to sign up for a user before giving out your link. The first user to sign up is set as the admin user.\n"
|
||||||
|
uninstall-alert: ~
|
||||||
|
restore-alert: ~
|
||||||
|
start-alert: "It may take several minutes after startup for this service to be ready for use.\n"
|
||||||
|
has-instructions: true
|
||||||
|
os-version-required: ">=0.2.8"
|
||||||
|
os-version-recommended: ">=0.2.8"
|
||||||
|
ports:
|
||||||
|
- internal: 80
|
||||||
|
tor: 80
|
||||||
|
lan: standard
|
||||||
|
- internal: 3000
|
||||||
|
tor: 3000
|
||||||
|
lan: ~
|
||||||
|
- internal: 4000
|
||||||
|
tor: 4000
|
||||||
|
lan: ~
|
||||||
|
image:
|
||||||
|
type: tar
|
||||||
|
shm-size-mb: ~
|
||||||
|
mount: /root/persistence
|
||||||
|
public: ~
|
||||||
|
shared: ~
|
||||||
|
assets: []
|
||||||
|
hidden-service-version: v3
|
||||||
|
dependencies: {}
|
||||||
|
actions:
|
||||||
|
- id: reset-admin-password
|
||||||
|
name: Reset Admin Password
|
||||||
|
description: This action will reset your admin password to a random value
|
||||||
|
allowed-statuses:
|
||||||
|
- RUNNING
|
||||||
|
command:
|
||||||
|
- docker_entrypoint.sh
|
||||||
|
- reset_admin_password.sh
|
||||||
|
|]
|
||||||
|
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = do
|
spec = do
|
||||||
describe "parsing app manifest ports" $ do
|
describe "parsing app manifest ports" $ do
|
||||||
it "should yield true for cups 0.2.3" $ do
|
it "should parse mastodon 3.3.0" $ do
|
||||||
res <- decodeThrow @IO @AppManifest cups023Manifest
|
res <- decodeThrow @IO @AppManifest mastodon330Manifest
|
||||||
uiAvailable res `shouldBe` True
|
print res
|
||||||
it "should yield false for cups 0.2.3 Mod" $ do
|
lanUiAvailable res `shouldBe` True
|
||||||
res <- decodeThrow @IO @AppManifest cups023ManifestModNoUI
|
torUiAvailable res `shouldBe` True
|
||||||
uiAvailable res `shouldBe` False
|
|
||||||
|
|||||||
Reference in New Issue
Block a user