on master help sign in
Merge remote-tracking branch 'origin/qa' into use-posix-escape

# Conflicts: # lib/command/src/Obelisk/Command/Project.hs # lib/command/src/Obelisk/Command/Run.hs

commit553677c39422f7a38aa310dc2b19e1624caf07c3 authorOwen Shepherd <414owen@gmail.com> dateWed 18 Dec 2019, 21:27 (7 years ago) parent6ab28f7ba0e28f
12 files changed+169−74
ChangeLog.md+4−1 FAQ.md+1−1 README.md+2−2 default.nix+4−1 lib/command/src/Obelisk/Command.hs+2−1 lib/command/src/Obelisk/Command/Deploy.hs+1−1 lib/command/src/Obelisk/Command/Project.hs+74−27 lib/command/src/Obelisk/Command/Run.hs+9−2 lib/route/src/Obelisk/Route/Frontend.hs+22−6 lib/run/obelisk-run.cabal+3 lib/run/src/Obelisk/Run.hs+28−15 lib/selftest/src/Obelisk/SelfTest.hs+19−17
ChangeLog.md+4−1
@@ -11,12 +11,15 @@ This project's release branch is `master`. This log is written from the perspect
1111* Add support in obelisk-route for single parameters in URL paths
1212* Bump reflex-platform so that obelisk now uses reflex-dom 0.5.2.0
1313* Use a `--pure` nix shell in `ob run` for parity with `ob repl` and more resilience against "works on my machine".
14−* Use `--keep NIX_PATH` for all pure shells so references to `<nixpkgs>` continues to work.
14+* Pin usages of `<nixpkgs>` in obelisk thunks, etc. to the nixpkgs used by the project's obelisk impl.
1515* Backport ACMEv2 support in obelisk server to regain LetsEncrypt account creation.
1616* Enable https in `ob run`
1717* `ob run` now handles ghci errors better, and includes a custom ghcid
1818 version. As a result, you no longer need to have ghcid installed to
1919 use `ob run`, as we provide one for you.
20+* `ob` commands now complain less on systems with umasks other than `0022`.
21+* Ignore package environment files in `ob run` and `ob repl`
22+* Add `Obelisk.Route.Frontend.routeLinkDynAttr`.
2023
2124## v0.2.0.0 - 2019-8-17
2225
FAQ.md+1−1
@@ -81,7 +81,7 @@ project's dependencies and,
8181Assuming we are in a project created with `ob init`, `ob run` calls (see
8282`lib/command/src/Obelisk/Command.hs`):
8383
84− nix-shell --pure --keep NIX_PATH -A shells.ghc --run 'ob --no-handoff internal run-static-io <real-run-function>'
84+ nix-shell --pure -A shells.ghc --run 'ob --no-handoff internal run-static-io <real-run-function>'
8585
8686where
8787
README.md+2−2
@@ -69,8 +69,8 @@ nix-env -f /path/to/obelisk -iA command
6969Note that `ob` will defer to the version found in your project's `.obelisk/impl` directory. To update that version specifically:
7070
7171```shell
72−ob thunk unpack ./obelisk/impl
73−cd ./obelisk/impl
72+ob thunk unpack ./.obelisk/impl
73+cd ./.obelisk/impl
7474# apply your changes
7575```
7676
default.nix+4−1
@@ -48,6 +48,7 @@ let
4848 # Add obelisk packages
4949 (self: super: let
5050 pkgs = self.callPackage ({ pkgs }: pkgs) {};
51+ onLinux = pkg: f: if pkgs.stdenv.isLinux then f pkg else pkg;
5152 in {
5253 obelisk-executable-config-inject = pkgs.obeliskExecutableConfig.platforms.web.inject self;
5354
@@ -62,7 +63,9 @@ let
6263 ];
6364 };
6465 obelisk-frontend = self.callCabal2nix "obelisk-frontend" (cleanSource ./lib/frontend) {};
65− obelisk-run = self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {};
66+ obelisk-run = onLinux (self.callCabal2nix "obelisk-run" (cleanSource ./lib/run) {}) (pkg:
67+ haskellLib.overrideCabal pkg (drv: { librarySystemDepends = [ pkgs.iproute ]; })
68+ );
6669 obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
6770 obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
6871 obelisk-snap-extras = self.callCabal2nix "obelisk-snap-extras" (cleanSource ./lib/snap-extras) {};
lib/command/src/Obelisk/Command.hs+2−1
@@ -269,7 +269,8 @@ mkObeliskConfig :: IO Obelisk
269269mkObeliskConfig = do
270270 cliArgs <- getArgs
271271 -- This function should not use argument parser (full argument parsing happens post handoff)
272− let logLevel = toLogLevel $ "-v" `elem` cliArgs
272+ -- TODO: See if we can use the argument parser with a subset of the parsers to get logging level out.
273+ let logLevel = toLogLevel $ any (`elem` ["-v", "--verbose"]) cliArgs
273274 notInteractive <- not <$> isInteractiveTerm
274275 cliConf <- newCliConfig logLevel notInteractive notInteractive $ \case
275276 ObeliskError_ProcessError (ProcessFailure p code) ann ->
lib/command/src/Obelisk/Command/Deploy.hs+1−1
@@ -281,7 +281,7 @@ createKeystore root config = do
281281 [ "-genkeypair", "-noprompt"
282282 , "-keystore", _keytoolConfig_keystore config
283283 , "-keyalg", "RSA", "-keysize", "2048"
284− , "-validity", "1000000000"
284+ , "-validity", "1000000"
285285 , "-storepass", _keytoolConfig_storepass config
286286 , "-alias", _keytoolConfig_alias config
287287 , "-keypass", _keytoolConfig_keypass config
lib/command/src/Obelisk/Command/Project.hs+74−27
@@ -1,6 +1,7 @@
11{-# LANGUAGE FlexibleContexts #-}
22{-# LANGUAGE LambdaCase #-}
33{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE TemplateHaskell #-}
45module Obelisk.Command.Project
56 ( InitSource (..)
67 , initProject
@@ -17,6 +18,7 @@ module Obelisk.Command.Project
1718 , toImplDir
1819 ) where
1920
21+import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked)
2022import Control.Monad
2123import Control.Monad.IO.Class (liftIO)
2224import Control.Monad.State
@@ -25,12 +27,15 @@ import Data.Bits
2527import Data.Function (on)
2628import qualified Data.Text as T
2729import System.Directory
30+import System.Environment (lookupEnv)
2831import System.FilePath
2932import System.IO.Temp
30−import System.Posix (FileStatus, UserID, deviceID, fileID, fileMode, fileOwner, getFileStatus, getRealUserID)
33+import System.IO.Unsafe (unsafePerformIO)
34+import System.Posix (FileStatus, FileMode, CMode (..), UserID, deviceID, fileID, fileMode, fileOwner, getFileStatus, getRealUserID)
3135import System.Posix.Files
3236import System.Process (CreateProcess, cwd, proc, waitForProcess, delegate_ctlc)
3337import Text.ShellEscape (Bash, bytes)
38+import System.Which (staticWhich)
3439
3540import GitHub.Data.GitData (Branch)
3641import GitHub.Data.Name (Name)
@@ -38,6 +43,7 @@ import GitHub.Data.Name (Name)
3843import Obelisk.App (MonadObelisk)
3944import Obelisk.CliApp
4045import Obelisk.Command.Thunk
46+
4147--TODO: Make this module resilient to random exceptions
4248
4349--TODO: Don't hardcode this
@@ -54,10 +60,10 @@ obeliskSourceWithBranch branch = ThunkSource_GitHub $ GitHubSource
5460 }
5561
5662data InitSource
57− = InitSource_Default
58− | InitSource_Branch (Name Branch)
59− | InitSource_Symlink FilePath
60− deriving Show
63+ = InitSource_Default
64+ | InitSource_Branch (Name Branch)
65+ | InitSource_Symlink FilePath
66+ deriving Show
6167
6268-- | Path to obelisk directory in given path
6369toObeliskDir :: FilePath -> FilePath
@@ -118,15 +124,16 @@ initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
118124findProjectObeliskCommand :: MonadObelisk m => FilePath -> m (Maybe FilePath)
119125findProjectObeliskCommand target = do
120126 myUid <- liftIO getRealUserID
127+ processUmask <- liftIO getUmask
121128 targetStat <- liftIO $ getFileStatus target
122− (result, insecurePaths) <- flip runStateT [] $ walkToProjectRoot target targetStat myUid >>= \case
129+ (result, insecurePaths) <- flip runStateT [] $ walkToProjectRoot target targetStat processUmask myUid >>= \case
123130 Nothing -> pure Nothing
124131 Just projectRoot -> liftIO (doesDirectoryExist $ toImplDir projectRoot) >>= \case
125132 False -> do
126133 putLog Warning $ "Found obelisk directory in " <> T.pack projectRoot <> " but the implementation (impl) file is missing"
127134 pure Nothing
128135 True -> do
129− walkToImplDir projectRoot myUid -- For security check
136+ walkToImplDir projectRoot myUid processUmask -- For security check
130137 return $ Just projectRoot
131138 case (result, insecurePaths) of
132139 (Just projDir, []) -> do
@@ -137,16 +144,36 @@ findProjectObeliskCommand target = do
137144 putLog Error $ T.unlines
138145 [ "Error: Found a project at " <> T.pack (normalise projDir) <> ", but had to traverse one or more insecure directories to get there:"
139146 , T.unlines $ fmap (T.pack . normalise) insecurePaths
140− , "Please ensure that all of these directories are owned by you and are not writable by anyone else."
147+ , "Please ensure that all of these directories are owned by you, not world-writable, and no more group-writable than permitted by your umask."
141148 ]
142149 return Nothing
143150
151+-- | Get the umask for the Obelisk process.
152+--
153+-- Because of
154+-- http://man7.org/linux/man-pages/man2/umask.2.html#NOTES we have to set the
155+-- umask to read it. We are using 'withMVarMasked' to guarantee that setting and
156+-- reading isn't interrupted by any exception or interleaved with another thread.
157+getUmask :: IO FileMode
158+getUmask = withMVarMasked globalUmaskLock $ \() -> do
159+ initialMask <- setFileCreationMask safeUmask
160+ void (setFileCreationMask initialMask)
161+ pure initialMask
162+ where
163+ safeUmask :: FileMode
164+ safeUmask = CMode 0o22
165+
166+{-# NOINLINE globalUmaskLock #-}
167+globalUmaskLock :: MVar ()
168+globalUmaskLock = unsafePerformIO (newMVar ())
169+
144170-- | Get the FilePath to the containing project directory, if there is one
145171findProjectRoot :: MonadObelisk m => FilePath -> m (Maybe FilePath)
146172findProjectRoot target = do
147173 myUid <- liftIO getRealUserID
148174 targetStat <- liftIO $ getFileStatus target
149− (result, _) <- liftIO $ runStateT (walkToProjectRoot target targetStat myUid) []
175+ umask <- liftIO $ getUmask
176+ (result, _) <- liftIO $ runStateT (walkToProjectRoot target targetStat umask myUid) []
150177 return result
151178
152179withProjectRoot :: MonadObelisk m => FilePath -> (FilePath -> m a) -> m a
@@ -159,15 +186,15 @@ withProjectRoot target f = findProjectRoot target >>= \case
159186-- traversed in the process. Return the project root directory, if found.
160187walkToProjectRoot
161188 :: (MonadState [FilePath] m, MonadIO m)
162− => FilePath -> FileStatus -> UserID -> m (Maybe FilePath)
163−walkToProjectRoot this thisStat myUid = liftIO (doesDirectoryExist this) >>= \case
189+ => FilePath -> FileStatus -> FileMode -> UserID -> m (Maybe FilePath)
190+walkToProjectRoot this thisStat desiredUmask myUid = liftIO (doesDirectoryExist this) >>= \case
164191 -- It's not a directory, so it can't be a project
165192 False -> do
166193 let dir = takeDirectory this
167194 dirStat <- liftIO $ getFileStatus dir
168− walkToProjectRoot dir dirStat myUid
195+ walkToProjectRoot dir dirStat desiredUmask myUid
169196 True -> do
170− when (not $ isWritableOnlyBy thisStat myUid) $ modify (this:)
197+ when (not $ isWellOwnedAndWellPermissioned thisStat myUid desiredUmask) $ modify (this:)
171198 liftIO (doesDirectoryExist $ toObeliskDir this) >>= \case
172199 True -> return $ Just this
173200 False -> do
@@ -177,25 +204,38 @@ walkToProjectRoot this thisStat myUid = liftIO (doesDirectoryExist this) >>= \ca
177204 isSameFileAs = (==) `on` fileIdentity
178205 if thisStat `isSameFileAs` nextStat
179206 then return Nothing -- Found a cycle; probably hit root directory
180− else walkToProjectRoot next nextStat myUid
207+ else walkToProjectRoot next nextStat desiredUmask myUid
181208
182209-- | Walk from the given project root directory to its Obelisk implementation
183210-- directory, accumulating potentially insecure directories that were traversed
184211-- in the process.
185−walkToImplDir :: (MonadState [FilePath] m, MonadIO m) => FilePath -> UserID -> m ()
186−walkToImplDir projectRoot myUid = do
212+walkToImplDir :: (MonadState [FilePath] m, MonadIO m) => FilePath -> UserID -> FileMode -> m ()
213+walkToImplDir projectRoot myUid umask = do
187214 let obDir = toObeliskDir projectRoot
188215 obDirStat <- liftIO $ getFileStatus obDir
189− when (not $ isWritableOnlyBy obDirStat myUid) $ modify (obDir:)
216+ when (not $ isWellOwnedAndWellPermissioned obDirStat myUid umask) $ modify (obDir:)
190217 let implThunk = obDir </> "impl"
191218 implThunkStat <- liftIO $ getFileStatus implThunk
192− when (not $ isWritableOnlyBy implThunkStat myUid) $ modify (implThunk:)
219+ when (not $ isWellOwnedAndWellPermissioned implThunkStat myUid umask) $ modify (implThunk:)
193220
194−--TODO: Is there a better way to ask if anyone else can write things?
195−--E.g. what about ACLs?
196−-- | Check to see if directory is only writable by a user whose User ID matches the second argument provided
197−isWritableOnlyBy :: FileStatus -> UserID -> Bool
198−isWritableOnlyBy s uid = fileOwner s == uid && fileMode s .&. 0o22 == 0
221+-- | Check to see if directory is writable by a user whose User ID matches the
222+-- second argument provided, and if the fact that other people can write to that
223+-- directory is in accordance with the umask of the system, passed as the third
224+-- argument.
225+isWellOwnedAndWellPermissioned :: FileStatus -> UserID -> FileMode -> Bool
226+isWellOwnedAndWellPermissioned s uid umask = isOwnedBy s uid && filePermissionIsSafe s umask
227+
228+isOwnedBy :: FileStatus -> UserID -> Bool
229+isOwnedBy s uid = fileOwner s == uid
230+
231+-- | Check to see if a directory respect the umask, but check explicitly that
232+-- it's not world writable in any case.
233+filePermissionIsSafe :: FileStatus -> FileMode -> Bool
234+filePermissionIsSafe s umask = not fileWorldWritable && fileGroupWritable <= umaskGroupWritable
235+ where
236+ fileWorldWritable = fileMode s .&. 0o002 == 0o002
237+ fileGroupWritable = fileMode s .&. 0o020 == 0o020
238+ umaskGroupWritable = umask .&. 0o020 == 0
199239
200240inProjectProc :: MonadObelisk m => String -> [Bash] -> m ()
201241inProjectProc shellName command = withProjectRoot "." $ \root ->
@@ -218,16 +258,23 @@ projectProc root isPure shellName command =
218258
219259projectShell :: MonadObelisk m => FilePath -> Bool -> String -> Maybe String -> m ()
220260projectShell root isPure shellName command = do
261+ let nixPath = $(staticWhich "nix")
262+ nixpkgsPath <- fmap T.strip $ readProcessAndLogStderr Debug $ proc nixPath ["eval", "(import .obelisk/impl {}).nixpkgs.path"]
263+ nixRemote <- liftIO $ lookupEnv "NIX_REMOTE"
221264 (_, _, _, ph) <- createProcess_ "runNixShellAttr" $ setCtlc $ setCwd (Just root) $ proc "nix-shell" $
222265 [ "default.nix"] <>
223− -- Keep $NIX_PATH in the env for --pure shells so '<nixpkgs>' works in sub-commands
224− -- TODO: Don't use <nixpkgs> for anything!
225− (if isPure then [ "--pure", "--keep", "NIX_PATH" ] else []) <>
266+ ["--pure" | isPure] <>
226267 [ "-A"
227268 , "shells." <> shellName
228269 ] <> case command of
229270 Nothing -> []
230− Just c -> ["--run", c]
271+ Just c ->
272+ -- TODO: Escape nixpkgsPath and nixRemote
273+ [ "--run"
274+ , "export NIX_PATH=nixpkgs=" <> T.unpack nixpkgsPath <> "; " <>
275+ maybe "" (\v -> "export NIX_REMOTE=" <> v <> "; ") nixRemote <>
276+ c
277+ ]
231278 void $ liftIO $ waitForProcess ph
232279
233280setCtlc :: CreateProcess -> CreateProcess
lib/command/src/Obelisk/Command/Run.hs+9−2
@@ -221,7 +221,7 @@ runGhciRepl
221221 :: MonadObelisk m
222222 => FilePath -- ^ Path to .ghci
223223 -> m ()
224−runGhciRepl dotGhci = inProjectProc "ghc" $ fmap (bash . BSU.fromString) $ "ghci" : ["-no-user-package-db", "-ghci-script", dotGhci]
224+runGhciRepl dotGhci = inProjectProc "ghc" $ fmap (bash . BSU.fromString) $ "ghci" : makeBaseGhciOptions dotGhci
225225
226226-- | Run ghcid
227227runGhcid
@@ -234,12 +234,19 @@ runGhcid dotGhci mcmd = callCommand $ unwords $ $(staticWhich "ghcid") : opts
234234 opts =
235235 [ "-W"
236236 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
237− , "--command='ghci -Wall -ignore-dot-ghci -fwarn-redundant-constraints -no-user-package-db -ghci-script " <> dotGhci <> "' "
237+ , "--command='ghci -Wall -ignore-dot-ghci -fwarn-redundant-constraints " <> unwords (makeBaseGhciOptions dotGhci) <> "' "
238238 , "--reload=config"
239239 , "--outputfile=ghcid-output.txt"
240240 ] <> testCmd
241241 testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'")
242242
243+makeBaseGhciOptions :: FilePath -> [String]
244+makeBaseGhciOptions dotGhci =
245+ [ "-no-user-package-db"
246+ , "-package-env -"
247+ , "-ghci-script " <> dotGhci
248+ ]
249+
243250getFreePort :: MonadIO m => m PortNumber
244251getFreePort = liftIO $ withSocketsDo $ do
245252 addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
lib/route/src/Obelisk/Route/Frontend.hs+22−6
@@ -53,6 +53,7 @@ module Obelisk.Route.Frontend
5353 , runRouteToUrlT
5454 , mapRouteToUrlT
5555 , routeLink
56+ , routeLinkDynAttr
5657 , dynRouteLink
5758 , adaptedUriPath
5859 , setAdaptedUriPath
@@ -74,6 +75,7 @@ import Control.Monad.Trans.Control
7475import Data.Coerce
7576import Data.Dependent.Sum (DSum (..))
7677import Data.GADT.Compare
78+import Data.Map (Map)
7779import Data.Monoid
7880import Data.Proxy
7981import Data.Text (Text)
@@ -526,9 +528,7 @@ routeLink r w = do
526528 setRoute $ r <$ domEvent Click e
527529 return a
528530
529−-- | A link widget that, when clicked, sets the route to current value of the
530−-- provided dynamic route. In non-javascript contexts the value of the dynamic post
531−-- build is used so the link still works like 'routeLink'.
531+-- | Like 'routeLinkDynAttr' but without custom attributes.
532532dynRouteLink
533533 :: forall t m a route.
534534 ( DomBuilder t m
@@ -537,11 +537,27 @@ dynRouteLink
537537 , SetRoute t (R route) m
538538 )
539539 => Dynamic t (R route) -- ^ Target route
540− -> m a -- ^ Child widget
540+ -> m a -- ^ Child widget of the @a@ element
541+ -> m a
542+dynRouteLink = routeLinkDynAttr mempty
543+
544+-- | An @a@-tag link widget that, when clicked, sets the route to current value of the
545+-- provided dynamic route. In non-JavaScript contexts the value of the dynamic post
546+-- build is used so the link still works like 'routeLink'.
547+routeLinkDynAttr
548+ :: forall t m a route.
549+ ( DomBuilder t m
550+ , PostBuild t m
551+ , RouteToUrl (R route) m
552+ , SetRoute t (R route) m
553+ )
554+ => Dynamic t (Map AttributeName Text) -- ^ Attributes for @a@ element. Note that if @href@ is present it will be ignored
555+ -> Dynamic t (R route) -- ^ Target route
556+ -> m a -- ^ Child widget of the @a@ element
541557 -> m a
542−dynRouteLink dr w = do
558+routeLinkDynAttr dAttr dr w = do
543559 enc <- askRouteToUrl
544− er <- dynamicAttributesToModifyAttributes $ ("href" =:) . enc <$> dr
560+ er <- dynamicAttributesToModifyAttributes $ zipDynWith (<>) (("href" =:) . enc <$> dr) dAttr
545561 let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
546562 & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (\_ -> preventDefault)
547563 & elementConfig_modifyAttributes .~ er
lib/run/obelisk-run.cabal+3
@@ -48,3 +48,6 @@ library
4848 exposed-modules:
4949 Obelisk.Run
5050 ghc-options: -Wall -fwarn-redundant-constraints
51+ if os(linux)
52+ cpp-options: -DIPROUTE_SUPPORTED
53+ build-depends: which
lib/run/src/Obelisk/Run.hs+28−15
@@ -1,3 +1,8 @@
1+{-# LANGUAGE CPP #-}
2+#if defined(IPROUTE_SUPPORTED)
3+{-# LANGUAGE TemplateHaskell #-}
4+#endif
5+
16{-# LANGUAGE ConstraintKinds #-}
27{-# LANGUAGE FlexibleContexts #-}
38{-# LANGUAGE FlexibleInstances #-}
@@ -62,12 +67,15 @@ import Snap.Core (Snap)
6267import System.Environment
6368import System.IO
6469import System.Process
65−import System.Exit (ExitCode(..))
6670import Text.URI (URI)
6771import qualified Text.URI as URI
6872import Text.URI.Lens
6973import Web.Cookie
7074
75+#if defined(IPROUTE_SUPPORTED)
76+import qualified System.Which
77+#endif
78+
7179run
7280 :: Int -- ^ Port to run the backend
7381 -> ([Text] -> Snap ()) -- ^ Static asset handler
@@ -99,7 +107,7 @@ runServeAsset rootPath = Snap.serveAsset "" rootPath . T.unpack . T.intercalate
99107
100108getConfigRoute :: Map Text ByteString -> Either Text URI
101109getConfigRoute configs = case Map.lookup "common/route" configs of
102− Just r ->
110+ Just r ->
103111 let stripped = T.strip (T.decodeUtf8 r)
104112 in case URI.mkURI stripped of
105113 Just route -> Right route
@@ -122,20 +130,20 @@ runWidget conf configs frontend validFullEncoder = do
122130 settings = setBeforeMainLoop beforeMainLoop (setPort port (setTimeout 3600 defaultSettings))
123131 -- Providing TLS here will also incidentally provide it to proxied requests to the backend.
124132 prepareRunner = case (uri ^? uriScheme . _Just . unRText) of
125− Just "https" -> do
133+ Just "https" -> do
126134 -- Generate a private key and self-signed certificate for TLS
127135 privateKey <- RSA.generateRSAKey' 2048 3
128136
129137 certRequest <- X509Request.newX509Req
130138 _ <- X509Request.setPublicKey certRequest privateKey
131− _ <- X509Request.signX509Req certRequest privateKey Nothing
139+ _ <- X509Request.signX509Req certRequest privateKey Nothing
132140
133141 cert <- X509.newX509 >>= X509Request.makeX509FromReq certRequest
134142 _ <- X509.setPublicKey cert privateKey
135143 now <- getCurrentTime
136144 _ <- X509.setNotBefore cert $ addUTCTime (-1) now
137145 _ <- X509.setNotAfter cert $ addUTCTime (365 * 24 * 60 * 60) now
138− _ <- X509.signX509 cert privateKey Nothing
146+ _ <- X509.signX509 cert privateKey Nothing
139147
140148 certByteString <- BSUTF8.fromString <$> PEM.writeX509 cert
141149 privateKeyByteString <- BSUTF8.fromString <$> PEM.writePKCS8PrivateKey privateKey Nothing
@@ -212,17 +220,22 @@ logPortBindErr p e = getProcessIdForPort p >>= \case
212220 Nothing -> putStrLn $ "runWidget: " <> show e
213221 Just pid -> putStrLn $ unwords [ "Port", show p, "is being used by process ID", show pid <> ".", "Please kill that process or change the port in config/common/route."]
214222
223+ssPath :: Maybe String
224+ssPath =
225+#if defined(IPROUTE_SUPPORTED)
226+ Just $(System.Which.staticWhich "ss")
227+#else
228+ Nothing
229+#endif
230+
215231getProcessIdForPort :: Int -> IO (Maybe Int)
216−getProcessIdForPort port = do
217− -- First check if 'ss' is available
218− (c, _, _) <- readProcessWithExitCode "which" ["ss"] mempty
219− case c of
220− ExitSuccess -> do
221− xs <- lines <$> readProcess "ss" ["-lptn", "sport = " <> show port] mempty
222− case uncons xs of
223− Just (_, x:_) -> return $ A.maybeResult $ A.parse parseSsPid $ BSC.pack x
224− _ -> return Nothing
225− _ -> return Nothing
232+getProcessIdForPort port = case ssPath of
233+ Just ss -> do
234+ xs <- lines <$> readProcess ss ["-lptn", "sport = " <> show port] mempty
235+ case uncons xs of
236+ Just (_, x:_) -> return $ A.maybeResult $ A.parse parseSsPid $ BSC.pack x
237+ _ -> return Nothing
238+ _ -> return Nothing
226239
227240parseSsPid :: A.Parser Int
228241parseSsPid = do
lib/selftest/src/Obelisk/SelfTest.hs+19−17
@@ -3,7 +3,6 @@
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE PatternGuards #-}
55{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE TypeApplications #-}
76module Obelisk.SelfTest where
87
98import Control.Exception (bracket, throw)
@@ -71,7 +70,7 @@ shellyOb f obTest = shelly $ f obTest
7170main :: IO ()
7271main = do
7372 -- Note: you can pass hspec arguments as well, eg: `-m <pattern>`
74− isVerbose <- (elem "-v") <$> getArgs
73+ isVerbose <- elem "-v" <$> getArgs
7574 unless isVerbose $
7675 putStrLn "Tests may take longer to run if there are unbuilt derivations: use -v for verbose output"
7776 let verbosity = bool silently verbosely isVerbose
@@ -118,7 +117,7 @@ main = do
118117 it "works with master branch impl" $ inTmp $ \_ -> run "ob" ["init", "--branch", "master"]
119118 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
120119 it "doesn't silently overwrite existing files" $ withSystemTempDirectory "ob-init" $ \dir -> do
121− let p force = (proc "ob" $ "--no-handoff" : "init" : if force then ["--force"] else []) { cwd = Just dir }
120+ let p force = (proc "ob" $ "--no-handoff" : "init" : ["--force"|force]) { cwd = Just dir }
122121 (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p False) ""
123122 (ExitFailure _, _, _) <- readCreateProcessWithExitCode (p False) ""
124123 (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p True) ""
@@ -128,7 +127,7 @@ main = do
128127 void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]
129128 ls tmp >>= liftIO . assertEqual "" []
130129
131− it "produces a valid route config" $ inTmpObInit $ \tmp -> liftIO $ do
130+ it "produces a valid route config" $ inTmpObInit $ \tmp -> liftIO $
132131 withCurrentDirectory (T.unpack $ toTextIgnore tmp) $ do
133132 configs <- getConfigs
134133 return (either (const Nothing) Just $ getConfigRoute configs) `shouldNotReturn` Nothing
@@ -136,10 +135,8 @@ main = do
136135 -- These tests fail with "Could not find module 'Obelisk.Generated.Static'"
137136 -- when not run by 'nix-build --attr selftest'
138137 describe "ob run" $ parallel $ do
139− it "works in root directory" $ inTmpObInit $ \_ -> do
140− testObRunInDir p0 p1 Nothing httpManager
141− it "works in sub directory" $ inTmpObInit $ \_ -> do
142− testObRunInDir p2 p3 (Just "frontend") httpManager
138+ it "works in root directory" $ inTmpObInit $ \_ -> testObRunInDir p0 p1 Nothing httpManager
139+ it "works in sub directory" $ inTmpObInit $ \_ -> testObRunInDir p2 p3 (Just "frontend") httpManager
143140
144141 describe "obelisk project" $ parallel $ do
145142 it "can build obelisk command" $ inTmpObInit $ \_ -> nixBuild ["-A", "command" , obeliskImpl]
@@ -149,14 +146,19 @@ main = do
149146
150147 describe "blank initialized project" $ parallel $ do
151148
152− it "can build ghc.backend" $ inTmpObInit $ \_ -> do
153− nixBuild ["-A", "ghc.backend"]
154− it "can build ghcjs.frontend" $ inTmpObInit $ \_ -> do
155− nixBuild ["-A", "ghcjs.frontend"]
149+ it "can build ghc.backend" $ inTmpObInit $ \_ -> nixBuild ["-A", "ghc.backend"]
150+ it "can build ghcjs.frontend" $ inTmpObInit $ \_ -> nixBuild ["-A", "ghcjs.frontend"]
156151
157152 if os == "darwin"
158− then it "can build ios" $ inTmpObInit $ \_ -> nixBuild ["-A", "ios.frontend"]
159− else it "can build android" $ inTmpObInit $ \_ -> nixBuild ["-A", "android.frontend"]
153+ then it "can build ios" $ inTmpObInit $ \_ -> nixBuild ["-A", "ios.frontend"]
154+ else it "can build android after accepting license" $ inTmpObInit $ \dir -> do
155+ let defaultNixPath = dir </> ("default.nix" :: Shelly.FilePath)
156+ writefile defaultNixPath
157+ =<< T.replace
158+ "# config.android_sdk.accept_license = false;"
159+ "config.android_sdk.accept_license = true;"
160+ <$> readfile defaultNixPath
161+ nixBuild ["-A", "android.frontend"]
160162
161163 forM_ ["ghc", "ghcjs"] $ \compiler -> do
162164 let
@@ -191,7 +193,7 @@ main = do
191193 branch' <- chdir dir $ run "git" ["rev-parse", "--abbrev-ref", "HEAD"]
192194 liftIO $ assertEqual "" branch (T.strip branch')
193195
194− it "can pack and unpack plain git repos" $ do
196+ it "can pack and unpack plain git repos" $
195197 shelly_ $ withSystemTempDirectory "git-repo" $ \dir -> do
196198 let repo = toTextIgnore $ dir </> ("repo" :: String)
197199 run_ "git" ["clone", "https://github.com/haskell/process.git", repo]
@@ -210,7 +212,7 @@ main = do
210212 testThunkPack $ fromText repo
211213
212214 it "aborts thunk pack when there are uncommitted files" $ inTmpObInit $ \dir -> do
213− void $ unpack
215+ void unpack
214216 testThunkPack (dir </> thunk)
215217
216218 describe "ob thunk update --branch" $ parallel $ do
@@ -289,7 +291,7 @@ alterRouteTo uri stdout = do
289291
290292-- | Handle stdout of `ob run`: check that the frontend and backend servers are started correctly
291293handleObRunStdout :: HTTP.Manager -> Handle -> Sh Text
292−handleObRunStdout httpManager stdout = flip fix (ObRunState_Init, []) $ \loop (state, msgs) -> do
294+handleObRunStdout httpManager stdout = flip fix (ObRunState_Init, []) $ \loop (state, msgs) ->
293295 liftIO (T.hGetLine stdout) >>= \t -> case state of
294296 ObRunState_Init
295297 | "Running test..." `T.isPrefixOf` t -> loop (ObRunState_BackendStarted, msgs)