diff --git a/hledger-web/Hledger/Web/Main.hs b/hledger-web/Hledger/Web/Main.hs index c3173d103..dbbf7d66a 100644 --- a/hledger-web/Hledger/Web/Main.hs +++ b/hledger-web/Hledger/Web/Main.hs @@ -9,15 +9,19 @@ Released under GPL version 3 or later. module Hledger.Web.Main where +import Control.Exception (bracket) import Control.Monad (when) import Data.String (fromString) import qualified Data.Text as T +import Network.Socket import Network.Wai (Application) -import Network.Wai.Handler.Warp (runSettings, defaultSettings, setHost, setPort) +import Network.Wai.Handler.Warp (runSettings, runSettingsSocket, defaultSettings, setHost, setPort) import Network.Wai.Handler.Launch (runHostPortUrl) import Prelude hiding (putStrLn) -import System.Exit (exitSuccess) +import System.Directory (removeFile) +import System.Exit (exitSuccess, exitFailure) import System.IO (hFlush, stdout) +import System.PosixCompat.Files (getFileStatus, isSocket) import Text.Printf (printf) import Yesod.Default.Config import Yesod.Default.Main (defaultDevelApp) @@ -76,7 +80,27 @@ web opts j = do putStrLn "Press ctrl-c to quit" hFlush stdout let warpsettings = setHost (fromString h) (setPort p defaultSettings) - Network.Wai.Handler.Warp.runSettings warpsettings app + case socket_ opts of + Just s -> do + if isUnixDomainSocketAvailable then + bracket + (do + sock <- socket AF_UNIX Stream 0 + setSocketOption sock ReuseAddr 1 + bind sock $ SockAddrUnix s + listen sock maxListenQueue + return sock + ) + (\_ -> do + sockstat <- getFileStatus s + when (isSocket sockstat) $ removeFile s + ) + (\sock -> Network.Wai.Handler.Warp.runSettingsSocket warpsettings sock app) + else do + putStrLn "Unix domain sockets are not available on your operating system" + putStrLn "Please try again without --socket" + exitFailure + Nothing -> Network.Wai.Handler.Warp.runSettings warpsettings app else do putStrLn "This server will exit after 2m with no browser windows open (or press ctrl-c)" putStrLn "Opening web browser..." diff --git a/hledger-web/Hledger/Web/WebOptions.hs b/hledger-web/Hledger/Web/WebOptions.hs index ee83f1f88..174df1c29 100644 --- a/hledger-web/Hledger/Web/WebOptions.hs +++ b/hledger-web/Hledger/Web/WebOptions.hs @@ -43,6 +43,11 @@ webflags = (\s opts -> Right $ setopt "cors" s opts) "ORIGIN" ("allow cross-origin requests from the specified origin; setting ORIGIN to \"*\" allows requests from any origin") + , flagReq + ["socket"] + (\s opts -> Right $ setopt "socket" s opts) + "SOCKET" + "use the given socket instead of the given IP and port (implies --serve)" , flagReq ["host"] (\s opts -> Right $ setopt "host" s opts) @@ -110,10 +115,11 @@ data WebOpts = WebOpts , capabilities_ :: [Capability] , capabilitiesHeader_ :: Maybe (CI ByteString) , cliopts_ :: CliOpts + , socket_ :: Maybe String } deriving (Show) defwebopts :: WebOpts -defwebopts = WebOpts def def Nothing def def def def [CapView, CapAdd] Nothing def +defwebopts = WebOpts def def Nothing def def def def [CapView, CapAdd] Nothing def Nothing instance Default WebOpts where def = defwebopts @@ -131,9 +137,12 @@ rawOptsToWebOpts rawopts = Left e -> error' ("Unknown capability: " ++ T.unpack e) Right [] -> [CapView, CapAdd] Right xs -> xs + sock = stripTrailingSlash <$> maybestringopt "socket" rawopts return defwebopts - { serve_ = boolopt "serve" rawopts + { serve_ = case sock of + Just _ -> True + Nothing -> boolopt "serve" rawopts , serve_api_ = boolopt "serve-api" rawopts , cors_ = maybestringopt "cors" rawopts , host_ = h @@ -143,6 +152,7 @@ rawOptsToWebOpts rawopts = , capabilities_ = caps , capabilitiesHeader_ = mk . BC.pack <$> maybestringopt "capabilities-header" rawopts , cliopts_ = cliopts + , socket_ = sock } where stripTrailingSlash = reverse . dropWhile (== '/') . reverse -- yesod don't like it diff --git a/hledger-web/hledger-web.cabal b/hledger-web/hledger-web.cabal index def16214f..5b2132e09 100644 --- a/hledger-web/hledger-web.cabal +++ b/hledger-web/hledger-web.cabal @@ -1,10 +1,10 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2. +-- This file has been generated from package.yaml by hpack version 0.32.0. -- -- see: https://github.com/sol/hpack -- --- hash: 194a16a30440803cd2bef5d5df0b6115ad66076c44407ed31efea9c4602e5e95 +-- hash: a9a6dea39ea5c963970cda9f595e7c4251332954aacd137fb6638a1e7640ee59 name: hledger-web version: 1.16.99 @@ -176,12 +176,14 @@ library , http-types , megaparsec >=7.0.0 && <8 , mtl >=2.2.1 + , network , semigroups , shakespeare >=2.0.2.2 , template-haskell , text >=1.2 , time >=1.5 , transformers + , unix-compat , utf8-string , wai , wai-cors diff --git a/hledger-web/hledger-web.m4.md b/hledger-web/hledger-web.m4.md index 84bf633ba..2a9b7801f 100644 --- a/hledger-web/hledger-web.m4.md +++ b/hledger-web/hledger-web.m4.md @@ -72,6 +72,10 @@ as shown in the synopsis above. `--port=PORT` : listen on this TCP port (default: 5000) +`--socket=SOCKETFILE` +: use a unix domain socket file to listen for requests instead of a TCP socket. Implies +`--serve`. It can only be used if the operating system can provide this type of socket. + `--base-url=URL` : set the base url (default: http://IPADDR:PORT). You would change this when sharing over the network, or integrating within a larger website. @@ -119,6 +123,20 @@ You can use `--host` to change this, eg `--host 0.0.0.0` to listen on all config Similarly, use `--port` to set a TCP port other than 5000, eg if you are running multiple hledger-web instances. +Both of these options are ignored when `--socket` is used. In this case, it +creates an `AF_UNIX` socket file at the supplied path and uses that for communication. +This is an alternative way of running multiple hledger-web instances behind +a reverse proxy that handles authentication for different users. +The path can be derived in a predictable way, eg by using the username within the path. +As an example, `nginx` as reverse proxy can use the variabel `$remote_user` to +derive a path from the username used in a [HTTP basic authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/). +The following `proxy_pass` directive allows access to all `hledger-web` +instances that created a socket in `/tmp/hledger/`: + +``` + proxy_pass http://unix:/tmp/hledger/${remote_user}.socket; +``` + You can use `--base-url` to change the protocol, hostname, port and path that appear in hyperlinks, useful eg for integrating hledger-web within a larger website. The default is `http://HOST:PORT/` using the server's configured host address and TCP port diff --git a/hledger-web/package.yaml b/hledger-web/package.yaml index 9870d2b85..2ad18e154 100644 --- a/hledger-web/package.yaml +++ b/hledger-web/package.yaml @@ -120,12 +120,14 @@ library: - http-types - megaparsec >=7.0.0 && <8 - mtl >=2.2.1 + - network - semigroups - shakespeare >=2.0.2.2 - template-haskell - text >=1.2 - time >=1.5 - transformers + - unix-compat - utf8-string - wai - wai-extra