@@ -8,22 +8,17 @@ module Obelisk.Command.Deploy where
88
99import Control.Lens
1010import Control.Monad
11−import Control.Monad.Catch (Exception (displayException), MonadThrow, throwM, try, bracket_)
11+import Control.Monad.Catch (Exception (displayException), MonadThrow, bracket, throwM, try)
1212import Control.Monad.IO.Class (liftIO)
1313import Data.Aeson (FromJSON, ToJSON, encode, eitherDecode)
1414import Data.Bits
1515import qualified Data.ByteString.Lazy as BSL
1616import Data.Default
17−import qualified Data.HashMap.Strict as HM
1817import qualified Data.Map as Map
19−import Data.Text (Text)
2018import qualified Data.Set as Set
2119import qualified Data.Text as T
2220import qualified Data.Text.IO as T
2321import GHC.Generics
24−import Nix.Pretty
25−import Nix.String (principledMakeNixStringWithoutContext)
26−import Nix.Value
2722import System.Directory
2823import System.FilePath
2924import System.IO
@@ -53,6 +48,7 @@ deployInit
5348 -> Bool -- ^ enable https
5449 -> m ()
5550deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps = do
51+ liftIO $ createDirectoryIfMissing True deployDir
5652 localKey <- withSpinner ("Preparing " <> T.pack deployDir) $ do
5753 localKey <- liftIO (doesFileExist sshKeyPath) >>= \case
5854 False -> failWith $ T.pack $ "ob deploy init: file does not exist: " <> sshKeyPath
@@ -79,22 +75,30 @@ deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps
7975 , deployDir </> "config" </> "common"
8076 , deployDir </> "config" </> "frontend"
8177 ]
78+
79+ let srcDir = deployDir </> "src"
80+ withSpinner ("Creating source thunk (" <> T.pack (makeRelative deployDir srcDir) <> ")") $ liftIO $ do
81+ createThunk srcDir thunkPtr
82+ setupObeliskImpl deployDir
83+
8284 withSpinner "Writing deployment configuration" $ do
8385 writeDeployConfig deployDir "backend_hosts" $ unlines hostnames
8486 writeDeployConfig deployDir "enable_https" $ show enableHttps
8587 writeDeployConfig deployDir "admin_email" adminEmail
8688 writeDeployConfig deployDir ("config" </> "common" </> "route") route
87− withSpinner "Creating source thunk (./src)" $ liftIO $ do
88− createThunk (deployDir </> "src") thunkPtr
89− setupObeliskImpl deployDir
89+ writeDeployConfig deployDir "module.nix" $
90+ "(import " <> toNixPath (makeRelative deployDir srcDir) <> " {}).obelisk.serverModules.mkBaseEc2"
91+
9092 withSpinner ("Initializing git repository (" <> T.pack deployDir <> ")") $
9193 initGit deployDir
9294
9395setupObeliskImpl :: FilePath -> IO ()
9496setupObeliskImpl deployDir = do
95− let implDir = toImplDir deployDir
97+ let
98+ implDir = toImplDir deployDir
99+ goBackUp = foldr (</>) "" $ (".." <$) $ splitPath $ makeRelative deployDir implDir
96100 createDirectoryIfMissing True implDir
97− writeFile (implDir </> "default.nix") "(import ../../src {}).obelisk"
101+ writeFile (implDir </> "default.nix") $ "(import " <> toNixPath (goBackUp </> "src") <> " {}).obelisk"
98102
99103deployPush :: MonadObelisk m => FilePath -> m [String] -> m ()
100104deployPush deployPath getNixBuilders = do
@@ -113,6 +117,8 @@ deployPush deployPath getNixBuilders = do
113117 Left err -> failWith $ "ob deploy push: couldn't read src thunk: " <> T.pack (show err)
114118 let version = show . _thunkRev_commit $ _thunkPtr_rev thunkPtr
115119 builders <- getNixBuilders
120+ let moduleFile = deployPath </> "module.nix"
121+ moduleFileExists <- liftIO $ doesFileExist moduleFile
116122 buildOutputByHost <- ifor (Map.fromSet (const ()) hosts) $ \host () -> do
117123 --TODO: What does it mean if this returns more or less than 1 line of output?
118124 [result] <- fmap lines $ nixCmd $ NixCmd_Build $ def
@@ -122,13 +128,13 @@ deployPush deployPath getNixBuilders = do
122128 , _target_expr = Nothing
123129 }
124130 & nixBuildConfig_outLink .~ OutLink_None
125− & nixCmdConfig_args .~
131+ & nixCmdConfig_args .~ (
126132 [ strArg "hostName" host
127133 , strArg "adminEmail" adminEmail
128134 , strArg "routeHost" routeHost
129135 , strArg "version" version
130136 , boolArg "enableHttps" enableHttps
131− ]
137+ ] <> [rawArg "module" ("import " <> toNixPath moduleFile) | moduleFileExists ])
132138 & nixCmdConfig_builders .~ builders
133139 pure result
134140 let knownHostsPath = deployPath </> "backend_known_hosts"
@@ -172,20 +178,6 @@ deployPush deployPath getNixBuilders = do
172178deployUpdate :: MonadObelisk m => FilePath -> m ()
173179deployUpdate deployPath = updateThunkToLatest (ThunkUpdateConfig Nothing (ThunkConfig Nothing)) (deployPath </> "src")
174180
175−keytoolToAndroidConfig :: KeytoolConfig -> HM.HashMap Text (NValue t Identity m)
176−keytoolToAndroidConfig conf =
177− let path = nvPath $ _keytoolConfig_keystore conf
178− fromStr = nvStr . principledMakeNixStringWithoutContext . T.pack
179− storepass = fromStr $ _keytoolConfig_storepass conf
180− alias = fromStr $ _keytoolConfig_alias conf
181− keypass = fromStr $ _keytoolConfig_keypass conf
182− in HM.fromList
183− [ ("storeFile", path)
184− , ("storePassword", storepass)
185− , ("keyAlias", alias)
186− , ("keyPassword", keypass)
187− ]
188−
189181data PlatformDeployment = Android | IOS
190182 deriving (Show, Eq)
191183
@@ -200,7 +192,7 @@ deployMobile platform mobileArgs = withProjectRoot "." $ \root -> do
200192 configDir = root </> "config"
201193 exists <- liftIO $ doesDirectoryExist srcDir
202194 unless exists $ failWith "ob test should be run inside of a deploy directory"
203− nixBuildTarget <- case platform of
195+ (nixBuildTarget, extraArgs) <- case platform of
204196 Android -> do
205197 let keystorePath = root </> "android_keystore.jks"
206198 keytoolConfPath = root </> "android_keytool_config.json"
@@ -224,43 +216,55 @@ deployMobile platform mobileArgs = withProjectRoot "." $ \root -> do
224216 checkKeytoolConfExist <- liftIO $ doesFileExist keytoolConfPath
225217 unless checkKeytoolConfExist $ failWith "Missing android KeytoolConfig"
226218 keytoolConfContents <- liftIO $ BSL.readFile keytoolConfPath
227− liftIO $ print keytoolConfContents
228− releaseKey :: String <- case eitherDecode keytoolConfContents :: Either String KeytoolConfig of
219+ keyArgs <- case eitherDecode keytoolConfContents :: Either String KeytoolConfig of
229220 Left err -> failWith $ T.pack err
230− Right conf -> return $
231− (printNix :: MonadObelisk m => NValue t Identity m -> String) $
232− nvSet (keytoolToAndroidConfig conf) HM.empty
221+ Right conf -> pure
222+ [ "--sign"
223+ , "--store-file", _keytoolConfig_keystore conf
224+ , "--store-password", _keytoolConfig_storepass conf
225+ , "--key-alias", _keytoolConfig_alias conf
226+ , "--key-password", _keytoolConfig_keypass conf
227+ ]
233228 let expr = mconcat
234− [ "with (import ", srcDir, " {});"
235− , "android.frontend.override (drv: { "
236− , "releaseKey = (if builtins.isNull drv.releaseKey then {} else drv.releaseKey) // " <> releaseKey <> "; "
229+ [ "with (import ", toNixPath srcDir, " {});"
230+ , "android.frontend.override (drv: {"
231+ , "isRelease = true;"
232+ , "staticSrc = (passthru.__androidWithConfig ", configDir, ").frontend.staticSrc;"
237233 , "assets = (passthru.__androidWithConfig ", configDir, ").frontend.assets;"
238234 , "})"
239235 ]
240− return $ Target
236+ return (Target
241237 { _target_path = Nothing
242238 , _target_attr = Nothing
243239 , _target_expr = Just expr
244− }
240+ }, keyArgs)
245241 IOS -> do
246242 let expr = mconcat
247− [ "with (import ", srcDir, " {});"
248− , "ios.frontend.override (_: { staticSrc = (passthru.__iosWithConfig ", configDir, ").frontend.staticSrc; })"
243+ [ "with (import ", toNixPath srcDir, " {});"
244+ , "ios.frontend.override (_: { staticSrc = (passthru.__iosWithConfig ", toNixPath configDir, ").frontend.staticSrc; })"
249245 ]
250− return $ Target
246+ return (Target
251247 { _target_path = Nothing
252248 , _target_attr = Nothing
253249 , _target_expr = Just expr
254− }
250+ }, [])
255251 result <- nixCmd $ NixCmd_Build $ def
256252 & nixBuildConfig_outLink .~ OutLink_None
257253 & nixCmdConfig_target .~ nixBuildTarget
258− putLog Notice $ T.pack $ "Your recently built android apk can be found at the following path: " <> show result
259− callProcessAndLogOutput (Notice, Error) $ proc (result </> "bin" </> "deploy") mobileArgs
254+ let mobileArtifact = case platform of
255+ IOS -> "iOS App"
256+ Android -> "Android APK"
257+ putLog Notice $ T.pack $ unwords ["Your recently built", mobileArtifact, "can be found at the following path:", show result]
258+ callProcessAndLogOutput (Notice, Error) $ proc (result </> "bin" </> "deploy") (mobileArgs ++ extraArgs)
260259 where
261− withEcho showEcho f = do
262− prevEcho <- hGetEcho stdin
263− bracket_ (hSetEcho stdin showEcho) (hSetEcho stdin prevEcho) f
260+ withEcho showEcho f = bracket
261+ (do
262+ prevEcho <- hGetEcho stdin
263+ hSetEcho stdin showEcho
264+ pure prevEcho
265+ )
266+ (hSetEcho stdin)
267+ (const f)
264268
265269data KeytoolConfig = KeytoolConfig
266270 { _keytoolConfig_keystore :: FilePath
@@ -273,19 +277,16 @@ instance FromJSON KeytoolConfig
273277instance ToJSON KeytoolConfig
274278
275279createKeystore :: MonadObelisk m => FilePath -> KeytoolConfig -> m ()
276−createKeystore root config = do
277− let expr = "with (import " <> toImplDir root <> ").reflex-platform.nixpkgs; pkgs.mkShell { buildInputs = [ pkgs.jdk ]; }"
278− callProcessAndLogOutput (Notice,Notice) $ setCwd (Just root) $ proc "nix-shell" ["default.nix", "-E" , expr, "--run" , keytoolCmd]
279− where
280− keytoolCmd = processToShellString "keytool"
281− [ "-genkeypair", "-noprompt"
282− , "-keystore", _keytoolConfig_keystore config
283− , "-keyalg", "RSA", "-keysize", "2048"
284− , "-validity", "1000000"
285− , "-storepass", _keytoolConfig_storepass config
286− , "-alias", _keytoolConfig_alias config
287− , "-keypass", _keytoolConfig_keypass config
288− ]
280+createKeystore root config =
281+ callProcessAndLogOutput (Notice, Notice) $ setCwd (Just root) $ proc jreKeyToolPath
282+ [ "-genkeypair", "-noprompt"
283+ , "-keystore", _keytoolConfig_keystore config
284+ , "-keyalg", "RSA", "-keysize", "2048"
285+ , "-validity", "1000000"
286+ , "-storepass", _keytoolConfig_storepass config
287+ , "-alias", _keytoolConfig_alias config
288+ , "-keypass", _keytoolConfig_keypass config
289+ ]
289290
290291-- | Simplified deployment configuration mechanism. At one point we may revisit this.
291292writeDeployConfig :: MonadObelisk m => FilePath -> FilePath -> String -> m ()