diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+Copyright Dino Morelli 2012
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Dino Morelli nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,10 @@
+#! /usr/bin/env runhaskell
+
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+import Distribution.Simple
+
+
+main = defaultMain
diff --git a/resources/README b/resources/README
new file mode 100644
--- /dev/null
+++ b/resources/README
@@ -0,0 +1,61 @@
+WHAT IS SSHTUN?
+
+We all know the value of ssh for secure remote shell activities and other tools like scp and sftp. Another powerful thing ssh can do is construct secure tunnels between IP addresses and ports. It can be used like a vpn.
+
+As an example, say you have a computer on a network that's difficult to gain access to from the outside. But within that network you are able to ssh out to anywhere you like. With another system outside the network set up, ssh can maintain an encrypted tunnel between the two systems so that access can be gained back through the tunnel from the outside system to the 'hidden' one.
+
+This is done with a command similar to this:
+
+   ssh -p 22 -N -R 22:localhost:2022 OUTSIDEUSER@OUTSIDEHOST
+
+This command will not create an interactive shell, but will instead block while this tunnel is open.
+
+One complication with this is failure of the tunnel for some reason, leaving no access to the inside system to restart it. This is the purpose of sshtun.
+
+sshtun is a daemon that executes the ssh command and blocks on it. If the tunnel is closed, sshtun will attempt to reestablish it.
+
+Optionally, sshtun can be instructed to keep the tunnel open or closed via a file at an http URL, a switch file. The daemon will poll this file periodically via http, allowing you to control the tunnel from the outside and start it when you need it.
+
+
+CONFIGURING SSHTUN
+
+First, it's necessary to be able to passwordless ssh from a user (not root) on the inside system to a user on the outside system. You will need to use ssh-keygen and adjust outside's authorized_keys. Please see ssh(1) for more information on how to configure this.
+
+And bear in mind that this will allow unattended shell access, so plan accordingly. Not a bad idea to have an sshtun user on the outside system who has no real power on that system.
+
+If you can perform an ssh tunnel command as above yourself from the shell, than you have things ready to be able to configure sshtun.
+
+
+Next, you need to edit an sshtun.conf file and place it in /etc/ on the inside system. You can find a heavily-documented example of this file in the same directory that this README is in. This file contains all of the relevant information to execute the ssh tunnel command and also contains settings for polling intervals, the switch http location, etc.
+
+
+sshtun defaults to /var/log/sshtun.log for logging. If you're going to be using it a lot, this log file will get big. Some systems have logrotate installed to manage log files, rotate, compress, and delete old logs. In the same directory that this README is in, I have included logrotate.d/sshtun. You can place this file in /etc/logrotate.d/ to manage the log.
+
+
+Although not necessary, you may want to install this software as a system daemon in /etc/init.d/ or /etc/rc.d/, whatever is appropriate for your operating system. In this case, all that's necessary is to put a link to the sshtun binary in the directory where other daemons are. sshtun drops right into this environment and responds to the same set of start/stop/restart commands that other daemons do.
+
+
+USAGE
+
+Once you have sshtun configured, it must be run as root like this:
+
+   # sshtun start
+
+You should then be able to see status information in the log file (defaults to /var/log/sshtun.log) and use your tunnel.
+
+Other commands are "stop" and "restart"
+
+If you are using the switch file over http facility, there's a script called sshtun-switch.sh, in the same directory as this README, for turning the desired state on and off. The idea is you have a system with an http server running. The machine who wants to keep the tunnel open (or not) can make http requests to it to examine the expected state, up or down. The sshtun-switch.sh script is intended to be deployed to the system where the http server is running, and is used manually to switch the desired state.
+
+
+PROBLEMS AND FUTURE DEVELOPMENT
+
+Only one sshtun instance can be running on a system. There's no real reason for this other than that's how it works at this time. It could be changed if there's interest among the userbase for multiple, simultaneous, named tunnels.
+
+If you are using the http switch facility, and the switch file becomes unavailable when the inside system polls for it, this is taken to mean that the tunnel should be shut down immediately. Even though this may be extreme, it seemed like the safest default to use for the moment. Again, we can change this if anyone has special needs or better ideas.
+
+
+CONTACT
+
+Project page:  http://ui3.info/d/proj/sshtun.html
+Email developer: Dino Morelli  dino@ui3.info
diff --git a/resources/logrotate.d/sshtun b/resources/logrotate.d/sshtun
new file mode 100644
--- /dev/null
+++ b/resources/logrotate.d/sshtun
@@ -0,0 +1,8 @@
+/var/log/sshtun.log {
+   rotate 7
+   daily
+   compress
+   delaycompress
+   missingok
+   notifempty
+}
diff --git a/resources/sshtun-switch.sh b/resources/sshtun-switch.sh
new file mode 100644
--- /dev/null
+++ b/resources/sshtun-switch.sh
@@ -0,0 +1,39 @@
+#! /bin/bash
+
+
+# This path and file needs to describe the file that will be read by
+# sshtun on the remote system via http
+
+switchDir=/var/www
+switchFile=tflag
+
+
+switchFileDown="${switchFile}-down"
+switchFileUp="${switchFile}-up"
+
+function checkSetup {
+   # Handle first-time creation of the files
+   # Possible problem: permissions for the http server?
+
+   [ -f "$switchFileDown" ] || echo 0 > $switchFileDown
+   [ -f "$switchFileUp" ] || echo 1 > $switchFileUp
+}
+
+
+cd $switchDir
+
+case "$1" in
+   "up")
+      checkSetup
+      rm $switchFile
+      ln -s $switchFileUp $switchFile
+      ;;
+   "down")
+      checkSetup
+      rm $switchFile
+      ln -s $switchFileDown $switchFile
+      ;;
+   *)
+      echo "usage: $0 [up|down]"
+      ;;
+esac
diff --git a/resources/sshtun.conf b/resources/sshtun.conf
new file mode 100644
--- /dev/null
+++ b/resources/sshtun.conf
@@ -0,0 +1,57 @@
+# Configuration file for the sshtun daemon
+
+# Path to the log file
+# If this is changed, make sure it's updated in /etc/logrotate.d/sshtun!
+logFile=/var/log/sshtun.log
+
+# Logging priority. These are the values of System.Log.Priority from
+# the hslogger library:
+#   DEBUG      Debug messages
+#   INFO       More verbose, this is a reasonable default
+#   NOTICE     Only program start/stop is logged
+#   ERROR      Error conditions
+# Only the priority specified here and higher will be logged
+# DEBUG is lowest (more logging) and ERROR is highest (less logging)
+logPriority=INFO
+
+# Location of simple text file available via http. The contents of the 
+# file are expected to be either a 1 or a 0, corresponding to whether 
+# we want the tunnel to be up or not.
+# Setting this equal to nothing means the tunnel should not be switched
+# at all, stays up all the time.
+#   switchUrl=http://foo.com/tflag
+switchUrl=
+
+# Frequency that we will check the flag, in seconds
+switchPollInterval=300
+
+# When the root user starts this daemon and then drops privileges, use
+# the following user on this local system to keep the daemon running. This
+# is the user whose ssh keypair must be coordinated with the remote
+# system's user.
+localDaemonUser=LOCALUSER
+
+# Both sshPort and localPort won't be manipulated by users directly, 
+# see remotePort below
+
+# Port we are using to establish this tunnel, from here to that system
+sshPort=22
+
+# Port that will be used to tunnel back to this system
+localPort=22
+
+# Port to use the tunnel, like this:
+#   remoteHost$ ssh -p 2022 LOCALUSER@localhost
+remotePort=2022
+
+# User and host of remote system
+remoteUser=REMOTEUSER
+remoteHost=REMOTEHOST
+
+# Additional ssh parameters
+addlSshArgs=
+# A useful one is enabling X11 forwarding:
+#addlSshArgs=-X
+
+# Time in seconds to wait before attempting to reestablish the tunnel
+tunnelRetryDelay=300
diff --git a/src/Sshtun/Common.hs b/src/Sshtun/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Sshtun/Common.hs
@@ -0,0 +1,44 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+module Sshtun.Common
+   where
+
+import Control.Concurrent
+import Control.Concurrent.STM
+import System.Process
+
+
+data TunnelState
+   = Running ProcessHandle
+   | Stopped
+
+data DesiredState = Run | Stop
+
+type Shared = (TunnelState, DesiredState)
+
+
+sleep :: Int -> IO ()
+sleep = threadDelay . (*) 1000000
+
+
+run :: TVar Shared -> IO ()
+run shared = do
+   atomically $ do
+      (tst, _) <- readTVar shared
+      writeTVar shared (tst, Run)
+
+
+stop :: TVar Shared -> IO ()
+stop shared = do
+   tst <- atomically $ do
+      (tst, _) <- readTVar shared
+      writeTVar shared (Stopped, Stop)
+      return tst
+   stopPh tst
+
+
+stopPh :: TunnelState -> IO ()
+stopPh (Running ph) = terminateProcess ph
+stopPh Stopped      = return ()
diff --git a/src/Sshtun/Conf.hs b/src/Sshtun/Conf.hs
new file mode 100644
--- /dev/null
+++ b/src/Sshtun/Conf.hs
@@ -0,0 +1,185 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+{-# LANGUAGE FlexibleContexts #-} 
+
+{- |
+   Simple module for loading config files
+-}
+module Sshtun.Conf
+   ( Conf (..)
+   , parseConf
+   )
+   where
+
+import Control.Monad.Error
+import Data.Map hiding ( map )
+import Data.Maybe ( catMaybes )
+import Prelude hiding ( lookup )
+import System.Log
+import Text.Regex ( matchRegex, mkRegex )
+
+
+type ConfMap = Map String String
+
+data Conf = Conf
+   { logFile :: String
+   , logPriority :: Priority
+   , switchUrl :: Maybe String
+   , switchPollInterval :: Int
+   , localDaemonUser :: String
+   , sshPort :: Int
+   , localPort :: Int
+   , remotePort :: Int
+   , remoteUser :: String
+   , remoteHost :: String
+   , addlSshArgs :: String
+   , tunnelRetryDelay :: Int
+   }
+   deriving Show
+
+emptyConf :: Conf
+emptyConf = Conf
+   { logFile = ""
+   , logPriority = DEBUG
+   , switchUrl = Nothing
+   , switchPollInterval = 0
+   , localDaemonUser = ""
+   , sshPort = 0
+   , localPort = 0
+   , remotePort = 0
+   , remoteUser = ""
+   , remoteHost = ""
+   , addlSshArgs = ""
+   , tunnelRetryDelay = 0
+   }
+
+
+extractConfItem :: MonadError String m =>
+   ConfMap -> (String, String -> m b) -> m b
+extractConfItem cm (k, f) =
+   maybe (throwError $ "Missing config field: " ++ k)
+      f $ lookup k cm
+
+
+parseConf :: Monad m => String -> m (Either String Conf)
+parseConf entireConf = runErrorT $ do
+   mutatorActions <- mapM (extractConfItem cm) exts
+   foldM (\c f -> f c) emptyConf mutatorActions
+
+   where
+      cm = parseToMap entireConf
+
+      exts =
+         [ ( "logFile"
+           , (\s -> return (\c -> return $ c { logFile = s }))
+           )
+         , ( "logPriority"
+           , (\s -> return (\c -> do
+               p <- strToPriority s
+               return $ c { logPriority = p }
+               ))
+           )
+         , ( "switchUrl"
+           , (\s -> return (\c ->
+               return $ c { switchUrl = parseSwitchUrl s }))
+           )
+         , ( "switchPollInterval"
+           , (\s -> return (\c -> do
+               n <- readE ("switchPollInterval, unable to parse: " ++ s) s
+               return $ c { switchPollInterval = n }
+               ))
+           )
+         , ( "localDaemonUser"
+           , (\s -> return (\c -> return $ c { localDaemonUser = s }))
+           )
+         , ( "sshPort"
+           , (\s -> return (\c -> do
+               n <- readE ("sshPort, unable to parse: " ++ s) s
+               return $ c { sshPort = n }
+               ))
+           )
+         , ( "localPort"
+           , (\s -> return (\c -> do
+               n <- readE ("localPort, unable to parse: " ++ s) s
+               return $ c { localPort = n }
+               ))
+           )
+         , ( "remotePort"
+           , (\s -> return (\c -> do
+               n <- readE ("remotePort, unable to parse: " ++ s) s
+               return $ c { remotePort = n }
+               ))
+           )
+         , ( "remoteUser"
+           , (\s -> return (\c -> return $ c { remoteUser = s }))
+           )
+         , ( "remoteHost"
+           , (\s -> return (\c -> return $ c { remoteHost = s }))
+           )
+         , ( "addlSshArgs"
+           , (\s -> return (\c -> return $ c { addlSshArgs = s }))
+           )
+         , ( "tunnelRetryDelay"
+           , (\s -> return (\c -> do
+               n <- readE ("tunnelRetryDelay, unable to parse: " ++ s) s
+               return $ c { tunnelRetryDelay = n }
+               ))
+           )
+         ]
+
+
+parseSwitchUrl :: String -> Maybe String
+parseSwitchUrl "" = Nothing
+parseSwitchUrl s  = Just s
+
+
+readE :: (MonadError e m, Read a) => e -> String -> m a
+readE msg s = case reads s of
+   ((x, ""):[]) -> return x
+   _            -> throwError msg
+
+
+{- |
+   Parse config file data into a simple (Map String String).
+
+   For example, this:
+
+   >  --- file start ---
+   >  foo=one
+   >  # a comment
+   >
+   >  bar
+   >  baz-blorp=2
+   >  --- file end ---
+
+   becomes:
+
+   >  fromList [("foo","one"),("bar",""),("baz-blorp","2")]
+
+   Comments (prefixed with #) and blank lines in the config file 
+   are discarded.
+-}
+parseToMap :: String -> ConfMap
+parseToMap entireConf =
+   fromList $ map listToPair
+      $ catMaybes $ map (matchRegex re) $ lines entireConf
+
+   where
+      listToPair [k, v] = (k, v)
+      listToPair _      = undefined  -- Should never happen
+
+      re = mkRegex "^([^#][^=]*)=?(.*)"
+
+
+strToPriority :: MonadError String m => String -> m Priority
+strToPriority "DEBUG"     = return DEBUG
+strToPriority "INFO"      = return INFO
+strToPriority "NOTICE"    = return NOTICE
+strToPriority "WARNING"   = return WARNING
+strToPriority "ERROR"     = return ERROR
+strToPriority "CRITICAL"  = return CRITICAL
+strToPriority "ALERT"     = return ALERT
+strToPriority "EMERGENCY" = return EMERGENCY
+strToPriority x           = throwError $ "Invalid logPriority: " ++ x
diff --git a/src/Sshtun/Log.hs b/src/Sshtun/Log.hs
new file mode 100644
--- /dev/null
+++ b/src/Sshtun/Log.hs
@@ -0,0 +1,45 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+module Sshtun.Log
+   ( initLogging, logM, logTest
+   , Priority (..)  -- Re-exported from System.Log
+   )
+   where
+
+import System.Log.Formatter
+import System.Log.Handler ( setFormatter )
+import System.Log.Handler.Simple ( fileHandler )
+import qualified System.Log.Logger as L ( logM )
+import           System.Log.Logger hiding ( logM )
+
+
+{- Convenience wrapper for logging
+-}
+logM :: Priority -> String -> IO ()
+logM = L.logM rootLoggerName
+
+
+{- Set up logging
+-}
+initLogging :: FilePath -> Priority -> IO ()
+initLogging logFile logPriority = do
+   h <- fileHandler logFile logPriority >>= \lh -> return $
+      setFormatter lh (simpleLogFormatter "[$time : $prio] $msg")
+   updateGlobalLogger rootLoggerName $ setHandlers [h]
+   updateGlobalLogger rootLoggerName $ setLevel logPriority
+
+
+{- Test function to generate every kind of log message
+-}
+logTest :: IO ()
+logTest = do
+   logM DEBUG     "log test message 1 of 8"
+   logM INFO      "log test message 2 of 8"
+   logM NOTICE    "log test message 3 of 8"
+   logM WARNING   "log test message 4 of 8"
+   logM ERROR     "log test message 5 of 8"
+   logM CRITICAL  "log test message 6 of 8"
+   logM ALERT     "log test message 7 of 8"
+   logM EMERGENCY "log test message 8 of 8"
diff --git a/src/Sshtun/Switch.hs b/src/Sshtun/Switch.hs
new file mode 100644
--- /dev/null
+++ b/src/Sshtun/Switch.hs
@@ -0,0 +1,47 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+module Sshtun.Switch
+   ( switchWatcher
+   )
+   where
+
+import Control.Concurrent.STM
+import Data.Maybe
+import Network.Curl
+import Text.Printf
+
+import Sshtun.Common
+import Sshtun.Conf
+import Sshtun.Log
+
+
+switchWatcher :: Conf -> TVar Shared -> IO ()
+switchWatcher conf shared = do
+   response <- curlGetString (fromJust . switchUrl $ conf) []
+   switch shared =<< respToState response
+
+   logM INFO "Switch watcher starting to wait"
+   sleep $ switchPollInterval conf
+   logM INFO "Switch watcher done waiting"
+   switchWatcher conf shared
+
+
+respToState :: (CurlCode, String) -> IO DesiredState
+respToState (CurlOK, "1\n") = return Run
+respToState (CurlOK, "0\n") = return Stop
+respToState (code,   _    ) = do
+   logM ERROR $ printf "Problem reading switch file: %s" (show code)
+   return Stop
+
+
+switch :: TVar Shared -> DesiredState -> IO ()
+
+switch shared Run = do
+   logM INFO "Switch watcher setting desired state to Run"
+   run shared
+
+switch shared Stop = do
+   logM INFO "Switch watcher setting desired state to Stop"
+   stop shared
diff --git a/src/Sshtun/Tunnel.hs b/src/Sshtun/Tunnel.hs
new file mode 100644
--- /dev/null
+++ b/src/Sshtun/Tunnel.hs
@@ -0,0 +1,54 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+module Sshtun.Tunnel
+   ( tunnelStart
+   )
+   where
+
+import Control.Concurrent.STM
+import System.Process
+import Text.Printf
+
+import Sshtun.Common
+import Sshtun.Conf
+import Sshtun.Log
+
+
+{- This is an ugly hack to delay things until after switchWatcher 
+   reads the flag status
+-}
+tunnelStart :: Conf -> TVar Shared -> IO ()
+tunnelStart c s = sleep 5 >> tunnelManager c s
+
+
+{- This is the tunnel managing loop, tries to keep it running,
+   if desired
+-}
+tunnelManager :: Conf -> TVar Shared -> IO ()
+tunnelManager conf shared = do
+   state <- atomically . readTVar $ shared
+
+   case state of
+      (Stopped, Run) -> do
+         -- Tunnel is stopped, but we'd like it to be started
+
+         logM INFO "Tunnel manager starting tunnel"
+         ph <- runCommand $ printf "ssh -p %d -N -R %d:localhost:%d %s %s@%s"
+            (sshPort conf) (remotePort conf) (localPort conf)
+            (addlSshArgs conf) (remoteUser conf) (remoteHost conf)
+         atomically $ writeTVar shared (Running ph, Run)
+
+         -- Then, we wait. Possibly for a long time
+         _ <- waitForProcess ph
+
+         -- Tunnel has (possibly unexpectedly) stopped, make a note of this
+         logM INFO "Tunnel manager unblocked"
+         -- Read this again, may have changed during long wait
+         (_, dst) <- atomically . readTVar $ shared
+         atomically $ writeTVar shared (Stopped, dst)
+      _ -> return ()
+
+   sleep $ tunnelRetryDelay conf
+   tunnelManager conf shared
diff --git a/src/main.hs b/src/main.hs
new file mode 100644
--- /dev/null
+++ b/src/main.hs
@@ -0,0 +1,101 @@
+-- Copyright: 2012 Dino Morelli
+-- License: BSD3 (see LICENSE)
+-- Author: Dino Morelli <dino@ui3.info>
+
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Monad
+import Data.Maybe
+import System.Directory
+import System.Environment
+import System.Exit
+import System.Posix.Daemonize (CreateDaemon(..), serviced, simpleDaemon)
+import System.Posix.Signals
+import System.Posix.User
+import Text.Printf
+
+import Paths_sshtun
+import Sshtun.Common
+import Sshtun.Conf
+import Sshtun.Log
+import Sshtun.Switch
+import Sshtun.Tunnel
+
+
+confPath :: FilePath
+confPath = "/etc/sshtun.conf"
+
+
+main :: IO ()
+main = do
+   checkEnv
+
+   shared <- atomically $ newTVar (Stopped, Stop)
+
+   mapM_ ( \signal -> installHandler signal
+      (Catch $ handler shared) Nothing ) [sigINT, sigTERM]
+
+   readFile confPath >>= parseConf >>= either exitFail
+      (\c -> startDaemon (localDaemonUser c) $ sshtunMain c shared)
+
+
+exitFail :: String -> IO ()
+exitFail msg = do
+   instDocPath <- getDataFileName "README"
+   _ <- printf "%s\n\nPlease see %s\n" msg instDocPath
+
+   exitWith $ ExitFailure 1
+
+
+sshtunMain :: Conf -> TVar Shared -> () -> IO ()
+sshtunMain conf shared _ = do
+   initLogging (logFile conf) (logPriority conf)
+
+   logM NOTICE "sshtun starting"
+
+   -- Only start the Switch Watcher thread if the user has configured
+   -- it, otherwise, we run all the time
+   if (isJust . switchUrl $ conf)
+      then do
+         _ <- forkIO $ switchWatcher conf shared
+         return ()
+      else run shared
+
+   tunnelStart conf shared
+
+
+startDaemon :: String -> (() -> IO ()) -> IO ()
+startDaemon localUser p = serviced $ simpleDaemon
+   { program = p
+   , user = Just localUser
+   }
+
+
+handler :: TVar Shared -> IO ()
+handler shared = do
+   logM NOTICE "sshtun stopping"
+   stop shared
+
+
+checkEnv :: IO ()
+checkEnv = do
+   -- root user check
+   euid <- getEffectiveUserID
+   when (euid /= 0) $
+      exitFail "This service must be run by the root user"
+
+   -- conf file check
+   doesFileExist confPath >>= (flip unless $ do
+      let msg = init . unlines $
+            [ "Unable to find conf file at " ++ confPath
+            , "This could mean sshtun isn't installed fully"
+            ]
+      exitFail msg
+      )
+
+   -- args check
+   args <- getArgs
+   when (
+      (not $ null args) &&
+      (not $ head args `elem` ["start", "stop", "restart"])
+      ) $ exitFail "usage: sshtun {start|stop|restart}"
diff --git a/sshtun.cabal b/sshtun.cabal
new file mode 100644
--- /dev/null
+++ b/sshtun.cabal
@@ -0,0 +1,38 @@
+name:                sshtun
+version:             1.0.0
+cabal-version:       >= 1.8
+build-type:          Simple
+license:             BSD3
+license-file:        LICENSE
+copyright:           2012 Dino Morelli
+author:              Dino Morelli
+maintainer:          Dino Morelli <dino@ui3.info>
+stability:           experimental
+homepage:            http://ui3.info/d/proj/sshtun.html
+synopsis:            Wrapper daemon to manage an ssh tunnel
+description:         This is a daemon that executes an ssh command to form a secure tunnel and then blocks on it. If the tunnel goes down, sshtun can attempt to reestablish it. It can also be set up to monitor a file on an http server to determine if the tunnel should be up or not, so you can switch it on or off remotely.
+category:            Application, Network
+tested-with:         GHC >= 7.0.3
+
+data-dir:            resources
+data-files:          README
+                     sshtun.conf
+                     sshtun-switch.sh
+                     logrotate.d/sshtun
+
+source-repository    head
+   type:             darcs
+   location:         http://ui3.info/darcs/sshtun
+
+executable           sshtun
+   main-is:          main.hs
+   build-depends:    base >= 3 && < 5, containers, curl, directory,
+                     hdaemonize, hslogger, mtl, process, regex-compat,
+                     stm, unix
+   hs-source-dirs:   src
+   other-modules:    Sshtun.Common
+                     Sshtun.Conf
+                     Sshtun.Log
+                     Sshtun.Switch
+                     Sshtun.Tunnel
+   ghc-options:      -Wall -threaded
