diff --git a/Makefile b/Makefile
deleted file mode 100644
--- a/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-.PHONY: test
-
-prog = ghc
-path = /usr/bin/ghc-7.6.3
-
-all: deps build install
-
-tags:
-	hasktags --etags --output='TAGS' *
-
-build:
-	cabal configure --with-$(prog)=$(path) && cabal build --with-$(prog)=$(path)
-
-install:
-	cabal install -w $(path)
-
-deps:
-	cabal install --only-dependencies -w $(path)
-
-test: build
-	cabal test
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# Welcome!
+![Hackage: Pub](https://img.shields.io/hackage/v/pub.svg?style=flat)
+![Build Status: Pub](https://travis-ci.org/ixmatus/pub.svg?branch=master)
+
+The `pub` utility publishes input from stdin to a redis channel and the `sub`
+utility consumes from a redis channel, writing to stdout.
+
+## Quickstart
+Publishing to Redis from `stdin` is easy with the `pub` utility:
+```shell
+$ pub --help
+Pipe stdin to a redis pub/sub channel
+
+Usage: pub --channel TEXT [--host STRING] [--port INTEGER] [--db INTEGER]
+           [--version]
+
+Available options:
+  -h,--help                Show this help text
+  --channel TEXT           Redis channel to publish to
+  --host STRING            Redis host (default: localhost)
+  --port INTEGER           Redis post (default: 6379)
+  --db INTEGER             Redis db (default: 0)
+
+$ journalctl -f nginx.service | pub --channel "nginx-loglines"
+```
+
+and subscribing to Redis channels and piping to `stdout` is also easy with the
+`sub` utility:
+
+```shell
+$ sub --help
+Subscribe to redis channels and pipe to stdout
+
+Usage: sub [--channel TEXT]... [--host STRING] [--port INTEGER] [--db INTEGER]
+           [--version]
+
+Available options:
+  -h,--help                Show this help text
+  --channel TEXT...        Redis channel(s) to subscribe to
+  --host STRING            Redis host (default: localhost)
+  --port INTEGER           Redis post (default: 6379)
+  --db INTEGER             Redis db (default: 0)
+
+$ sub --channel "nginx-loglines" --channel "haproxy-loglines" # This subscribes to both redis channels
+```
+
+## Building
+You can build multiple ways:
+
+- `stack build`
+- `cabal build`
+- `nix-build --not-out-link -A pub release.nix`
+- `nix-shell -A pub.env release.nix`
diff --git a/README.org b/README.org
deleted file mode 100644
--- a/README.org
+++ /dev/null
@@ -1,21 +0,0 @@
-* Welcome!
-  [[https://hackage.haskell.org/package/pub][https://img.shields.io/hackage/v/pub.svg?style=flat]]
-  [[https://travis-ci.org/ixmatus/pub][https://travis-ci.org/ixmatus/pub.svg?branch=master]]
-
-  =Pub= builds two utilities for piping to and from a redis pub/sub
-  channel and stdout/stdin.
-
-** Install
-   =cabal install pub=
-
-** Examples
-
-*** Using pub
-    #+BEGIN_SRC
-    tail -F /some/log/file | grep "logfileProperty" | pub "logline"
-    #+END_SRC
-
-*** Using sub
-    #+BEGIN_SRC
-    sub "logline" | grep "somethingspecific"
-    #+END_SRC
diff --git a/pub.cabal b/pub.cabal
--- a/pub.cabal
+++ b/pub.cabal
@@ -1,5 +1,5 @@
 Name:                   pub
-Version:                2.0.2
+Version:                3.0.0
 Author:                 Parnell Springmeyer <parnell@digitalmentat.com>
 Maintainer:             Parnell Springmeyer <parnell@digitalmentat.com>
 License:                BSD3
@@ -29,83 +29,34 @@
 Build-Type:             Simple
 
 Data-Files:
-  Makefile
   LICENSE
-  README.org
+  README.md
 
 Executable pub
   Default-Language:     Haskell2010
   HS-Source-Dirs:       pub
-  GHC-Options:          -threaded -Wall -fwarn-tabs -funbox-strict-fields
-                        -fno-warn-orphans -fno-warn-unused-do-bind -rtsopts -with-rtsopts=-N
+  GHC-Options:          -threaded -Wall -rtsopts -with-rtsopts=-N
   Main-Is:              Main.hs
-  Other-Modules:        Pub.Internal
-
   Build-Depends:
-    base                      >= 4        && < 5,
-    network                   >= 2.4.2.2  && < 2.5,
-    bytestring                >= 0.10.0.2 && < 0.10.1,
-    cmdargs                   >= 0.10.7   && < 0.11,
-    hslogger                  >= 1.2.3    && < 1.3,
-    ConfigFile                >= 1.1.1    && < 1.2,
-    text                      >= 1.1.0.1  && < 1.2,
-    safe                      >= 0.3      && < 0.4,
-    containers                >= 0.5.0.0  && < 0.6,
-    hedis                     >= 0.6      && < 0.7,
-    pipes                     >= 4.1.1    && < 4.2,
-    pipes-bytestring          >= 2        && < 3,
-    mtl                       >= 2.1      && < 2.3,
-    system-filepath           >= 0.4.12   && < 0.5,
-    groom                     >= 0.1.2    && < 0.2,
-    time                      >= 1.4      && < 1.5,
-    transformers              >= 0.4      && < 0.5
+                base             >= 4.9      && < 5
+              , bytestring       >= 0.10.8.1 && < 0.11
+              , hedis            >= 0.9.5    && < 1.0
+              , optparse-generic >= 1.1.5    && < 2.0
+              , pipes            >= 4.1.9    && < 5.0
+              , pipes-bytestring >= 2.1.4    && < 3.0
+              , text             >= 1.2.2.1  && < 2.0
 
 Executable sub
   Default-Language:     Haskell2010
   HS-Source-Dirs:       sub
-  GHC-Options:          -threaded -Wall -fwarn-tabs -funbox-strict-fields
-                        -fno-warn-orphans -fno-warn-unused-do-bind -rtsopts -with-rtsopts=-N
+  GHC-Options:          -threaded -Wall -rtsopts -with-rtsopts=-N
   Main-Is:              Main.hs
-  Other-Modules:        Sub.Internal
-
   Build-Depends:
-    base                      >= 4        && < 5,
-    network                   >= 2.4.2.2  && < 2.5,
-    bytestring                >= 0.10.0.2 && < 0.10.1,
-    cmdargs                   >= 0.10.7   && < 0.11,
-    hslogger                  >= 1.2.3    && < 1.3,
-    ConfigFile                >= 1.1.1    && < 1.2,
-    text                      >= 1.1.0.1  && < 1.2,
-    safe                      >= 0.3      && < 0.4,
-    containers                >= 0.5.0.0  && < 0.6,
-    hedis                     >= 0.6      && < 0.7,
-    mtl                       >= 2.1      && < 2.3,
-    system-filepath           >= 0.4.12   && < 0.5,
-    groom                     >= 0.1.2    && < 0.2,
-    time                      >= 1.4      && < 1.5,
-    transformers              >= 0.4      && < 0.5
-
--- Test-Suite tests
---   Type:                 exitcode-stdio-1.0
---   Default-Language:     Haskell2010
---   Hs-Source-Dirs:       test, src
---   Ghc-Options:          -Wall
---   Main-Is:              Test.hs
---   Other-Modules:        Pub.Internal
-
---   Build-Depends:
---     base                       >= 4.4      && < 5,
---     groom                      >= 0.1.2    && < 0.2,
---     cmdargs                    >= 0.10.7   && < 0.11,
---     hslogger                   >= 1.2.3    && < 1.3,
---     system-filepath            >= 0.4.12   && < 0.5,
---     bytestring                 >= 0.9.1    && < 0.11,
---     QuickCheck                 >= 2.6      && < 2.8,
---     HUnit                      >= 1.2.5.2  && < 1.3,
---     tasty                      >= 0.8      && < 0.9,
---     tasty-golden               >= 2.2.1    && < 2.3,
---     tasty-hunit                >= 0.8.0.1  && < 0.9,
---     tasty-quickcheck           >= 0.8      && < 0.9
+                base             >= 4.9 && < 5
+              , bytestring       >= 0.10.8.1 && < 0.11
+              , hedis            >= 0.9.5    && < 1.0
+              , optparse-generic >= 1.1.5    && < 2.0
+              , text             >= 1.2.2.1  && < 2.0
 
 Source-Repository head
   Type:                 git
diff --git a/pub/Main.hs b/pub/Main.hs
--- a/pub/Main.hs
+++ b/pub/Main.hs
@@ -5,38 +5,60 @@
 -- License     :  AllRightsReserved
 -- Maintainer  :  Parnell Springmeyer <parnell@digitalmentat.com>
 -- Stability   :  stable
---
--- This is the entry point for the `pub` executable. Main sets up
--- the cli options configuration and parses configuration file options.
 ----------------------------------------------------------------------------
 
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE FlexibleInstances  #-}
+{-# LANGUAGE LambdaCase         #-}
+{-# LANGUAGE OverloadedStrings  #-}
+{-# LANGUAGE RecordWildCards    #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeOperators      #-}
 
 module Main where
 
-import           Data.Version           (showVersion)
-import           Paths_pub              (version)
-import           System.Console.CmdArgs
+import           Control.Monad
+import           Data.ByteString  (ByteString)
+import           Data.Maybe
+import           Data.Version     (showVersion)
+import qualified Database.Redis   as Redis
+import           Options.Generic
+import qualified Paths_pub        as Pub
+import           Pipes
+import qualified Pipes.ByteString
+import qualified System.IO
 
-import           Pub
-import           Pub.Internal
+data Options w = Options
+  { channel :: w ::: ByteString    <?> "Redis channel to publish to"
+  , host    :: w ::: Maybe String  <?> "Redis host (default: localhost)"
+  , port    :: w ::: Maybe Integer <?> "Redis post (default: 6379)"
+  , db      :: w ::: Maybe Integer <?> "Redis db   (default: 0)"
+  , version :: Bool
+  } deriving (Generic)
 
--- | Command line argument configuration.
---
--- Help messages, switches, options, and the human-readable version
--- number is configured here.
-programArgs :: PArgs
-programArgs = PArgs
-    { chan  = def &= argPos 0 &= typ "CHANNEL"
-    , host  = def &= name "h" &= typ "STRING" &= help "Redis host (default `localhost`)"
-    , port  = def &= name "p" &= help "Redis port (default `6379`)"
-    , db    = def &= name "d" &= help "Redis database (default `0`)"
-    } &=
-    verbosity &=
-    help    "Pipe stdin to a pub/sub channel." &=
-    summary ("pub v" ++ showVersion version) &=
-    noAtExpand &=
-    details ["Given an input on stdin, pipe to a redis pub/sub channel."]
+instance ParseRecord (Options Wrapped)
+deriving instance Show (Options Unwrapped)
 
 main :: IO ()
-main = cmdArgs programArgs >>= handleOpts >>= pipePublish
+main = do
+  Options{..} <- unwrapRecord "Pipe stdin to a redis pub/sub channel"
+  let stderr = System.IO.stderr
+  when version $
+    System.IO.hPutStrLn stderr (showVersion Pub.version)
+  conn <-
+    Redis.connect
+      Redis.defaultConnectInfo
+       { Redis.connectHost     = (fromMaybe "localhost" host)
+       , Redis.connectPort     = Redis.PortNumber (fromInteger $ fromMaybe 6379 port)
+       , Redis.connectDatabase = fromMaybe 0 db
+       }
+
+  let publish =
+        (\value ->
+          -- TODO: should we use runRedis here!?!?
+          lift (Redis.runRedis conn $ Redis.publish channel value) >>= \case
+            Left reply -> lift (System.IO.hPutStrLn stderr $ show reply)
+            Right _    -> pure ())
+
+  runEffect (for Pipes.ByteString.stdin publish)
diff --git a/pub/Pub/Internal.hs b/pub/Pub/Internal.hs
deleted file mode 100644
--- a/pub/Pub/Internal.hs
+++ /dev/null
@@ -1,66 +0,0 @@
------------------------------------------------------------------------------
--- |
--- Module      :  Pub.Internal
--- Copyright   :  (C) 2014 Parnell Springmeyer
--- License     :  AllRightsReserved
--- Maintainer  :  Parnell Springmeyer <parnell@digitalmentat.com>
--- Stability   :  stable
---
--- Internal functions and types for the `Main` program. Mostly
--- responsible for defining the `cmdargs` data type, parsing
--- configuration, and setting the log level.
-----------------------------------------------------------------------------
-
-{-# LANGUAGE DataKinds          #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE OverloadedStrings  #-}
-{-# LANGUAGE StandaloneDeriving #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-module Pub.Internal where
-
-import           Control.Applicative
-import qualified Data.ByteString.Char8   as C8
-import           Database.Redis          as R
-import           Network.Socket.Internal as NSI (PortNumber)
-import           System.Console.CmdArgs
-import           System.Log.Logger
-import           Text.Groom              (groom)
-
--- | Data type for program CLI options.
-data PArgs = PArgs
-    { chan :: String
-    , host :: Maybe String
-    , port :: Maybe Integer
-    , db   :: Maybe Integer
-    } deriving (Data, Typeable, Show, Eq)
-
--- | Data type for settings once merged from CLI.
-data Settings = Settings
-    { loglevel  :: !Priority
-    , channel   :: C8.ByteString
-    , redisHost :: Maybe HostName
-    , redisPort :: Maybe PortID
-    , redisDB   :: Maybe Integer
-    } deriving (Show, Eq)
-
--- | Given options, set the logging level, and create the settings
--- record.
-handleOpts :: PArgs -> IO Settings
-handleOpts cliopts = do
-    whenNormal $ updateGlobalLogger "Console" (setLevel ERROR)
-    whenLoud   $ updateGlobalLogger "Console" (setLevel DEBUG)
-
-    let c    = C8.pack $ chan cliopts
-        p    = (fromInteger <$> (port cliopts)) :: Maybe NSI.PortNumber
-        conf = Settings { loglevel  = INFO
-                        , channel   = c
-                        , redisHost = host cliopts
-                        , redisPort = R.PortNumber <$> p
-                        , redisDB   = db cliopts
-                        }
-
-    debugM "Console" "Configuration parsed"
-    debugM "Console" $ groom conf
-
-    return conf
diff --git a/sub/Main.hs b/sub/Main.hs
--- a/sub/Main.hs
+++ b/sub/Main.hs
@@ -5,38 +5,59 @@
 -- License     :  AllRightsReserved
 -- Maintainer  :  Parnell Springmeyer <parnell@digitalmentat.com>
 -- Stability   :  stable
---
--- This is the entry point for the `sub` executable. Main sets up
--- the cli options configuration and parses configuration file options.
 ----------------------------------------------------------------------------
 
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE FlexibleInstances  #-}
+{-# LANGUAGE LambdaCase         #-}
+{-# LANGUAGE OverloadedStrings  #-}
+{-# LANGUAGE RecordWildCards    #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeOperators      #-}
 
 module Main where
 
-import           Data.Version           (showVersion)
-import           Paths_pub              (version)
-import           System.Console.CmdArgs
+import           Control.Monad
+import           Data.ByteString       (ByteString)
+import qualified Data.ByteString.Char8 as C8
+import           Data.Maybe
+import           Data.Version          (showVersion)
+import qualified Database.Redis        as Redis
+import           Options.Generic
+import qualified Paths_pub             as Pub
+import qualified System.IO
 
-import           Sub
-import           Sub.Internal
+data Options w = Options
+  { channel :: w ::: [ByteString]  <?> "Redis channel(s) to subscribe to"
+  , host    :: w ::: Maybe String  <?> "Redis host (default: localhost)"
+  , port    :: w ::: Maybe Integer <?> "Redis post (default: 6379)"
+  , db      :: w ::: Maybe Integer <?> "Redis db   (default: 0)"
+  , version :: Bool
+  } deriving (Generic)
 
--- | Command line argument configuration.
---
--- Help messages, switches, options, and the human-readable version
--- number is configured here.
-programArgs :: PArgs
-programArgs = PArgs
-    { chan  = def &= argPos 0 &= typ "CHANNEL"
-    , host  = def &= name "h" &= typ "STRING" &= help "Redis host (default `localhost`)"
-    , port  = def &= name "p" &= help "Redis port (default `6379`)"
-    , db    = def &= name "d" &= help "Redis database (default `0`)"
-    } &=
-    verbosity &=
-    help    "Pipe from a pub/sub channel to stdout." &=
-    summary ("sub v" ++ showVersion version) &=
-    noAtExpand &=
-    details ["Given a redis pub/sub channel, pipe to stdout."]
+instance ParseRecord (Options Wrapped)
+deriving instance Show (Options Unwrapped)
 
 main :: IO ()
-main = cmdArgs programArgs >>= handleOpts >>= pipePublish
+main = do
+  Options{..} <- unwrapRecord "Subscribe to redis channels and pipe to stdout"
+  let stderr = System.IO.stderr
+  let stdout = System.IO.stdout
+  when version $
+    System.IO.hPutStrLn stderr (showVersion Pub.version)
+  conn <-
+    Redis.connect
+      Redis.defaultConnectInfo
+       { Redis.connectHost     = (fromMaybe "localhost" host)
+       , Redis.connectPort     = Redis.PortNumber (fromInteger $ fromMaybe 6379 port)
+       , Redis.connectDatabase = fromMaybe 0 db
+       }
+
+  Redis.runRedis conn $
+    Redis.pubSub
+     (Redis.subscribe channel)
+     (\msg -> do
+         C8.putStrLn $ Redis.msgMessage msg
+         System.IO.hFlush stdout
+         pure mempty)
diff --git a/sub/Sub/Internal.hs b/sub/Sub/Internal.hs
deleted file mode 100644
--- a/sub/Sub/Internal.hs
+++ /dev/null
@@ -1,66 +0,0 @@
------------------------------------------------------------------------------
--- |
--- Module      :  Sub.Internal
--- Copyright   :  (C) 2014 Parnell Springmeyer
--- License     :  AllRightsReserved
--- Maintainer  :  Parnell Springmeyer <parnell@digitalmentat.com>
--- Stability   :  stable
---
--- Internal functions and types for the `Main` program. Mostly
--- responsible for defining the `cmdargs` data type, parsing
--- configuration, and setting the log level.
-----------------------------------------------------------------------------
-
-{-# LANGUAGE DataKinds          #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE OverloadedStrings  #-}
-{-# LANGUAGE StandaloneDeriving #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-module Sub.Internal where
-
-import           Control.Applicative
-import qualified Data.ByteString.Char8   as C8
-import           Database.Redis          as R
-import           Network.Socket.Internal as NSI (PortNumber)
-import           System.Console.CmdArgs
-import           System.Log.Logger
-import           Text.Groom              (groom)
-
--- | Data type for program CLI options.
-data PArgs = PArgs
-    { chan :: String
-    , host :: Maybe String
-    , port :: Maybe Integer
-    , db   :: Maybe Integer
-    } deriving (Data, Typeable, Show, Eq)
-
--- | Data type for settings once merged from CLI.
-data Settings = Settings
-    { loglevel  :: !Priority
-    , channel   :: C8.ByteString
-    , redisHost :: Maybe HostName
-    , redisPort :: Maybe PortID
-    , redisDB   :: Maybe Integer
-    } deriving (Show, Eq)
-
--- | Given options, set the logging level, and create the settings
--- record.
-handleOpts :: PArgs -> IO Settings
-handleOpts cliopts = do
-    whenNormal $ updateGlobalLogger "Console" (setLevel ERROR)
-    whenLoud   $ updateGlobalLogger "Console" (setLevel DEBUG)
-
-    let c    = C8.pack $ chan cliopts
-        p    = (fromInteger <$> (port cliopts)) :: Maybe NSI.PortNumber
-        conf = Settings { loglevel  = INFO
-                        , channel   = c
-                        , redisHost = host cliopts
-                        , redisPort = R.PortNumber <$> p
-                        , redisDB   = db cliopts
-                        }
-
-    debugM "Console" "Configuration parsed"
-    debugM "Console" $ groom conf
-
-    return conf
