p2phttp: Added --socket option
authorJoey Hess <joeyh@joeyh.name>
Mon, 7 Jul 2025 20:40:02 +0000 (16:40 -0400)
committerJoey Hess <joeyh@joeyh.name>
Mon, 7 Jul 2025 20:40:02 +0000 (16:40 -0400)
Used protectedOutput to set up a umask that makes the socket only
accessible by the current user.

Authentication is still needed when using this option unless it is combined
with --wideopen. It was just simpler to keep authentication separate from
this.

CHANGELOG
Command/P2PHttp.hs
doc/git-annex-p2phttp.mdwn
doc/todo/p2phttp__58___listen_on_unix_domain_sockets.mdwn
doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_3_eadeac1803ffc89e7684df508765e561._comment [new file with mode: 0644]
doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_4_3d8584bfb819f3c6ece5ecdec3d9c020._comment [new file with mode: 0644]

index 09d75769af458c5789100de68b5ab230068759f4..76ce9410b102f09cbe3286702efeb88fd1c4da70 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 git-annex (10.20250631) UNRELEASED; urgency=medium
 
   * p2phttp: Scan multilevel directories with --directory.
+  * p2phttp: Added --socket option.
 
  -- Joey Hess <id@joeyh.name>  Mon, 07 Jul 2025 15:59:42 -0400
 
index 3d61c213ef1df11f0f08d2b6eb31f28a37aa96f8..1463ec5dda3bc9c4e1719c33909f4559b06b92f8 100644 (file)
@@ -22,11 +22,13 @@ import qualified Git.Construct
 import qualified Annex
 import Types.Concurrency
 import qualified Utility.RawFilePath as R
+import Utility.FileMode
 
 import Servant
 import qualified Network.Wai.Handler.Warp as Warp
 import qualified Network.Wai.Handler.WarpTLS as Warp
 import Network.Socket (PortNumber)
+import qualified Network.Socket as Socket
 import System.PosixCompat.Files (isSymbolicLink)
 import qualified Data.Map as M
 import Data.String
@@ -42,6 +44,7 @@ cmd = noMessages $ dontCheck repoExists $
 data Options = Options
        { portOption :: Maybe PortNumber
        , bindOption :: Maybe String
+       , socketOption :: Maybe FilePath
        , certFileOption :: Maybe FilePath
        , privateKeyFileOption :: Maybe FilePath
        , chainFileOption :: [FilePath]
@@ -67,6 +70,10 @@ optParser _ = Options
                ( long "bind" <> metavar paramAddress
                <> help "specify address to bind to"
                ))
+       <*> optional (strOption
+               ( long "socket" <> metavar paramPath
+               <> help "bind to unix domain socket"
+               ))
        <*> optional (strOption
                ( long "certfile" <> metavar paramFile
                <> help "TLS certificate file for HTTPS"
@@ -174,12 +181,20 @@ runServer o mst = go `finally` serverShutdownCleanup mst
                let settings = Warp.setPort port $ Warp.setHost host $
                        Warp.defaultSettings
                mstv <- newTMVarIO mst
+               let app = p2pHttpApp mstv
                case (certFileOption o, privateKeyFileOption o) of
-                       (Nothing, Nothing) -> Warp.runSettings settings (p2pHttpApp mstv)
-                       (Just certfile, Just privatekeyfile) -> do
-                               let tlssettings = Warp.tlsSettingsChain
-                                       certfile (chainFileOption o) privatekeyfile
-                               Warp.runTLS tlssettings settings (p2pHttpApp mstv)
+                       (Nothing, Nothing) -> case socketOption o of
+                               Nothing -> Warp.runSettings settings app
+                               Just socketpath -> 
+                                       withsocket socketpath $ \sock ->
+                                               Warp.runSettingsSocket settings sock app
+                       (Just certfile, Just privatekeyfile) ->
+                               case socketOption o of
+                                       Nothing -> do
+                                               let tlssettings = Warp.tlsSettingsChain
+                                                       certfile (chainFileOption o) privatekeyfile
+                                               Warp.runTLS tlssettings settings app
+                                       Just _socketpath -> giveup "HTTPS is not supported with --socket"
                        _ -> giveup "You must use both --certfile and --privatekeyfile options to enable HTTPS."
        port = maybe
                (fromIntegral defaultP2PHttpProtocolPort)
@@ -189,6 +204,13 @@ runServer o mst = go `finally` serverShutdownCleanup mst
                (fromString "*") -- both ipv4 and ipv6
                fromString
                (bindOption o)
+       withsocket socketpath =
+               bracket (opensocket socketpath) Socket.close
+       opensocket socketpath = protectedOutput $ do
+               sock <- Socket.socket Socket.AF_UNIX Socket.Stream 0
+               Socket.bind sock $ Socket.SockAddrUnix socketpath
+               Socket.listen sock Socket.maxListenQueue
+               return sock
 
 mkServerState :: Options -> M.Map Auth P2P.ServerMode -> Annex P2PHttpServerState
 mkServerState o authenv = 
index c712721fb1ff766175e1205d54eda0357a93d132..a7ecd581d8eebfd7a6cef473aa95fd415d8d4cc7 100644 (file)
@@ -103,6 +103,12 @@ convenient way to download the content of any key, by using the path
 
   What address to bind to. The default is to bind to all addresses.
 
+* `--socket=path`
+
+  Rather than binding to an address, create and listen to a unix domain
+  socket at the specified location. This can be useful when proxying
+  to `git-annex p2phttp`.
+
 * `--certfile=filename` 
 
   TLS certificate file to use. Combining this with `--privatekeyfile`
index d61a8a5402af3c7f47d5b65cd5538bc868ab76bf..58a79fe2f4ca5ac10156995f28ccfca425a17f5a 100644 (file)
@@ -1,3 +1,5 @@
 For p2phttp support in forgejo-aneksajo I decided to just spawn a `git annex p2phttp --wideopen` server, do authentication on the Forgejo side, and then proxy requests to p2phttp. Since p2phttp only supports serving one repository at the moment this means that I have to allocate one free port per repository. Actually finding a free port adds complexity and a race condition, as there also seems to be no way to set `--port 0` for p2phttp and then figure out which port it bound to.
 
 This would be simplified if p2phttp could listen on unix domain sockets instead.
+
+> [[done]] --[[Joey]]
diff --git a/doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_3_eadeac1803ffc89e7684df508765e561._comment b/doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_3_eadeac1803ffc89e7684df508765e561._comment
new file mode 100644 (file)
index 0000000..b596538
--- /dev/null
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 3"""
+ date="2025-07-07T19:26:56Z"
+ content="""
+I've made it support nested directories, which was easy.
+
+Should be possible to make it use runSettingsSocket indeed though.
+"""]]
diff --git a/doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_4_3d8584bfb819f3c6ece5ecdec3d9c020._comment b/doc/todo/p2phttp__58___listen_on_unix_domain_sockets/comment_4_3d8584bfb819f3c6ece5ecdec3d9c020._comment
new file mode 100644 (file)
index 0000000..43e4f2e
--- /dev/null
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2025-07-07T20:28:38Z"
+ content="""
+Implemented a --socket option. I have not tried connecting to it as a
+client, but it seems to be listening to it, so I assume all is good.
+
+Note that it still checks for authentication when using the socket,
+so you will probably want to combine it with --wideopen. The socket mode
+allows only the current user to access it.
+"""]]