diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e0cd4518805a746f81b26e83c87c82426157fab2
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+build-android:
+  stage: build
+  tags:
+    - nix
+    - linux
+  script:
+    - cd obelisk
+    - nix-build -A android.frontend -o result-android
+  artifacts:
+    paths:
+      - obelisk/result-android/android-app-debug.apk
diff --git a/obelisk/.gitignore b/obelisk/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fa491075ade27a5b99b0b9e9c6a8845f39393647
--- /dev/null
+++ b/obelisk/.gitignore
@@ -0,0 +1,16 @@
+.attr-cache
+.cabal-sandbox
+*.hi
+*.o
+cabal.project.local
+cabal.sandbox.config
+ctags
+dist-newstyle/
+dist/
+ghcid-output.txt
+profile/
+result
+result-*
+tags
+TAGS
+static.out
diff --git a/obelisk/.obelisk/impl/default.nix b/obelisk/.obelisk/impl/default.nix
new file mode 100644
index 0000000000000000000000000000000000000000..2b4d4ab11148e306f1f2f6acd94168ec417507e7
--- /dev/null
+++ b/obelisk/.obelisk/impl/default.nix
@@ -0,0 +1,2 @@
+# DO NOT HAND-EDIT THIS FILE
+import (import ./thunk.nix)
\ No newline at end of file
diff --git a/obelisk/.obelisk/impl/github.json b/obelisk/.obelisk/impl/github.json
new file mode 100644
index 0000000000000000000000000000000000000000..e2d81dd7c9e110bf21f9d18d32a933d2c776735b
--- /dev/null
+++ b/obelisk/.obelisk/impl/github.json
@@ -0,0 +1,8 @@
+{
+  "owner": "obsidiansystems",
+  "repo": "obelisk",
+  "branch": "master",
+  "private": false,
+  "rev": "41f97410cfa2e22a4ed9e9344abcd58bbe0f3287",
+  "sha256": "04bpzji7y3nz573ib3g6icb56s5zbj4zxpakhqaql33v2v77hi9g"
+}
diff --git a/obelisk/.obelisk/impl/thunk.nix b/obelisk/.obelisk/impl/thunk.nix
new file mode 100644
index 0000000000000000000000000000000000000000..bbf2dc18fbbea1ae35515c775663a8d95dcc23c5
--- /dev/null
+++ b/obelisk/.obelisk/impl/thunk.nix
@@ -0,0 +1,9 @@
+# DO NOT HAND-EDIT THIS FILE
+let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
+  if !fetchSubmodules && !private then builtins.fetchTarball {
+    url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
+  } else (import <nixpkgs> {}).fetchFromGitHub {
+    inherit owner repo rev sha256 fetchSubmodules private;
+  };
+  json = builtins.fromJSON (builtins.readFile ./github.json);
+in fetch json
\ No newline at end of file
diff --git a/obelisk/backend/backend.cabal b/obelisk/backend/backend.cabal
new file mode 100644
index 0000000000000000000000000000000000000000..25f690346b18c8b6424530462b7df005963a9cc8
--- /dev/null
+++ b/obelisk/backend/backend.cabal
@@ -0,0 +1,29 @@
+name: backend
+version: 0.1
+cabal-version: >= 1.8
+build-type: Simple
+
+library
+  hs-source-dirs: src
+  if impl(ghcjs)
+    buildable: False
+  build-depends: base
+               , common
+               , frontend
+               , obelisk-backend
+               , obelisk-route
+  exposed-modules:
+    Backend
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
+
+executable backend
+  main-is: main.hs
+  hs-source-dirs: src-bin
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -threaded -fno-show-valid-hole-fits
+  if impl(ghcjs)
+    buildable: False
+  build-depends: base
+               , backend
+               , common
+               , frontend
+               , obelisk-backend
diff --git a/obelisk/backend/frontend.jsexe b/obelisk/backend/frontend.jsexe
new file mode 120000
index 0000000000000000000000000000000000000000..3a9cb6fefca28481c6526dbe2f1773817e110efa
--- /dev/null
+++ b/obelisk/backend/frontend.jsexe
@@ -0,0 +1 @@
+../frontend-js/bin/frontend.jsexe
\ No newline at end of file
diff --git a/obelisk/backend/frontendJs/frontend.jsexe b/obelisk/backend/frontendJs/frontend.jsexe
new file mode 120000
index 0000000000000000000000000000000000000000..af9b8f4536e79ba91d3cf5610cd61c82193c36ac
--- /dev/null
+++ b/obelisk/backend/frontendJs/frontend.jsexe
@@ -0,0 +1 @@
+../../frontend-js/bin/frontend.jsexe
\ No newline at end of file
diff --git a/obelisk/backend/src-bin/main.hs b/obelisk/backend/src-bin/main.hs
new file mode 100644
index 0000000000000000000000000000000000000000..00f1994a2c58c5696512adbf37b8b53aa235d570
--- /dev/null
+++ b/obelisk/backend/src-bin/main.hs
@@ -0,0 +1,6 @@
+import Backend
+import Frontend
+import Obelisk.Backend
+
+main :: IO ()
+main = runBackend backend frontend
diff --git a/obelisk/backend/src/Backend.hs b/obelisk/backend/src/Backend.hs
new file mode 100644
index 0000000000000000000000000000000000000000..5842ce9fd410acf1d55cce3421157e122e452256
--- /dev/null
+++ b/obelisk/backend/src/Backend.hs
@@ -0,0 +1,10 @@
+module Backend where
+
+import Common.Route
+import Obelisk.Backend
+
+backend :: Backend BackendRoute FrontendRoute
+backend = Backend
+  { _backend_run = \serve -> serve $ const $ return ()
+  , _backend_routeEncoder = fullRouteEncoder
+  }
diff --git a/obelisk/backend/static b/obelisk/backend/static
new file mode 120000
index 0000000000000000000000000000000000000000..4dab1644d836089c02492f343a6b50701ecd7e08
--- /dev/null
+++ b/obelisk/backend/static
@@ -0,0 +1 @@
+../static
\ No newline at end of file
diff --git a/obelisk/cabal.project b/obelisk/cabal.project
new file mode 100644
index 0000000000000000000000000000000000000000..fe0438b092ccb8bf97c085da63c8aad4a45d2a51
--- /dev/null
+++ b/obelisk/cabal.project
@@ -0,0 +1,3 @@
+optional-packages:
+  *
+write-ghc-environment-files: never
diff --git a/obelisk/common/common.cabal b/obelisk/common/common.cabal
new file mode 100644
index 0000000000000000000000000000000000000000..82b89916cc3d7ffefc81ef2e36d48a1d0efea0d3
--- /dev/null
+++ b/obelisk/common/common.cabal
@@ -0,0 +1,15 @@
+name: common
+version: 0.1
+cabal-version: >= 1.2
+build-type: Simple
+
+library
+  hs-source-dirs: src
+  build-depends: base
+               , obelisk-route
+               , mtl
+               , text
+  exposed-modules:
+    Common.Api
+    Common.Route
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
diff --git a/obelisk/common/src/Common/Api.hs b/obelisk/common/src/Common/Api.hs
new file mode 100644
index 0000000000000000000000000000000000000000..1ea3df09d4ecfd052f8142b1ef7efa4da4f1b883
--- /dev/null
+++ b/obelisk/common/src/Common/Api.hs
@@ -0,0 +1,4 @@
+module Common.Api where
+
+commonStuff :: String
+commonStuff = "Here is a string defined in Common.Api"
diff --git a/obelisk/common/src/Common/Route.hs b/obelisk/common/src/Common/Route.hs
new file mode 100644
index 0000000000000000000000000000000000000000..1dce9a734d13742970c78fbe0672fab8699613bd
--- /dev/null
+++ b/obelisk/common/src/Common/Route.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE EmptyCase #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
+module Common.Route where
+
+{- -- You will probably want these imports for composing Encoders.
+import Prelude hiding (id, (.))
+import Control.Category
+-}
+
+import Data.Text (Text)
+import Data.Functor.Identity
+
+import Obelisk.Route
+import Obelisk.Route.TH
+
+data BackendRoute :: * -> * where
+  -- | Used to handle unparseable routes.
+  BackendRoute_Missing :: BackendRoute ()
+  -- You can define any routes that will be handled specially by the backend here.
+  -- i.e. These do not serve the frontend, but do something different, such as serving static files.
+
+data FrontendRoute :: * -> * where
+  FrontendRoute_Main :: FrontendRoute ()
+  -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend.
+
+fullRouteEncoder
+  :: Encoder (Either Text) Identity (R (FullRoute BackendRoute FrontendRoute)) PageName
+fullRouteEncoder = mkFullRouteEncoder
+  (FullRoute_Backend BackendRoute_Missing :/ ())
+  (\case
+      BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty)
+  (\case
+      FrontendRoute_Main -> PathEnd $ unitEncoder mempty)
+
+concat <$> mapM deriveRouteComponent
+  [ ''BackendRoute
+  , ''FrontendRoute
+  ]
diff --git a/obelisk/config/common/example b/obelisk/config/common/example
new file mode 100644
index 0000000000000000000000000000000000000000..e368f70d7fc7e42f8e5971f99f569cfd98650669
--- /dev/null
+++ b/obelisk/config/common/example
@@ -0,0 +1 @@
+This string comes from config/common/example
\ No newline at end of file
diff --git a/obelisk/config/common/route b/obelisk/config/common/route
new file mode 100644
index 0000000000000000000000000000000000000000..99298a3da0b5cf8684f3169b08315af328a13012
--- /dev/null
+++ b/obelisk/config/common/route
@@ -0,0 +1 @@
+http://localhost:8000
\ No newline at end of file
diff --git a/obelisk/config/readme.md b/obelisk/config/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..7ca5a54b35175a22b1fa4ae4f62bed167da081e5
--- /dev/null
+++ b/obelisk/config/readme.md
@@ -0,0 +1,9 @@
+### Config
+
+Obelisk projects should contain a config folder with the following subfolders: common, frontend, and backend.
+
+Things that should never be transmitted to the frontend belong in backend/ (e.g., email credentials)
+
+Frontend-only configuration belongs in frontend/.
+
+Shared configuration files (e.g., the route config) belong in common/
diff --git a/obelisk/default.nix b/obelisk/default.nix
new file mode 100644
index 0000000000000000000000000000000000000000..3a4a76953486198a84c313f5cde1a41bf9b62a60
--- /dev/null
+++ b/obelisk/default.nix
@@ -0,0 +1,25 @@
+{ system ? builtins.currentSystem
+, obelisk ? import ./.obelisk/impl {
+    inherit system;
+    iosSdkVersion = "13.2";
+
+    # You must accept the Android Software Development Kit License Agreement at
+    # https://developer.android.com/studio/terms in order to build Android apps.
+    # Uncomment and set this to `true` to indicate your acceptance:
+    config.android_sdk.accept_license = true;
+
+    # In order to use Let's Encrypt for HTTPS deployments you must accept
+    # their terms of service at https://letsencrypt.org/repository/.
+    # Uncomment and set this to `true` to indicate your acceptance:
+    # terms.security.acme.acceptTerms = false;
+  }
+}:
+with obelisk;
+project ./. ({ ... }: {
+  # Not using "storage.private" (our newer domain name) since that makes the build
+  # fail: "[...] is not a valid Java package name as 'private' is a Java keyword."
+  android.applicationId = "io.privatestorage.privatestoragemobile";
+  android.displayName = "Private Storage Mobile";
+  ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal";
+  ios.bundleName = "Obelisk Minimal Example";
+})
diff --git a/obelisk/frontend/frontend.cabal b/obelisk/frontend/frontend.cabal
new file mode 100644
index 0000000000000000000000000000000000000000..ba6e5e07040707c2f93f7cc96c0a3a881a1ef20a
--- /dev/null
+++ b/obelisk/frontend/frontend.cabal
@@ -0,0 +1,36 @@
+name: frontend
+version: 0.1
+cabal-version: >= 1.8
+build-type: Simple
+
+library
+  hs-source-dirs: src
+  build-depends: base
+               , common
+               , obelisk-frontend
+               , obelisk-route
+               , jsaddle
+               , reflex-dom-core
+               , obelisk-executable-config-lookup
+               , obelisk-generated-static
+               , text
+  exposed-modules:
+    Frontend
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits
+
+executable frontend
+  main-is: main.hs
+  hs-source-dirs: src-bin
+  build-depends: base
+               , common
+               , obelisk-frontend
+               , obelisk-route
+               , reflex-dom
+               , obelisk-generated-static
+               , frontend
+  ghc-options: -threaded -O -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -fno-show-valid-hole-fits
+  if impl(ghcjs)
+    ghc-options: -dedupe
+    cpp-options: -DGHCJS_BROWSER
+  if os(darwin)
+    ghc-options: -dynamic
diff --git a/obelisk/frontend/src-bin/main.hs b/obelisk/frontend/src-bin/main.hs
new file mode 100644
index 0000000000000000000000000000000000000000..9408dc315a894bc7657b2e3a6f7ea7f0d331e2b8
--- /dev/null
+++ b/obelisk/frontend/src-bin/main.hs
@@ -0,0 +1,10 @@
+import Frontend
+import Common.Route
+import Obelisk.Frontend
+import Obelisk.Route.Frontend
+import Reflex.Dom
+
+main :: IO ()
+main = do
+  let Right validFullEncoder = checkEncoder fullRouteEncoder
+  run $ runFrontend validFullEncoder frontend
diff --git a/obelisk/frontend/src/Frontend.hs b/obelisk/frontend/src/Frontend.hs
new file mode 100644
index 0000000000000000000000000000000000000000..b5453787ad5794d77047cc4ec7bb0e8963e1bd58
--- /dev/null
+++ b/obelisk/frontend/src/Frontend.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Frontend where
+
+import Control.Monad
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import Language.Javascript.JSaddle (eval, liftJSM)
+
+import Obelisk.Frontend
+import Obelisk.Configs
+import Obelisk.Route
+import Obelisk.Generated.Static
+
+import Reflex.Dom.Core
+
+import Common.Api
+import Common.Route
+
+
+-- This runs in a monad that can be run on the client or the server.
+-- To run code in a pure client or pure server context, use one of the
+-- `prerender` functions.
+frontend :: Frontend (R FrontendRoute)
+frontend = Frontend
+  { _frontend_head = do
+      el "title" $ text "Obelisk Minimal Example"
+      elAttr "link" ("href" =: $(static "main.css") <> "type" =: "text/css" <> "rel" =: "stylesheet") blank
+  , _frontend_body = do
+      el "h1" $ text "Welcome to Obelisk!"
+      el "p" $ text $ T.pack commonStuff
+      
+      -- `prerender` and `prerender_` let you choose a widget to run on the server
+      -- during prerendering and a different widget to run on the client with
+      -- JavaScript. The following will generate a `blank` widget on the server and
+      -- print "Hello, World!" on the client.
+      prerender_ blank $ liftJSM $ void $ eval ("console.log('Hello, World!')" :: T.Text)
+
+      elAttr "img" ("src" =: $(static "obelisk.jpg")) blank
+      el "div" $ do
+        exampleConfig <- getConfig "common/example"
+        case exampleConfig of
+          Nothing -> text "No config file found in config/common/example"
+          Just s -> text $ T.decodeUtf8 s
+      return ()
+  }
diff --git a/obelisk/static/main.css b/obelisk/static/main.css
new file mode 100644
index 0000000000000000000000000000000000000000..57c2d532160ad790e1b05f47342c36297a6ddafb
--- /dev/null
+++ b/obelisk/static/main.css
@@ -0,0 +1,3 @@
+p {
+  color: red;  
+}
\ No newline at end of file
diff --git a/obelisk/static/obelisk.jpg b/obelisk/static/obelisk.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..68c8682d0809b5265fef807bdb8d46c9829e55ed
Binary files /dev/null and b/obelisk/static/obelisk.jpg differ