on master help sign in
.github/pull_request_template.md+2
since splitsnapshots
unifiedsplit
@@ -3,6 +3,8 @@
3I have:
3I have:
4
4
5 - [ ] Based work on latest `develop` branch
5 - [ ] Based work on latest `develop` branch
6+ - [ ] Followed the [contribution guide](https://github.com/obsidiansystems/obelisk/blob/develop/CONTRIBUTING.md#submitting-changes)
6 - [ ] Looked for lint in my changes with `hlint .` (lint found code you did not write can be left alone)
7 - [ ] Looked for lint in my changes with `hlint .` (lint found code you did not write can be left alone)
7 - [ ] Run the test suite: `$(nix-build -A selftest --no-out-link)`
8 - [ ] Run the test suite: `$(nix-build -A selftest --no-out-link)`
9+ - [ ] [Updated the changelog](https://github.com/obsidiansystems/obelisk/blob/develop/CONTRIBUTING.md#in-the-changelog)
8 - [ ] (Optional) Run CI tests locally: `nix-build release.nix -A build.x86_64-linux --no-out-link` (or `x86_64-darwin` on macOS)
10 - [ ] (Optional) Run CI tests locally: `nix-build release.nix -A build.x86_64-linux --no-out-link` (or `x86_64-darwin` on macOS)
ChangeLog.md+19−1
@@ -2,8 +2,26 @@
2
2
3This project's release branch is `master`. This log is written from the perspective of the release branch: when changes hit `master`, they are considered released.
3This project's release branch is `master`. This log is written from the perspective of the release branch: when changes hit `master`, they are considered released.
4
4
5−## Unreleased
5+## v0.9.1.0
6
6
7+* [#801](https://github.com/obsidiansystems/obelisk/pull/801): Remove errors and warning for local packages without a library component
8+* [#812](https://github.com/obsidiansystems/obelisk/pull/812): Add support for `NoImplicitPrelude` and other extensions disabled via `No`
9+* Pinned version bumps:
10+ * reflex-platform [0.7.0.0](https://github.com/reflex-frp/reflex-platform/releases/tag/v0.7.0.0)
11+ * hnix 0.8.0
12+* [#787](https://github.com/obsidiansystems/obelisk/pull/787): Set `immutable` cache control directive when serving content-addressed static assets
13+* Use iOS SDK 13.2
14+
15+
16+## v0.9.0.1
17+
18+* ([#810](https://github.com/obsidiansystems/obelisk/pull/810)) Fix loading of `all.js` in fully compiled web apps.
19+
20+## v0.9.0.0
21+
22+* **(Breaking change)** Backport nixpkgs upgrades to ACME/Let's Encrypt handling so that HTTPS deployments continue to work flawlessly. If your deployment is having trouble renewing [Let's Encrypt](https://letsencrypt.org/) certificates, upgrade to this version.
23+ * **IMPORTANT:** In order to use [Let's Encrypt](https://letsencrypt.org/) you must now accept their [terms of service](https://letsencrypt.org/repository/). To do that, add `terms.security.acme.acceptTerms = true;` to the `import ./.obelisk/impl {` section in your `default.nix`. The new skeleton application may serve as an [example](https://github.com/obsidiansystems/obelisk/blob/4759342ab3570888c027d4c58cb5694cb832d624/skeleton/default.nix#L13).
24+* Update reflex-platform dependency to v0.6.0.0
7* ([#715](https://github.com/obsidiansystems/obelisk/pull/715)) In `Obelisk.Route` deprecate `isoEncoder` and `prismEncoder` in favor of more precisely named `viewEncoder` and `reviewEncoder` (respectively) and improve documentation regarding contravariance of `reviewEncoder`.
25* ([#715](https://github.com/obsidiansystems/obelisk/pull/715)) In `Obelisk.Route` deprecate `isoEncoder` and `prismEncoder` in favor of more precisely named `viewEncoder` and `reviewEncoder` (respectively) and improve documentation regarding contravariance of `reviewEncoder`.
8* ([#739](https://github.com/obsidiansystems/obelisk/pull/739)) Improve `ob shell` by allowing commands to be passed verbatim after a `--` argument. For example, `ob shell 'run command'` can now be written `ob shell -- run command`.
26* ([#739](https://github.com/obsidiansystems/obelisk/pull/739)) Improve `ob shell` by allowing commands to be passed verbatim after a `--` argument. For example, `ob shell 'run command'` can now be written `ob shell -- run command`.
9* ([#735](https://github.com/obsidiansystems/obelisk/pull/735)) Fix regression causing custom `Prelude`s to break `ob run`/`ob watch`/`ob repl`.
27* ([#735](https://github.com/obsidiansystems/obelisk/pull/735)) Fix regression causing custom `Prelude`s to break `ob run`/`ob watch`/`ob repl`.
README.md+6−3
@@ -66,7 +66,10 @@ Obelisk assumes basic knowledge of [Haskell](https://www.haskell.org/) and [Refl
66 sudo launchctl stop org.nixos.nix-daemon
66 sudo launchctl stop org.nixos.nix-daemon
67 sudo launchctl start org.nixos.nix-daemon
67 sudo launchctl start org.nixos.nix-daemon
68 ```
68 ```
69+1. Install obelisk:
70+ ```bash
69−1. Install obelisk: `nix-env -f https://github.com/obsidiansystems/obelisk/archive/master.tar.gz -iA command`
71+ nix-env -f https://github.com/obsidiansystems/obelisk/archive/master.tar.gz -iA command
72+ ```
70
73
71### Accessing private repositories
74### Accessing private repositories
72
75
@@ -111,7 +114,7 @@ First create a new EC2 instance:
1111. Launch a NixOS 19.09 EC2 instance (we recommend [this AMI](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-00a8eeaf232a74f84))
1141. Launch a NixOS 19.09 EC2 instance (we recommend [this AMI](https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-00a8eeaf232a74f84))
1121. In the instance configuration wizard ensure that your instance has at least 1GB RAM and 10GB disk space.
1151. In the instance configuration wizard ensure that your instance has at least 1GB RAM and 10GB disk space.
1131. When prompted save your AWS private key (`~/myaws.pem`) somewhere safe. We'll need it later during deployment.
1161. When prompted save your AWS private key (`~/myaws.pem`) somewhere safe. We'll need it later during deployment.
114−1. Go to "Security Groups", select your instance's security group and under "Inbound" tab add a new rule for HTTP port 80 and 443.
117+1. Go to "Security Groups", select your instance's security group and under "Inbound" tab add a new rule for HTTP port 80 and HTTPS port 443.
115
118
116At this stage your instance should be booting and become accessible shortly. Note down the hostname of your EC2 instance.
119At this stage your instance should be booting and become accessible shortly. Note down the hostname of your EC2 instance.
117
120
@@ -218,7 +221,7 @@ Your account must also belong to an Apple Developer Team, if you want to access
2181. Click accept to authorize on both the computer and the iPhone.
2211. Click accept to authorize on both the computer and the iPhone.
219
222
220##### Xcode
223##### Xcode
221−Install Xcode 8.2 (contains iOS SDK 10.2) and open it so that it runs its post install tool setup.
224+Install Xcode 11.2 (contains iOS SDK 13.2) and open it so that it runs its post install tool setup.
222
225
223These versions will work out of the box but iOS SDKs prior to 11.3 should also work. You can choose another installed version in `default.nix`
226These versions will work out of the box but iOS SDKs prior to 11.3 should also work. You can choose another installed version in `default.nix`
224
227
default.nix+44−37
@@ -2,6 +2,9 @@
2, profiling ? false
2, profiling ? false
3, iosSdkVersion ? "13.2"
3, iosSdkVersion ? "13.2"
4, config ? {}
4, config ? {}
5+, terms ? { # Accepted terms, conditions, and licenses
6+ security.acme.acceptTerms = false;
7+ }
5, reflex-platform-func ? import ./dep/reflex-platform
8, reflex-platform-func ? import ./dep/reflex-platform
6}:
9}:
7let
10let
@@ -114,7 +117,7 @@ in rec {
114 "${routeHost}".email = adminEmail;
117 "${routeHost}".email = adminEmail;
115 } else {};
118 } else {};
116
119
117− security.acme.${if enableHttps then "acceptTerms" else null} = true;
120+ security.acme.${if enableHttps && (terms.security.acme.acceptTerms or false) then "acceptTerms" else null} = true;
118 };
121 };
119
122
120 mkObeliskApp =
123 mkObeliskApp =
@@ -138,6 +141,9 @@ in rec {
138 locations.${baseUrl} = {
141 locations.${baseUrl} = {
139 proxyPass = "http://127.0.0.1:" + toString internalPort;
142 proxyPass = "http://127.0.0.1:" + toString internalPort;
140 proxyWebsockets = true;
143 proxyWebsockets = true;
144+ extraConfig = ''
145+ access_log off;
146+ '';
141 };
147 };
142 };
148 };
143 };
149 };
@@ -180,7 +186,9 @@ in rec {
180 set -eux
186 set -eux
181 ln -s '${if profiling then backend else haskellLib.justStaticExecutables backend}'/bin/* $out/
187 ln -s '${if profiling then backend else haskellLib.justStaticExecutables backend}'/bin/* $out/
182 ln -s '${mkAssets assets}' $out/static.assets
188 ln -s '${mkAssets assets}' $out/static.assets
183− ln -s '${mkAssets (compressedJs frontend optimizationLevel)}'/* $out
189+ for d in '${mkAssets (compressedJs frontend optimizationLevel)}'/*/; do
190+ ln -s "$d" "$out"/"$(basename "$d").assets"
191+ done
184 echo ${version} > $out/version
192 echo ${version} > $out/version
185 '';
193 '';
186
194
@@ -329,39 +337,40 @@ in rec {
329 });
337 });
330 in allConfig;
338 in allConfig;
331 in (mkProject (projectDefinition args)).projectConfig);
339 in (mkProject (projectDefinition args)).projectConfig);
332− projectOutHost = projectOut { inherit system; };
340+ mainProjectOut = projectOut { inherit system; };
333 serverOn = projectInst: version: serverExe
341 serverOn = projectInst: version: serverExe
334 projectInst.ghc.backend
342 projectInst.ghc.backend
335− projectOutHost.ghcjs.frontend
343+ mainProjectOut.ghcjs.frontend
336 projectInst.passthru.staticFiles
344 projectInst.passthru.staticFiles
337 projectInst.passthru.__closureCompilerOptimizationLevel
345 projectInst.passthru.__closureCompilerOptimizationLevel
338 version;
346 version;
347+ linuxExe = serverOn (projectOut { system = "x86_64-linux"; });
339 dummyVersion = "Version number is only available for deployments";
348 dummyVersion = "Version number is only available for deployments";
349+ in mainProjectOut // {
340− profiledObRun =
350+ __unstable__.profiledObRun = let
341− let
342− profiled = projectOut { inherit system; enableLibraryProfiling = true; };
351+ profiled = projectOut { inherit system; enableLibraryProfiling = true; };
343− exeSource = builtins.toFile "ob-run-profiled.hs" ''
352+ exeSource = builtins.toFile "ob-run.hs" ''
344− {-# LANGUAGE NoImplicitPrelude #-}
353+ {-# LANGUAGE NoImplicitPrelude #-}
345− {-# LANGUAGE PackageImports #-}
354+ {-# LANGUAGE PackageImports #-}
346− module Main where
355+ module Main where
347−
356+
348− -- Explicitly import Prelude from base lest there be multiple modules called Prelude
357+ -- Explicitly import Prelude from base lest there be multiple modules called Prelude
349− import "base" Prelude (IO, (++), read)
358+ import "base" Prelude (IO, (++), read)
350−
359+
351− import "base" Control.Exception (finally)
360+ import "base" Control.Exception (finally)
352− import "reflex" Reflex.Profiled (writeProfilingData)
361+ import "reflex" Reflex.Profiled (writeProfilingData)
353− import "base" System.Environment (getArgs)
362+ import "base" System.Environment (getArgs)
354−
363+
355− import qualified "obelisk-run" Obelisk.Run
364+ import qualified "obelisk-run" Obelisk.Run
356− import qualified Frontend
365+ import qualified Frontend
357− import qualified Backend
366+ import qualified Backend
358−
367+
359− main :: IO ()
368+ main :: IO ()
360− main = do
369+ main = do
361− [portStr, assets, profFileName] <- getArgs
370+ [portStr, assets, profFileName] <- getArgs
362− Obelisk.Run.run (read portStr) (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend
371+ Obelisk.Run.run (read portStr) (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend
363− `finally` writeProfilingData (profFileName ++ ".rprof")
372+ `finally` writeProfilingData (profFileName ++ ".rprof")
364− '';
373+ '';
365 in nixpkgs.runCommand "ob-run" {
374 in nixpkgs.runCommand "ob-run" {
366 buildInputs = [ (profiled.ghc.ghcWithPackages (p: [p.backend p.frontend])) ];
375 buildInputs = [ (profiled.ghc.ghcWithPackages (p: [p.backend p.frontend])) ];
367 } ''
376 } ''
@@ -369,16 +378,14 @@ in rec {
369 mkdir -p $out/bin
378 mkdir -p $out/bin
370 ghc -x hs -prof -fno-prof-auto -threaded ob-run.hs -o $out/bin/ob-run
379 ghc -x hs -prof -fno-prof-auto -threaded ob-run.hs -o $out/bin/ob-run
371 '';
380 '';
372− in projectOutHost // lib.makeExtensible (self: {
381+
373− __unstable__.profiledObRun = profiledObRun;
382+ linuxExeConfigurable = linuxExe;
374− exeConfigurable = { system, version }: serverOn (projectOut { inherit system; }) version;
383+ linuxExe = linuxExe dummyVersion;
375− exe = self.exeConfigurable { inherit system; version = dummyVersion; };
384+ exe = serverOn mainProjectOut dummyVersion;
376− linuxExeConfigurable = version: self.exeConfigurable { system = "x86_64-linux"; inherit version; };
377− linuxExe = self.linuxExeConfigurable dummyVersion;
378 server = args@{ hostName, adminEmail, routeHost, enableHttps, version, module ? serverModules.mkBaseEc2 }:
385 server = args@{ hostName, adminEmail, routeHost, enableHttps, version, module ? serverModules.mkBaseEc2 }:
379− server (args // { exe = self.linuxExeConfigurable version; });
386+ server (args // { exe = linuxExe version; });
380 obelisk = import (base' + "/.obelisk/impl") {};
387 obelisk = import (base' + "/.obelisk/impl") {};
381− });
388+ };
382 haskellPackageSets = {
389 haskellPackageSets = {
383 inherit (reflex-platform) ghc ghcjs;
390 inherit (reflex-platform) ghc ghcjs;
384 };
391 };
dep/reflex-platform/github.json+3−3
@@ -1,8 +1,8 @@
1{
1{
2 "owner": "reflex-frp",
2 "owner": "reflex-frp",
3 "repo": "reflex-platform",
3 "repo": "reflex-platform",
4− "branch": "eac@tweaks-from-wrinkl",
4+ "branch": "release/0.7.0.0",
5 "private": false,
5 "private": false,
6− "rev": "7ae363f19849f7e97422b7a4d44ec25d5faa3838",
6+ "rev": "27a7851a780ff21472d85f9a02fb84635ef9027a",
7− "sha256": "0xhx6x5h0yaq581g7ibcfsw0h8wh4mn0pzl9zn4w9ish54mbn9fz"
7+ "sha256": "1m4zlw91dpjlbdchjyxhvhv3k7lilm0qa17dhhww868xxc27sann"
8}
8}
haskell-overlays/misc-deps.nix+8−2
@@ -9,8 +9,14 @@ let
9in
9in
10
10
11{
11{
12− hnix = haskellLib.dontCheck (haskellLib.doJailbreak (self.callCabal2nix "hnix" (hackGet ../dep/hnix) {}));
12+ regex-base = self.callHackage "regex-base" "0.94.0.0" {};
13+ regex-posix = self.callHackage "regex-posix" "0.96.0.0" {};
14+ regex-tdfa = self.callHackage "regex-tdfa" "1.3.1.0" {};
15+ test-framework = haskellLib.dontCheck (self.callHackage "test-framework" "0.8.2.0" {});
16+ hnix = haskellLib.dontCheck (self.callHackage "hnix" "0.8.0" {});
13− hnix-store-core = self.callHackage "hnix-store-core" "0.1.0.0" {};
17+ hnix-store-core = self.callHackage "hnix-store-core" "0.2.0.0" {};
18+
19+ aeson-gadt-th = self.callHackage "aeson-gadt-th" "0.2.4" {};
14
20
15 ghcid = self.callCabal2nix "ghcid" (hackGet ../dep/ghcid) {};
21 ghcid = self.callCabal2nix "ghcid" (hackGet ../dep/ghcid) {};
16 # Exports more internals
22 # Exports more internals
haskell-overlays/obelisk.nix+4
@@ -19,8 +19,12 @@ in
19 obelisk-command = haskellLib.overrideCabal (self.callCabal2nix "obelisk-command" (obeliskCleanSource ../lib/command) {}) {
19 obelisk-command = haskellLib.overrideCabal (self.callCabal2nix "obelisk-command" (obeliskCleanSource ../lib/command) {}) {
20 librarySystemDepends = [
20 librarySystemDepends = [
21 pkgs.jre
21 pkgs.jre
22+ pkgs.git
22 pkgs.nix
23 pkgs.nix
23 pkgs.nix-prefetch-git
24 pkgs.nix-prefetch-git
25+ pkgs.openssh
26+ pkgs.rsync
27+ pkgs.which
24 (haskellLib.justStaticExecutables self.ghcid)
28 (haskellLib.justStaticExecutables self.ghcid)
25 ];
29 ];
26 };
30 };
lib/command/obelisk-command.cabal+1−1
@@ -1,5 +1,5 @@
1name: obelisk-command
1name: obelisk-command
2−version: 0.8.0.0
2+version: 0.9.0.1
3cabal-version: >= 1.8
3cabal-version: >= 1.8
4build-type: Simple
4build-type: Simple
5
5
lib/command/src/Obelisk/Command.hs+4−6
@@ -138,7 +138,7 @@ packageNames = some (strArgument (metavar "PACKAGE-NAME..."))
138deployCommand :: ArgsConfig -> Parser DeployCommand
138deployCommand :: ArgsConfig -> Parser DeployCommand
139deployCommand cfg = hsubparser $ mconcat
139deployCommand cfg = hsubparser $ mconcat
140 [ command "init" $ info (DeployCommand_Init <$> deployInitOpts) $ progDesc "Initialize a deployment configuration directory"
140 [ command "init" $ info (DeployCommand_Init <$> deployInitOpts) $ progDesc "Initialize a deployment configuration directory"
141− , command "push" $ info (DeployCommand_Push <$> switchConfigParser <*> remoteBuilderParser) mempty
141+ , command "push" $ info (DeployCommand_Push <$> remoteBuilderParser) mempty
142 , command "test" $ info (DeployCommand_Test <$> platformP) $ progDesc "Test your obelisk project from a mobile platform."
142 , command "test" $ info (DeployCommand_Test <$> platformP) $ progDesc "Test your obelisk project from a mobile platform."
143 , command "update" $ info (pure DeployCommand_Update) $ progDesc "Update the deployment's src thunk to latest"
143 , command "update" $ info (pure DeployCommand_Update) $ progDesc "Update the deployment's src thunk to latest"
144 ]
144 ]
@@ -148,8 +148,6 @@ deployCommand cfg = hsubparser $ mconcat
148 , command "ios" $ info ((,) <$> pure IOS <*> fmap pure (strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal"))) mempty
148 , command "ios" $ info ((,) <$> pure IOS <*> fmap pure (strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal"))) mempty
149 ]
149 ]
150
150
151− switchConfigParser = flag (Just DoActivateDeployment) Nothing (long "upload-only" <> help "Only upload the new configuration but do not activate it")
152−
153 remoteBuilderParser :: Parser (Maybe RemoteBuilder)
151 remoteBuilderParser :: Parser (Maybe RemoteBuilder)
154 remoteBuilderParser =
152 remoteBuilderParser =
155 flag (if enabledByDefault then enabled else Nothing) enabled (mconcat
153 flag (if enabledByDefault then enabled else Nothing) enabled (mconcat
@@ -182,7 +180,7 @@ data RemoteBuilder = RemoteBuilder_ObeliskVM
182
180
183data DeployCommand
181data DeployCommand
184 = DeployCommand_Init DeployInitOpts
182 = DeployCommand_Init DeployInitOpts
185− | DeployCommand_Push (Maybe DoActivateDeployment) (Maybe RemoteBuilder)
183+ | DeployCommand_Push (Maybe RemoteBuilder)
186 | DeployCommand_Test (PlatformDeployment, [String])
184 | DeployCommand_Test (PlatformDeployment, [String])
187 | DeployCommand_Update
185 | DeployCommand_Update
188 deriving Show
186 deriving Show
@@ -386,9 +384,9 @@ ob = \case
386 ObCommand_Init source force -> initProject source force
384 ObCommand_Init source force -> initProject source force
387 ObCommand_Deploy dc -> case dc of
385 ObCommand_Deploy dc -> case dc of
388 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> deployInit deployOpts root
386 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> deployInit deployOpts root
389− DeployCommand_Push doActivate remoteBuilder -> do
387+ DeployCommand_Push remoteBuilder -> do
390 deployPath <- liftIO $ canonicalizePath "."
388 deployPath <- liftIO $ canonicalizePath "."
391− deployPush deployPath doActivate $ case remoteBuilder of
389+ deployPush deployPath $ case remoteBuilder of
392 Nothing -> pure []
390 Nothing -> pure []
393 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
391 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
394 DeployCommand_Update -> deployUpdate "."
392 DeployCommand_Update -> deployUpdate "."
lib/command/src/Obelisk/Command/Deploy.hs+41−53
@@ -17,7 +17,6 @@ import Data.Bits
17import qualified Data.ByteString.Lazy as BSL
17import qualified Data.ByteString.Lazy as BSL
18import Data.Default
18import Data.Default
19import qualified Data.Map as Map
19import qualified Data.Map as Map
20−import Data.Maybe (listToMaybe)
21import qualified Data.Set as Set
20import qualified Data.Set as Set
22import Data.String.Here.Interpolated (i)
21import Data.String.Here.Interpolated (i)
23import qualified Data.Text as T
22import qualified Data.Text as T
@@ -49,8 +48,6 @@ data DeployInitOpts = DeployInitOpts
49 , _deployInitOpts_enableHttps :: Bool
48 , _deployInitOpts_enableHttps :: Bool
50 } deriving Show
49 } deriving Show
51
50
52−data DoActivateDeployment = DoActivateDeployment deriving (Eq, Ord, Show)
53−
54deployInit :: MonadObelisk m => DeployInitOpts -> FilePath -> m ()
51deployInit :: MonadObelisk m => DeployInitOpts -> FilePath -> m ()
55deployInit deployOpts root = do
52deployInit deployOpts root = do
56 let deployDir = _deployInitOpts_outputDir deployOpts
53 let deployDir = _deployInitOpts_outputDir deployOpts
@@ -88,13 +85,12 @@ deployInit' thunkPtr (DeployInitOpts deployDir sshKeyPath hostnames route adminE
88 --accidentally create a production deployment that uses development
85 --accidentally create a production deployment that uses development
89 --credentials to connect to some resources. This could result in, e.g.,
86 --credentials to connect to some resources. This could result in, e.g.,
90 --production data backed up to a dev environment.
87 --production data backed up to a dev environment.
91− withSpinner "Creating project configuration directories" $ do
88+ withSpinner "Creating project configuration directories" $ liftIO $ do
92− callProcessAndLogOutput (Notice, Error) $
89+ mapM_ (createDirectoryIfMissing True)
93− proc "mkdir" [ "-p"
94− , deployDir </> "config" </> "backend"
90+ [ deployDir </> "config" </> "backend"
95− , deployDir </> "config" </> "common"
91+ , deployDir </> "config" </> "common"
96− , deployDir </> "config" </> "frontend"
92+ , deployDir </> "config" </> "frontend"
97− ]
93+ ]
98
94
99 let srcDir = deployDir </> "src"
95 let srcDir = deployDir </> "src"
100 withSpinner ("Creating source thunk (" <> T.pack (makeRelative deployDir srcDir) <> ")") $ do
96 withSpinner ("Creating source thunk (" <> T.pack (makeRelative deployDir srcDir) <> ")") $ do
@@ -120,28 +116,22 @@ setupObeliskImpl deployDir = liftIO $ do
120 createDirectoryIfMissing True implDir
116 createDirectoryIfMissing True implDir
121 writeFile (implDir </> "default.nix") $ "(import " <> toNixPath (goBackUp </> "src") <> " {}).obelisk"
117 writeFile (implDir </> "default.nix") $ "(import " <> toNixPath (goBackUp </> "src") <> " {}).obelisk"
122
118
123−deployPush :: MonadObelisk m => FilePath -> Maybe DoActivateDeployment -> m [String] -> m ()
119+deployPush :: MonadObelisk m => FilePath -> m [String] -> m ()
124−deployPush deployPath doActivateDeployment getNixBuilders = do
120+deployPush deployPath getNixBuilders = do
125 hosts <- Set.fromList . filter (/= mempty) . lines <$> readDeployConfig deployPath "backend_hosts"
121 hosts <- Set.fromList . filter (/= mempty) . lines <$> readDeployConfig deployPath "backend_hosts"
126 adminEmail <- readDeployConfig deployPath "admin_email"
122 adminEmail <- readDeployConfig deployPath "admin_email"
127 enableHttps <- read <$> readDeployConfig deployPath "enable_https"
123 enableHttps <- read <$> readDeployConfig deployPath "enable_https"
128 route <- readDeployConfig deployPath $ "config" </> "common" </> "route"
124 route <- readDeployConfig deployPath $ "config" </> "common" </> "route"
129 routeHost <- getHostFromRoute enableHttps route
125 routeHost <- getHostFromRoute enableHttps route
130− let
131− srcPath = deployPath </> "src"
126+ let srcPath = deployPath </> "src"
127+ thunkPtr <- readThunk srcPath >>= \case
128+ Right (ThunkData_Packed _ ptr) -> return ptr
129+ Right ThunkData_Checkout -> do
132− handleUnpackedWith f = checkGitCleanStatus srcPath True >>= \case
130+ checkGitCleanStatus srcPath True >>= \case
133− True -> f
131+ True -> packThunk (ThunkPackConfig False (ThunkConfig Nothing)) srcPath
134− False -> failWith $ "ob deploy push: ensure " <> T.pack srcPath <> " has no pending changes and latest is pushed upstream."
132+ False -> failWith $ T.pack $ "ob deploy push: ensure " <> srcPath <> " has no pending changes and latest is pushed upstream."
135−
133+ Left err -> failWith $ "ob deploy push: couldn't read src thunk: " <> T.pack (show err)
136− version' <- readThunk srcPath >>= \case
134+ let version = show . _thunkRev_commit $ _thunkPtr_rev thunkPtr
137− Right (ThunkData_Packed _ ptr) -> return $ Just $ show $ _thunkRev_commit $ _thunkPtr_rev ptr
138− Right ThunkData_Checkout -> handleUnpackedWith $ Just . show . _thunkRev_commit . _thunkPtr_rev <$> packThunk (ThunkPackConfig False (ThunkConfig Nothing)) srcPath
139− Left err -> do
140− putLog Warning [i|No valid thunk found at ${srcPath} due to ${err}: Attempting to find commit hash anyway.|]
141− handleUnpackedWith $ fmap show . listToMaybe . T.lines <$> readGitProcess srcPath ["rev-parse", "HEAD"]
142− version <- case version' of
143− Nothing -> failWith [i|Could not determine commit hash of ${srcPath}|]
144− Just v -> v <$ putLog Notice [i|Deploying commit ${v}|]
145 builders <- getNixBuilders
135 builders <- getNixBuilders
146 let moduleFile = deployPath </> "module.nix"
136 let moduleFile = deployPath </> "module.nix"
147 moduleFileExists <- liftIO $ doesFileExist moduleFile
137 moduleFileExists <- liftIO $ doesFileExist moduleFile
@@ -169,35 +159,33 @@ deployPush deployPath doActivateDeployment getNixBuilders = do
169 callProcess'
159 callProcess'
170 (Map.fromList [("NIX_SSHOPTS", unwords sshOpts)])
160 (Map.fromList [("NIX_SSHOPTS", unwords sshOpts)])
171 "nix-copy-closure" ["-v", "--to", "--use-substitutes", "root@" <> host, "--gzip", outputPath]
161 "nix-copy-closure" ["-v", "--to", "--use-substitutes", "root@" <> host, "--gzip", outputPath]
172− case doActivateDeployment of
173− Nothing -> putLog Warning "Skipping activation of deployment; server is unchanged"
174− Just _ -> do
175− withSpinner "Uploading config" $ ifor_ buildOutputByHost $ \host _ -> do
162+ withSpinner "Uploading config" $ ifor_ buildOutputByHost $ \host _ -> do
176− callProcessAndLogOutput (Notice, Warning) $
163+ callProcessAndLogOutput (Notice, Warning) $
177− proc "rsync"
164+ proc rsyncPath
178− [ "-e ssh " <> unwords sshOpts
165+ [ "-e " <> sshPath <> " " <> unwords sshOpts
179− , "-qarvz"
166+ , "-qarvz"
180− , deployPath </> "config"
167+ , deployPath </> "config"
181− , "root@" <> host <> ":/var/lib/backend"
168+ , "root@" <> host <> ":/var/lib/backend"
182− ]
169+ ]
183− --TODO: Create GC root so we're sure our closure won't go away during this time period
170+ --TODO: Create GC root so we're sure our closure won't go away during this time period
184− withSpinner "Switching to new configuration" $ ifor_ buildOutputByHost $ \host outputPath -> do
171+ withSpinner "Switching to new configuration" $ ifor_ buildOutputByHost $ \host outputPath -> do
185− callProcessAndLogOutput (Notice, Warning) $
172+ callProcessAndLogOutput (Notice, Warning) $
186− proc "ssh" $ sshOpts <>
173+ proc sshPath $ sshOpts <>
187− [ "root@" <> host
174+ [ "root@" <> host
188− , unwords
175+ , unwords
176+ -- Note that we don't want to $(staticWhich "nix-env") here, because this is executing on a remote machine
189− [ "nix-env -p /nix/var/nix/profiles/system --set " <> outputPath
177+ [ "nix-env -p /nix/var/nix/profiles/system --set " <> outputPath
190− , "&&"
178+ , "&&"
191− , "/nix/var/nix/profiles/system/bin/switch-to-configuration switch"
179+ , "/nix/var/nix/profiles/system/bin/switch-to-configuration switch"
192− ]
193 ]
180 ]
181+ ]
194 isClean <- checkGitCleanStatus deployPath True
182 isClean <- checkGitCleanStatus deployPath True
195 when (not isClean) $ do
183 when (not isClean) $ do
196 withSpinner "Committing changes to Git" $ do
184 withSpinner "Committing changes to Git" $ do
197− callProcessAndLogOutput (Debug, Error) $ proc "git"
185+ callProcessAndLogOutput (Debug, Error) $
198− ["-C", deployPath, "add", "."]
186+ gitProc deployPath ["add", "."]
199− callProcessAndLogOutput (Debug, Error) $ proc "git"
187+ callProcessAndLogOutput (Debug, Error) $
200− ["-C", deployPath, "commit", "-m", "New deployment"]
188+ gitProc deployPath ["commit", "-m", "New deployment"]
201 putLog Notice $ "Deployed => " <> T.pack route
189 putLog Notice $ "Deployed => " <> T.pack route
202 where
190 where
203 callProcess' envMap cmd args = do
191 callProcess' envMap cmd args = do
@@ -327,7 +315,7 @@ readDeployConfig deployDir fname = liftIO $ do
327
315
328verifyHostKey :: MonadObelisk m => FilePath -> FilePath -> String -> m ()
316verifyHostKey :: MonadObelisk m => FilePath -> FilePath -> String -> m ()
329verifyHostKey knownHostsPath keyPath hostName =
317verifyHostKey knownHostsPath keyPath hostName =
330− callProcessAndLogOutput (Notice, Warning) $ proc "ssh" $
318+ callProcessAndLogOutput (Notice, Warning) $ proc sshPath $
331 sshArgs knownHostsPath keyPath True <>
319 sshArgs knownHostsPath keyPath True <>
332 [ "root@" <> hostName
320 [ "root@" <> hostName
333 , "-o", "NumberOfPasswordPrompts=0"
321 , "-o", "NumberOfPasswordPrompts=0"
lib/command/src/Obelisk/Command/Preprocessor.hs+3−2
@@ -47,7 +47,8 @@ applyPackages origPath inPath outPath packagePaths' = do
47 Left err -> do
47 Left err -> do
48 hPutStrLn stderr $ "Error: Unable to parse cabal package " <> packagePath <> "; Skipping preprocessor on " <> origPath <> ". Error: " <> show err
48 hPutStrLn stderr $ "Error: Unable to parse cabal package " <> packagePath <> "; Skipping preprocessor on " <> origPath <> ". Error: " <> show err
49 pure Nothing
49 pure Nothing
50− Right (_, packageInfo) -> pure $ Just packageInfo
50+ Right (Just (_, packageInfo)) -> pure $ Just packageInfo
51+ Right Nothing -> pure Nothing
51
52
52 writeOutput packageInfo' inPath outPath
53 writeOutput packageInfo' inPath outPath
53
54
@@ -75,7 +76,7 @@ generateHeader origPath packageInfo =
75 ext -> (TL.fromString (show ext) :)
76 ext -> (TL.fromString (show ext) :)
76 showExt = \case
77 showExt = \case
77 EnableExtension ext -> [TL.fromString (show ext)]
78 EnableExtension ext -> [TL.fromString (show ext)]
78− DisableExtension _ -> []
79+ DisableExtension ext -> ["No" <> TL.fromString (show ext)]
79 UnknownExtension ext -> [TL.fromString ext]
80 UnknownExtension ext -> [TL.fromString ext]
80
81
81 ghcOptions =
82 ghcOptions =
lib/command/src/Obelisk/Command/Project.hs+10−4
@@ -51,7 +51,7 @@ import Obelisk.App (MonadObelisk)
51import Obelisk.CliApp
51import Obelisk.CliApp
52import Obelisk.Command.Nix
52import Obelisk.Command.Nix
53import Obelisk.Command.Thunk
53import Obelisk.Command.Thunk
54−import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath)
54+import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath, cp, nixShellPath)
55
55
56--TODO: Make this module resilient to random exceptions
56--TODO: Make this module resilient to random exceptions
57
57
@@ -116,7 +116,7 @@ initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
116 skel <- nixBuildAttrWithCache implDir "skeleton" --TODO: I don't think there's actually any reason to cache this
116 skel <- nixBuildAttrWithCache implDir "skeleton" --TODO: I don't think there's actually any reason to cache this
117
117
118 callProcessAndLogOutput (Notice, Error) $
118 callProcessAndLogOutput (Notice, Error) $
119− proc "cp"
119+ proc cp
120 [ "-r"
120 [ "-r"
121 , "--preserve=links"
121 , "--preserve=links"
122 , obDir
122 , obDir
@@ -126,7 +126,7 @@ initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
126
126
127 withSpinner "Copying project skeleton" $ do
127 withSpinner "Copying project skeleton" $ do
128 callProcessAndLogOutput (Notice, Error) $
128 callProcessAndLogOutput (Notice, Error) $
129− proc "cp"
129+ proc cp
130 [ "-r"
130 [ "-r"
131 , "--no-preserve=mode"
131 , "--no-preserve=mode"
132 , "-T"
132 , "-T"
@@ -137,6 +137,12 @@ initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
137 let configDir = "config"
137 let configDir = "config"
138 createDirectoryIfMissing False configDir
138 createDirectoryIfMissing False configDir
139 mapM_ (createDirectoryIfMissing False . (configDir </>)) ["backend", "common", "frontend"]
139 mapM_ (createDirectoryIfMissing False . (configDir </>)) ["backend", "common", "frontend"]
140+ putLog Notice $ T.intercalate "\n"
141+ [ "An obelisk project has been successfully initialized. Next steps:"
142+ , " 'ob run': Start a development server"
143+ , " 'ob watch': Watch for changes without starting a server"
144+ , " 'ob repl': Load your project into GHCi"
145+ ]
140
146
141callHandoffOb
147callHandoffOb
142 :: MonadObelisk m
148 :: MonadObelisk m
@@ -292,7 +298,7 @@ bashEscape :: String -> String
292bashEscape = BSU.toString . bytes . bash . BSU.fromString
298bashEscape = BSU.toString . bytes . bash . BSU.fromString
293
299
294nixShellRunProc :: NixShellConfig -> ProcessSpec
300nixShellRunProc :: NixShellConfig -> ProcessSpec
295−nixShellRunProc cfg = setDelegateCtlc True $ proc "nix-shell" $ runNixShellConfig cfg
301+nixShellRunProc cfg = setDelegateCtlc True $ proc nixShellPath $ runNixShellConfig cfg
296
302
297nixShellWithoutPkgs
303nixShellWithoutPkgs
298 :: MonadObelisk m
304 :: MonadObelisk m
lib/command/src/Obelisk/Command/Run.hs+9−11
@@ -14,7 +14,7 @@ module Obelisk.Command.Run where
14import Control.Arrow ((&&&))
14import Control.Arrow ((&&&))
15import Control.Exception (Exception, bracket)
15import Control.Exception (Exception, bracket)
16import Control.Lens (ifor, (.~), (&))
16import Control.Lens (ifor, (.~), (&))
17−import Control.Monad (filterM, unless)
17+import Control.Monad (filterM)
18import Control.Monad.Except (runExceptT, throwError)
18import Control.Monad.Except (runExceptT, throwError)
19import Control.Monad.IO.Class (liftIO)
19import Control.Monad.IO.Class (liftIO)
20import Control.Monad.Reader (MonadIO)
20import Control.Monad.Reader (MonadIO)
@@ -304,15 +304,16 @@ parseCabalPackage
304 -> m (Maybe CabalPackageInfo)
304 -> m (Maybe CabalPackageInfo)
305parseCabalPackage dir = parseCabalPackage' dir >>= \case
305parseCabalPackage dir = parseCabalPackage' dir >>= \case
306 Left err -> Nothing <$ putLog Error err
306 Left err -> Nothing <$ putLog Error err
307− Right (warnings, pkgInfo) -> do
307+ Right (Just (warnings, pkgInfo)) -> do
308 for_ warnings $ putLog Warning . T.pack . show
308 for_ warnings $ putLog Warning . T.pack . show
309 pure $ Just pkgInfo
309 pure $ Just pkgInfo
310+ Right Nothing -> pure Nothing
310
311
311-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
312-- | Like 'parseCabalPackage' but returns errors and warnings directly so as to avoid 'MonadObelisk'.
312parseCabalPackage'
313parseCabalPackage'
313 :: (MonadIO m)
314 :: (MonadIO m)
314 => FilePath -- ^ Package directory
315 => FilePath -- ^ Package directory
315− -> m (Either T.Text ([Dist.PWarning], CabalPackageInfo))
316+ -> m (Either T.Text (Maybe ([Dist.PWarning], CabalPackageInfo)))
316parseCabalPackage' pkg = runExceptT $ do
317parseCabalPackage' pkg = runExceptT $ do
317 (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
318 (cabalContents, packageFile, packageName) <- guessCabalPackageFile pkg >>= \case
318 Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
319 Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack pkg
@@ -340,7 +341,7 @@ parseCabalPackage' pkg = runExceptT $ do
340 case condLibrary <$> result of
341 case condLibrary <$> result of
341 Right (Just condLib) -> do
342 Right (Just condLib) -> do
342 let (_, lib) = simplifyCondTree evalConfVar condLib
343 let (_, lib) = simplifyCondTree evalConfVar condLib
343− pure $ (warnings,) $ CabalPackageInfo
344+ pure $ Just $ (warnings,) $ CabalPackageInfo
344 { _cabalPackageInfo_packageName = T.pack packageName
345 { _cabalPackageInfo_packageName = T.pack packageName
345 , _cabalPackageInfo_packageFile = packageFile
346 , _cabalPackageInfo_packageFile = packageFile
346 , _cabalPackageInfo_packageRoot = takeDirectory packageFile
347 , _cabalPackageInfo_packageRoot = takeDirectory packageFile
@@ -354,17 +355,17 @@ parseCabalPackage' pkg = runExceptT $ do
354 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
355 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
355 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
356 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
356 }
357 }
357− Right Nothing -> throwError "Haskell package has no library component"
358+ Right Nothing -> pure Nothing
358 Left (_, errors) ->
359 Left (_, errors) ->
359 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
360 throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
360
361
361parsePackagesOrFail :: (MonadObelisk m, Foldable f) => f FilePath -> m (NE.NonEmpty CabalPackageInfo)
362parsePackagesOrFail :: (MonadObelisk m, Foldable f) => f FilePath -> m (NE.NonEmpty CabalPackageInfo)
362parsePackagesOrFail dirs' = do
363parsePackagesOrFail dirs' = do
363− (pkgDirErrs, packageInfos') <- fmap partitionEithers $ for dirs $ \dir -> do
364+ packageInfos' <- fmap catMaybes $ for dirs $ \dir -> do
364 flip fmap (parseCabalPackage dir) $ \case
365 flip fmap (parseCabalPackage dir) $ \case
365 Just packageInfo
366 Just packageInfo
366− | _cabalPackageInfo_buildable packageInfo -> Right packageInfo
367+ | _cabalPackageInfo_buildable packageInfo -> Just packageInfo
367− _ -> Left dir
368+ _ -> Nothing
368
369
369 -- Sort duplicate packages such that we prefer shorter paths, but fall back to alphabetical ordering.
370 -- Sort duplicate packages such that we prefer shorter paths, but fall back to alphabetical ordering.
370 let packagesByName = Map.map (NE.sortBy $ comparing $ \p -> let n = _cabalPackageInfo_packageFile p in (length n, n))
371 let packagesByName = Map.map (NE.sortBy $ comparing $ \p -> let n = _cabalPackageInfo_packageFile p in (length n, n))
@@ -386,9 +387,6 @@ parsePackagesOrFail dirs' = do
386 "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " dirs)
387 "No valid, buildable packages found" <> (if null dirs then "" else " in " <> intercalate ", " dirs)
387 Just xs -> pure xs
388 Just xs -> pure xs
388
389
389− unless (null pkgDirErrs) $
390− putLog Warning $ T.pack $ "Failed to find buildable packages in " <> intercalate ", " pkgDirErrs
391−
392 pure packageInfos
390 pure packageInfos
393 where
391 where
394 dirs = toList dirs'
392 dirs = toList dirs'
lib/command/src/Obelisk/Command/Thunk.hs+42−34
@@ -26,8 +26,9 @@ import qualified Data.ByteString.Lazy as LBS
26import Data.Containers.ListUtils (nubOrd)
26import Data.Containers.ListUtils (nubOrd)
27import Data.Default
27import Data.Default
28import Data.Either.Combinators (fromRight', rightToMaybe)
28import Data.Either.Combinators (fromRight', rightToMaybe)
29−import Data.Foldable (for_, toList)
29+import Data.Foldable (toList)
30import Data.Function ((&))
30import Data.Function ((&))
31+import Data.Functor ((<&>))
31import Data.Git.Ref (Ref)
32import Data.Git.Ref (Ref)
32import qualified Data.Git.Ref as Ref
33import qualified Data.Git.Ref as Ref
33import qualified Data.List as L
34import qualified Data.List as L
@@ -237,38 +238,35 @@ matchThunkSpecToDir
237 -> Set FilePath -- ^ Set of file paths relative to the given directory
238 -> Set FilePath -- ^ Set of file paths relative to the given directory
238 -> m ThunkData
239 -> m ThunkData
239matchThunkSpecToDir thunkSpec dir dirFiles = do
240matchThunkSpecToDir thunkSpec dir dirFiles = do
240− isCheckout <- fmap or $ flip Map.traverseWithKey (_thunkSpec_files thunkSpec) $ \expectedPath -> \case
241− ThunkFileSpec_CheckoutIndicator -> liftIO (doesDirectoryExist (dir </> expectedPath))
242− _ -> pure False
243− case isCheckout of
244− True -> pure ThunkData_Checkout
245− False -> do
246− for_ (nonEmpty (toList $ dirFiles `Set.difference` expectedPaths)) $ \fs ->
241+ case nonEmpty (toList $ dirFiles `Set.difference` expectedPaths) of
247− throwError $ ReadThunkError_UnrecognizedPaths $ (dir </>) <$> fs
242+ Just fs -> throwError $ ReadThunkError_UnrecognizedPaths $ (dir </>) <$> fs
243+ Nothing -> pure ()
248− for_ (nonEmpty (toList $ requiredPaths `Set.difference` dirFiles)) $ \fs ->
244+ case nonEmpty (toList $ requiredPaths `Set.difference` dirFiles) of
249− throwError $ ReadThunkError_MissingPaths $ (dir </>) <$> fs
245+ Just fs -> throwError $ ReadThunkError_MissingPaths $ (dir </>) <$> fs
246+ Nothing -> pure ()
250− datas <- fmap toList $ flip Map.traverseMaybeWithKey (_thunkSpec_files thunkSpec) $ \expectedPath -> \case
247+ datas <- fmap toList $ flip Map.traverseMaybeWithKey (_thunkSpec_files thunkSpec) $ \expectedPath -> \case
251− ThunkFileSpec_AttrCache -> Nothing <$ dirMayExist expectedPath
248+ ThunkFileSpec_AttrCache -> Nothing <$ dirMayExist expectedPath
252− ThunkFileSpec_CheckoutIndicator -> pure Nothing -- Handled above
249+ ThunkFileSpec_CheckoutIndicator -> liftIO (doesDirectoryExist (dir </> expectedPath)) <&> \case
250+ False -> Nothing
251+ True -> Just ThunkData_Checkout
253− ThunkFileSpec_FileMatches expectedContents -> handle (\(e :: IOError) -> throwError $ ReadThunkError_FileError e) $ do
252+ ThunkFileSpec_FileMatches expectedContents -> handle (\(e :: IOError) -> throwError $ ReadThunkError_FileError e) $ do
254− actualContents <- liftIO (T.readFile $ dir </> expectedPath)
253+ actualContents <- liftIO (T.readFile $ dir </> expectedPath)
255− case T.strip expectedContents == T.strip actualContents of
254+ case T.strip expectedContents == T.strip actualContents of
256− True -> pure Nothing
255+ True -> pure Nothing
257− False -> throwError $ ReadThunkError_FileDoesNotMatch (dir </> expectedPath) expectedContents
256+ False -> throwError $ ReadThunkError_FileDoesNotMatch (dir </> expectedPath) expectedContents
258− ThunkFileSpec_Ptr parser -> handle (\(e :: IOError) -> throwError $ ReadThunkError_FileError e) $ do
257+ ThunkFileSpec_Ptr parser -> handle (\(e :: IOError) -> throwError $ ReadThunkError_FileError e) $ do
259− let path = dir </> expectedPath
258+ let path = dir </> expectedPath
260− liftIO (doesFileExist path) >>= \case
259+ liftIO (doesFileExist path) >>= \case
261− False -> pure Nothing
260+ False -> pure Nothing
262− True -> do
261+ True -> do
263− actualContents <- liftIO $ LBS.readFile path
262+ actualContents <- liftIO $ LBS.readFile path
264− case parser actualContents of
263+ case parser actualContents of
265− Right v -> pure $ Just (thunkSpec, v)
264+ Right v -> pure $ Just (ThunkData_Packed thunkSpec v)
266− Left e -> throwError $ ReadThunkError_UnparseablePtr (dir </> expectedPath) e
265+ Left e -> throwError $ ReadThunkError_UnparseablePtr (dir </> expectedPath) e
267−
266+
268− uncurry ThunkData_Packed <$> case nonEmpty datas of
267+ case nonEmpty datas of
269− Nothing -> throwError ReadThunkError_UnrecognizedThunk
268+ Nothing -> throwError ReadThunkError_UnrecognizedThunk
270− Just xs -> fold1WithM xs $ \a@(_, ptrA) (_, ptrB) ->
269+ Just xs -> fold1WithM xs $ \a b -> either throwError pure (mergeThunkData a b)
271− if ptrA == ptrB then pure a else throwError $ ReadThunkError_AmbiguousPackedState ptrA ptrB
272 where
270 where
273 rootPathsOnly = Set.fromList . mapMaybe takeRootDir . Map.keys
271 rootPathsOnly = Set.fromList . mapMaybe takeRootDir . Map.keys
274 takeRootDir = fmap NonEmpty.head . nonEmpty . splitPath
272 takeRootDir = fmap NonEmpty.head . nonEmpty . splitPath
@@ -284,6 +282,15 @@ matchThunkSpecToDir thunkSpec dir dirFiles = do
284 True -> throwError $ ReadThunkError_UnrecognizedPaths $ expectedPath :| []
282 True -> throwError $ ReadThunkError_UnrecognizedPaths $ expectedPath :| []
285 False -> pure ()
283 False -> pure ()
286
284
285+ -- Combine 'ThunkData' from different files, preferring "Checkout" over "Packed"
286+ mergeThunkData ThunkData_Checkout ThunkData_Checkout = Right ThunkData_Checkout
287+ mergeThunkData ThunkData_Checkout ThunkData_Packed{} = Left bothPackedAndUnpacked
288+ mergeThunkData ThunkData_Packed{} ThunkData_Checkout = Left bothPackedAndUnpacked
289+ mergeThunkData a@(ThunkData_Packed _ ptrA) (ThunkData_Packed _ ptrB) =
290+ if ptrA == ptrB then Right a else Left $ ReadThunkError_AmbiguousPackedState ptrA ptrB
291+
292+ bothPackedAndUnpacked = ReadThunkError_UnrecognizedState "Both packed data and checkout present"
293+
287 fold1WithM (x :| xs) f = foldM f x xs
294 fold1WithM (x :| xs) f = foldM f x xs
288
295
289readThunkWith
296readThunkWith
@@ -298,7 +305,8 @@ readThunkWith specTypes dir = do
298 Left e -> putLog Debug [i|Thunk specification ${_thunkSpec_name spec} did not match ${dir}: ${e}|] *> loop rest
305 Left e -> putLog Debug [i|Thunk specification ${_thunkSpec_name spec} did not match ${dir}: ${e}|] *> loop rest
299 x@(Right _) -> x <$ putLog Debug [i|Thunk specification ${_thunkSpec_name spec} matched ${dir}|]
306 x@(Right _) -> x <$ putLog Debug [i|Thunk specification ${_thunkSpec_name spec} matched ${dir}|]
300
307
301−-- | Read a packed or unpacked thunk based on predefined thunk specifications.
308+-- | Read a thunk and validate that it is exactly a packed thunk.
309+-- If additional data is present, fail.
302readThunk :: (MonadObelisk m) => FilePath -> m (Either ReadThunkError ThunkData)
310readThunk :: (MonadObelisk m) => FilePath -> m (Either ReadThunkError ThunkData)
303readThunk = readThunkWith thunkSpecTypes
311readThunk = readThunkWith thunkSpecTypes
304
312
lib/command/src/Obelisk/Command/Utils.hs+27−1
@@ -60,6 +60,32 @@ nixPrefetchGitPath = $(staticWhich "nix-prefetch-git")
60nixPrefetchUrlPath :: FilePath
60nixPrefetchUrlPath :: FilePath
61nixPrefetchUrlPath = $(staticWhich "nix-prefetch-url")
61nixPrefetchUrlPath = $(staticWhich "nix-prefetch-url")
62
62
63+nixShellPath :: FilePath
64+nixShellPath = $(staticWhich "nix-shell")
65+
66+rsyncPath :: FilePath
67+rsyncPath = $(staticWhich "rsync")
68+
69+sshPath :: FilePath
70+sshPath = $(staticWhich "ssh")
71+
72+gitPath :: FilePath
73+gitPath = $(staticWhich "git")
74+
75+whichPath :: FilePath
76+whichPath = $(staticWhich "which")
77+
78+sshKeygenPath :: FilePath
79+sshKeygenPath = $(staticWhich "ssh-keygen")
80+
81+-- $(staticWhich "docker") was intentionally omitted, at least for now
82+-- One concern is that I don't know how particular docker is about having the
83+-- CLI exe match the version of the docker daemon, which is largely outside of
84+-- the control of obelisk-command.
85+-- TODO: Investigate the tradeoffs associated with this choice
86+dockerPath :: FilePath
87+dockerPath = "docker"
88+
63-- | Nix syntax requires relative paths to be prefixed by @./@ or
89-- | Nix syntax requires relative paths to be prefixed by @./@ or
64-- @../@. This will make a 'FilePath' that can be embedded in a Nix
90-- @../@. This will make a 'FilePath' that can be embedded in a Nix
65-- expression.
91-- expression.
@@ -97,7 +123,7 @@ initGit repo = do
97 git ["commit", "-m", "Initial commit."]
123 git ["commit", "-m", "Initial commit."]
98
124
99gitProcNoRepo :: [String] -> ProcessSpec
125gitProcNoRepo :: [String] -> ProcessSpec
100−gitProcNoRepo args = setEnvOverride (M.singleton "GIT_TERMINAL_PROMPT" "0" <>) $ proc "git" args
126+gitProcNoRepo args = setEnvOverride (M.singleton "GIT_TERMINAL_PROMPT" "0" <>) $ proc gitPath args
101
127
102gitProc :: FilePath -> [String] -> ProcessSpec
128gitProc :: FilePath -> [String] -> ProcessSpec
103gitProc repo = gitProcNoRepo . runGitInDir
129gitProc repo = gitProcNoRepo . runGitInDir
lib/command/src/Obelisk/Command/VmBuilder.hs+8−8
@@ -21,7 +21,7 @@ import qualified System.Info
21
21
22import Obelisk.App (MonadObelisk, getObeliskUserStateDir)
22import Obelisk.App (MonadObelisk, getObeliskUserStateDir)
23import Obelisk.CliApp
23import Obelisk.CliApp
24−import Obelisk.Command.Utils (rmPath)
24+import Obelisk.Command.Utils (rmPath, whichPath, sshKeygenPath, nixBuildExePath, dockerPath)
25
25
26-- | Generate the `--builders` argument string to enable the VM builder after ensuring it is available.
26-- | Generate the `--builders` argument string to enable the VM builder after ensuring it is available.
27getNixBuildersArg :: MonadObelisk m => m String
27getNixBuildersArg :: MonadObelisk m => m String
@@ -35,7 +35,7 @@ getNixBuildersArg = do
35
35
36checkForNixDarwin :: MonadObelisk m => String -> m ()
36checkForNixDarwin :: MonadObelisk m => String -> m ()
37checkForNixDarwin sshIdFile = do
37checkForNixDarwin sshIdFile = do
38− (exitCode, _, _) <- readCreateProcessWithExitCode $ proc "which" ["darwin-rebuild"]
38+ (exitCode, _, _) <- readCreateProcessWithExitCode $ proc whichPath ["darwin-rebuild"]
39 unless (exitCode == ExitSuccess) $ failWith $ T.intercalate "\n"
39 unless (exitCode == ExitSuccess) $ failWith $ T.intercalate "\n"
40 [ "Deployments from macOS require nix-darwin to be installed."
40 [ "Deployments from macOS require nix-darwin to be installed."
41 , "Follow the installation instructions here: https://github.com/LnL7/nix-darwin"
41 , "Follow the installation instructions here: https://github.com/LnL7/nix-darwin"
@@ -67,7 +67,7 @@ containerExists :: MonadObelisk m => FilePath -> m Bool
67containerExists stateDir = handle (\(_ :: IOError) -> failWith needDockerMsg) $ do
67containerExists stateDir = handle (\(_ :: IOError) -> failWith needDockerMsg) $ do
68 containerNames <- fmap (map T.strip . T.lines) $
68 containerNames <- fmap (map T.strip . T.lines) $
69 readProcessAndLogStderr Error $
69 readProcessAndLogStderr Error $
70− proc "docker" ["container", "list", "--all", "--format", "{{.Names}}"]
70+ proc dockerPath ["container", "list", "--all", "--format", "{{.Names}}"]
71 let exists = containerName `elem` containerNames
71 let exists = containerName `elem` containerNames
72 when exists $ testLinuxBuild stateDir
72 when exists $ testLinuxBuild stateDir
73 pure exists
73 pure exists
@@ -85,7 +85,7 @@ containerSshPort = 2222
85startContainer :: MonadObelisk m => m ()
85startContainer :: MonadObelisk m => m ()
86startContainer = withSpinner "Starting VM builder" $
86startContainer = withSpinner "Starting VM builder" $
87 callProcessAndLogOutput (Debug, Debug) $
87 callProcessAndLogOutput (Debug, Debug) $
88− proc "docker" ["start", containerName]
88+ proc dockerPath ["start", containerName]
89
89
90-- | Creates the Docker container; assumes it does not exist.
90-- | Creates the Docker container; assumes it does not exist.
91setupNixDocker :: MonadObelisk m => FilePath -> m ()
91setupNixDocker :: MonadObelisk m => FilePath -> m ()
@@ -98,12 +98,12 @@ setupNixDocker stateDir = withSpinner ("Creating Docker container named " <> con
98 callProcessAndLogOutput (Debug, Error) $
98 callProcessAndLogOutput (Debug, Error) $
99 proc rmPath ["-f", stateDir </> sshKeyFileName, stateDir </> sshKeyFileName <.> "pub"]
99 proc rmPath ["-f", stateDir </> sshKeyFileName, stateDir </> sshKeyFileName <.> "pub"]
100 callProcessAndLogOutput (Debug, Error) $
100 callProcessAndLogOutput (Debug, Error) $
101− proc "ssh-keygen" ["-t", "ed25519", "-f", stateDir </> sshKeyFileName, "-P", ""]
101+ proc sshKeygenPath ["-t", "ed25519", "-f", stateDir </> sshKeyFileName, "-P", ""]
102
102
103 -- Build the docker container (which uses the SSH keys in the 'ssh' folder)
103 -- Build the docker container (which uses the SSH keys in the 'ssh' folder)
104 containerId <- fmap T.strip $ readProcessAndLogStderr Error $
104 containerId <- fmap T.strip $ readProcessAndLogStderr Error $
105− proc "docker" ["build", stateDir, "--quiet"]
105+ proc dockerPath ["build", stateDir, "--quiet"]
106− callProcessAndLogOutput (Debug, Error) $ proc "docker"
106+ callProcessAndLogOutput (Debug, Error) $ proc dockerPath
107 [ "run"
107 [ "run"
108 , "--restart", "always"
108 , "--restart", "always"
109 , "--detach"
109 , "--detach"
@@ -155,7 +155,7 @@ testLinuxBuild :: MonadObelisk m => FilePath -> m ()
155testLinuxBuild stateDir
155testLinuxBuild stateDir
156 | System.Info.os == "linux" = failWith "Using the docker builder is not necessary on linux."
156 | System.Info.os == "linux" = failWith "Using the docker builder is not necessary on linux."
157 | otherwise = do
157 | otherwise = do
158− (exitCode, _stdout, stderr) <- readCreateProcessWithExitCode $ proc "nix-build"
158+ (exitCode, _stdout, stderr) <- readCreateProcessWithExitCode $ proc nixBuildExePath
159 [ "--no-out-link"
159 [ "--no-out-link"
160 , "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
160 , "-E", "(import <nixpkgs> { system = \"x86_64-linux\"; }).writeText \"test\" builtins.currentTime"
161 , "--builders", nixBuildersArgString stateDir
161 , "--builders", nixBuildersArgString stateDir
lib/executable-config/default.nix+2−3
@@ -5,14 +5,13 @@
5{ lib
5{ lib
6, runCommand
6, runCommand
7, obeliskCleanSource
7, obeliskCleanSource
8−, staticDirName ? ""
9}:
8}:
10
9
11let
10let
12 injectConfig = config: assets: runCommand "inject-config" {} (''
11 injectConfig = config: assets: runCommand "inject-config" {} (''
13 set -x
12 set -x
14− mkdir -p "$out"/'${staticDirName}'
13+ mkdir -p $out
15− cp --no-preserve=mode -Lr "${assets}"/. "$out"/'${staticDirName}'/
14+ cp --no-preserve=mode -Lr "${assets}" $out/static
16 chmod +w "$out"
15 chmod +w "$out"
17 '' + lib.optionalString (!(builtins.isNull config)) ''
16 '' + lib.optionalString (!(builtins.isNull config)) ''
18 if ! mkdir $out/config; then
17 if ! mkdir $out/config; then
lib/executable-config/lookup/src/Obelisk/Configs.hs+2
@@ -22,6 +22,7 @@ import Control.Applicative (Alternative)
22import Control.Monad (MonadPlus)
22import Control.Monad (MonadPlus)
23import Control.Monad.Base (MonadBase)
23import Control.Monad.Base (MonadBase)
24import Control.Monad.Catch (MonadThrow)
24import Control.Monad.Catch (MonadThrow)
25+import Control.Monad.Fail (MonadFail)
25import Control.Monad.Fix (MonadFix)
26import Control.Monad.Fix (MonadFix)
26import Control.Monad.IO.Class (MonadIO)
27import Control.Monad.IO.Class (MonadIO)
27import Control.Monad.Morph (MFunctor)
28import Control.Monad.Morph (MFunctor)
@@ -87,6 +88,7 @@ newtype ConfigsT m a = ConfigsT { unConfigsT :: ReaderT (Map Text ByteString) m
87 , Monad
88 , Monad
88 , MonadPlus
89 , MonadPlus
89 , Alternative
90 , Alternative
91+ , MonadFail
90 , MonadFix
92 , MonadFix
91 , MonadThrow
93 , MonadThrow
92 , MonadIO
94 , MonadIO
lib/route/src/Obelisk/Route.hs+3−2
@@ -170,6 +170,7 @@ import Data.Text (Text)
170import qualified Data.Text as T
170import qualified Data.Text as T
171import qualified Data.Text.Encoding as T
171import qualified Data.Text.Encoding as T
172import Data.Text.Lens (IsText, packed, unpacked)
172import Data.Text.Lens (IsText, packed, unpacked)
173+import Data.Type.Equality
173import Data.Universe
174import Data.Universe
174import Data.Universe.Some
175import Data.Universe.Some
175import Network.HTTP.Types.URI
176import Network.HTTP.Types.URI
@@ -1038,8 +1039,8 @@ someSumEncoder = Encoder $ pure $ EncoderImpl
1038
1039
1039data Void1 :: * -> * where {}
1040data Void1 :: * -> * where {}
1040
1041
1041−instance Universe (Some Void1) where
1042+instance UniverseSome Void1 where
1042− universe = []
1043+ universeSome = []
1043
1044
1044void1Encoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Some Void1) a
1045void1Encoder :: (Applicative check, MonadError Text parse) => Encoder check parse (Some Void1) a
1045void1Encoder = Encoder $ pure $ EncoderImpl
1046void1Encoder = Encoder $ pure $ EncoderImpl
lib/run/src/Obelisk/Run.hs+3−3
@@ -144,9 +144,9 @@ runWidget conf configs frontend validFullEncoder = do
144
144
145 cert <- X509.newX509 >>= X509Request.makeX509FromReq certRequest
145 cert <- X509.newX509 >>= X509Request.makeX509FromReq certRequest
146 _ <- X509.setPublicKey cert privateKey
146 _ <- X509.setPublicKey cert privateKey
147− now <- getCurrentTime
147+ timenow <- getCurrentTime
148− _ <- X509.setNotBefore cert $ addUTCTime (-1) now
148+ _ <- X509.setNotBefore cert $ addUTCTime (-1) timenow
149− _ <- X509.setNotAfter cert $ addUTCTime (365 * 24 * 60 * 60) now
149+ _ <- X509.setNotAfter cert $ addUTCTime (365 * 24 * 60 * 60) timenow
150 _ <- X509.signX509 cert privateKey Nothing
150 _ <- X509.signX509 cert privateKey Nothing
151
151
152 certByteString <- BSUTF8.fromString <$> PEM.writeX509 cert
152 certByteString <- BSUTF8.fromString <$> PEM.writeX509 cert
lib/snap-extras/src/Obelisk/Snap/Extras.hs+1−1
@@ -17,7 +17,7 @@ import System.Directory
17-- | Set response header for "permanent" caching
17-- | Set response header for "permanent" caching
18cachePermanently :: MonadSnap m => m ()
18cachePermanently :: MonadSnap m => m ()
19cachePermanently = do
19cachePermanently = do
20− modifyResponse $ setHeader "Cache-Control" "public, max-age=315360000"
20+ modifyResponse $ setHeader "Cache-Control" "public, max-age=315360000, immutable"
21 modifyResponse $ setHeader "Expires" "Tue, 01 Feb 2050 00:00:00 GMT" --TODO: This should be set to "approximately one year from the time the response is sent"
21 modifyResponse $ setHeader "Expires" "Tue, 01 Feb 2050 00:00:00 GMT" --TODO: This should be set to "approximately one year from the time the response is sent"
22
22
23-- | Set response header to not cache
23-- | Set response header to not cache
skeleton/backend/backend.cabal+2−2
@@ -14,12 +14,12 @@ library
14 , obelisk-route
14 , obelisk-route
15 exposed-modules:
15 exposed-modules:
16 Backend
16 Backend
17− ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O
17+ ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
18
18
19executable backend
19executable backend
20 main-is: main.hs
20 main-is: main.hs
21 hs-source-dirs: src-bin
21 hs-source-dirs: src-bin
22− ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -threaded
22+ ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -threaded -fno-show-valid-hole-fits
23 if impl(ghcjs)
23 if impl(ghcjs)
24 buildable: False
24 buildable: False
25 build-depends: base
25 build-depends: base
skeleton/common/common.cabal+1−1
@@ -12,4 +12,4 @@ library
12 exposed-modules:
12 exposed-modules:
13 Common.Api
13 Common.Api
14 Common.Route
14 Common.Route
15− ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O
15+ ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
skeleton/default.nix+9−2
@@ -1,10 +1,17 @@
1+{ system ? builtins.currentSystem
1−{ obelisk ? import ./.obelisk/impl {
2+, obelisk ? import ./.obelisk/impl {
2− system = builtins.currentSystem;
3+ inherit system;
3 iosSdkVersion = "13.2";
4 iosSdkVersion = "13.2";
5+
4 # You must accept the Android Software Development Kit License Agreement at
6 # You must accept the Android Software Development Kit License Agreement at
5 # https://developer.android.com/studio/terms in order to build Android apps.
7 # https://developer.android.com/studio/terms in order to build Android apps.
6 # Uncomment and set this to `true` to indicate your acceptance:
8 # Uncomment and set this to `true` to indicate your acceptance:
7 # config.android_sdk.accept_license = false;
9 # config.android_sdk.accept_license = false;
10+
11+ # In order to use Let's Encrypt for HTTPS deployments you must accept
12+ # their terms of service at https://letsencrypt.org/repository/.
13+ # Uncomment and set this to `true` to indicate your acceptance:
14+ # terms.security.acme.acceptTerms = false;
8 }
15 }
9}:
16}:
10with obelisk;
17with obelisk;
skeleton/frontend/frontend.cabal+2−2
@@ -16,7 +16,7 @@ library
16 , text
16 , text
17 exposed-modules:
17 exposed-modules:
18 Frontend
18 Frontend
19− ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O
19+ ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
20
20
21executable frontend
21executable frontend
22 main-is: main.hs
22 main-is: main.hs
@@ -28,7 +28,7 @@ executable frontend
28 , reflex-dom
28 , reflex-dom
29 , obelisk-generated-static
29 , obelisk-generated-static
30 , frontend
30 , frontend
31− ghc-options: -threaded -O -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates
31+ ghc-options: -threaded -O -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -fno-show-valid-hole-fits
32 if impl(ghcjs)
32 if impl(ghcjs)
33 ghc-options: -dedupe
33 ghc-options: -dedupe
34 cpp-options: -DGHCJS_BROWSER
34 cpp-options: -DGHCJS_BROWSER