diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Justin Leitgeb
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/openssh-github-keys.cabal b/openssh-github-keys.cabal
new file mode 100644
--- /dev/null
+++ b/openssh-github-keys.cabal
@@ -0,0 +1,70 @@
+name:                openssh-github-keys
+version:             0.1.0.0
+synopsis:            Fetch OpenSSH keys from a Github team
+description:
+  .
+  This package fetches the OpenSSH public keys for all users from a
+  Github team. It is intended to be executed from the AuthorizedKeys
+  command in the sshd_config file, which then allows users to log in
+  using keys that they have in their Github accounts.
+  .
+homepage:            https://github.com/stackbuilders/openssh-github-keys
+license:             MIT
+license-file:        LICENSE
+author:              Stack Builders
+maintainer:          hackage@stackbuilders.com
+copyright:           2015 Stack Builders Inc.
+category:            System
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+Homepage:            https://github.com/stackbuilders/openssh-github-keys
+Bug-reports:         https://github.com/stackbuilders/openssh-github-keys/issues
+
+executable openssh-github-keys
+  main-is:             Main.hs
+  -- other-modules:
+  -- other-extensions:
+  build-depends:       base >=4.5 && <4.8
+                       , octohat >=0.1.2
+                       , dotenv
+                       , text
+                       , optparse-applicative
+                       , directory
+                       , filepath
+                       , unix
+
+  hs-source-dirs:      src
+  ghc-options:         -Wall
+
+  default-language:    Haskell2010
+
+library
+  exposed-modules:    System.OpensshGithubKeys
+
+  build-depends:         base >=4.5 && <4.8
+                       , octohat >=0.1.2
+                       , text
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+
+test-suite openssh-github-keys-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: spec, src
+  main-is: Spec.hs
+  build-depends:       base >=4.5 && <4.8
+                       , octohat >=0.1.2
+                       , text
+
+                       , hspec
+
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+
+source-repository head
+  type: git
+  location: https://github.com/stackbuilders/openssh-github-keys
diff --git a/spec/Spec.hs b/spec/Spec.hs
new file mode 100644
--- /dev/null
+++ b/spec/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Options.Applicative
+
+import qualified Data.Text as T
+
+import Network.Octohat (keysOfTeamInOrganization)
+import Network.Octohat.Types (runGitHub, OrganizationName(..),
+                              TeamName(..))
+
+import System.OpensshGithubKeys (formatKey)
+
+import System.Timeout (timeout)
+
+import qualified Configuration.Dotenv as Dotenv
+
+data Options = Options
+  { localUser    :: String
+  , organization :: String
+  , team         :: String
+  , users        :: [String]
+  , dotfile      :: Maybe FilePath
+
+  } deriving (Show)
+
+
+fetchKeys :: Options -> IO ()
+fetchKeys opts = do
+  if any (\u -> localUser opts == u) (users opts)
+    then do
+      res <- runGitHub $ keysOfTeamInOrganization
+             (OrganizationName $ T.pack $ organization opts)
+             (TeamName $ T.pack $ team opts)
+
+      case res of
+        Right membersWithKeys ->
+          putStrLn $ unlines $ concatMap formatKey membersWithKeys
+
+        Left e -> error $ "Error retrieving keys for organization '" ++ show e
+
+    else return ()
+
+config :: Parser Options
+config = Options
+     <$> argument str (metavar "AUTHENTICATE"
+                       <> help "Local user trying to authenticate currently")
+
+     <*> strOption (
+             long "organization"
+             <> short 'o'
+             <> metavar "ORGANIZATION"
+             <> help "GitHub organization from which to select teams")
+
+     <*> strOption (
+                  long "team"
+                  <> short 't'
+                  <> metavar "TEAM"
+                  <> help "GitHub team from which to select members' keys" )
+
+     <*> some (strOption (
+                  long "user"
+                  <> short 'u'
+                  <> metavar "USER"
+                  <> help "A local user that we should try to authenticate using Github" ))
+
+     <*> strOptional (
+                  long "dotfile"
+                  <> short 'f'
+                  <> metavar "DOTFILE"
+                  <> help "File in 'dotfile' format specifying GITHUB_TOKEN" )
+
+strOptional :: Mod OptionFields String -> Parser (Maybe String)
+strOptional flags = Just <$> strOption flags <|> pure Nothing
+
+main :: IO ()
+main = do
+  options <- execParser opts
+  readDotenvFile options
+
+  res <- timeout allowedTime (fetchKeys options)
+  case res of
+    Nothing -> error $ "Allowed time of " ++ show allowedTime ++
+               " microseconds exceeded while fetching keys from GitHub."
+    Just  _ -> return ()
+
+  where
+    allowedTime = 5 * 1000000 -- Allowed time in microseconds for fetching keys
+    opts = info (helper <*> config)
+      ( fullDesc
+
+     <> progDesc (unlines
+        [ "Fetches ssh public keys from TEAMS under ORGANIZATION on GitHub."
+        , "The output format is suitable for AuthorizedKeysCommand in"
+        , "sshd_config. Requires a github token, which can be specified"
+        , "in an environment variable GITHUB_TOKEN or in a dotenv file which"
+        , "can be specified with the -f option."])
+
+     <> header "openssh-github-keys - fetches team member keys from GitHub"
+      )
+
+-- | Conditionally reads the ~/.github_token file if it exists.
+readDotenvFile :: Options -> IO ()
+readDotenvFile opts =
+  case dotfile opts of
+    Just f -> Dotenv.loadFile False f
+    Nothing -> return ()
diff --git a/src/System/OpensshGithubKeys.hs b/src/System/OpensshGithubKeys.hs
new file mode 100644
--- /dev/null
+++ b/src/System/OpensshGithubKeys.hs
@@ -0,0 +1,16 @@
+module System.OpensshGithubKeys (formatKey) where
+
+import Data.List (intercalate)
+
+import qualified Data.Text as T
+
+import Network.Octohat.Types (Member(..), MemberWithKey(..), PublicKey(..))
+
+-- | Returns a list of keys for the user, with the username appended for easier
+-- identification.
+formatKey :: MemberWithKey -> [String]
+formatKey mkeys =
+  map (\k -> intercalate " " [(T.unpack $ publicKey k), ghUsername])
+  (memberKey mkeys)
+
+  where ghUsername = (T.unpack . memberLogin . member) mkeys
