Running a phoenix appplication on a unix domain socket

I want to run a bunch of phoenix applications on unix domain sockets, and put a nginx reverse proxy in front of them. Plug.Cowboy can listen on a unix domain socket by passing a {:local PATH} to the ip parameter of the endpoint, and it works. Except there is a permission problem. The socket file is created with 0755 permission so my reverse proxy cannot connect to it.

Is there anyway to create the socket file with a more relaxed permission? I don’t want to change my umask. I can make it work by changing the permission manually after the application is up, but that kind of sucks.

I know that it sucks, but this is what manpage says:

When creating a new socket, the owner and group of the socket file are set according to the usual rules. The socket file has all permissions enabled, other than those that are turned off by the process umask(2).

The owner, group, and permissions of a pathname socket can be changed (using chown(2) and chmod(2)).

I understand, but I don’t have a good place to chmod the file. Before the endpoint start, there is no file. How do I know the enpoint has started, and to chmod accordingly? starting a Task to poll the file until it exists? that sucks.

I found another problem when using a Unix socket. It does not delete the file on start. I can do that in my startup script no problem, but what if the Endpoint crashed, but the whole BEAM process does not crash, the file will be there and preventing the Endpoint to startup.

About 1st problem - you can start task after your endpoint that will chmod the socket, like:

[
  # …
  Endpoint,
  {Task, fn -> File.chmod!("/tmp/socket", 0o777 end},
  # …
]

About 2nd problem - there is no direct solution for that. It would highly depend on how you manage startup of your application. If you are using systemd then you could use cleanup action that runs after process shuts down or you could use socket activation instead. It highly depends on your configuration there.

I will try, thank you.

For the second problem, my fear is the endpoint can crash then is restarted by the supervisor. Because the socket file would still be there, it will crash again into an endless loop. Systemd won’t know because the whole beam process is still there.

I think the best solution is to patch cowboy, so it will rm the socket file on startup, and make the permission to the socket file wide open. Http over unix domain socket is unspeced and only used behind reverse proxy. IMHO, it make the most sense this way.

Would it work to write a “GenServer sandwich” around the Endpoint? The first GS would do nothing on boot, but would register to receive callback on shutdown and delete the file. The second would set permissions on startup and do nothing on shutdown.

You could define the 3 processes in a supervisor so that if any of them crash, they all shutdown and reboot. There’s no patching involved in this approach.

If you do that via systemd.socket then systemd will be responsible for creating and deleting socket, so it will not end in endless loop.

You can do it on your own in Application.start/2 callback instead of patching Cowboy. Or as I said, you can do it via ExecStartPost= or ExecStopPost= directives.

1 Like

Thanks @hauleth and @sb8244 . Let me try to contact the cowboy author and send a PR first. I think patching cowboy will provide least surprise for future users and I cannot think of any downsides.

Any results on that?

Ranch took some patches:

After reading the discussion there I understand that the socket file is now removed automatically, right?

And what about the permissions? I read that passing the permisson’s octal is also possible? How does one do this in context of Phoenix?

I don’t know. The Cowboy maintainer does not want to have a default with permission wide open, and I think he has a point. So someone would have to pass the option from Phoenix to Plug to Cowboy then finally to Ranch.

Yes, that’s what I am looking for - whether there’s already an established way of passing appropriate options. Also, I did some tests and (at least by default) it seems that the socket is not removed so does this (automatic removal) require passing some options too?

On one of the cowboy issues someone left an example config for Phoenix:

      http: [
        ip: {:local, System.fetch_env!("SOCKET_FILE_PATH")},
        port: 0,
        transport_options: [
          post_listen_callback: fn _ -> File.chmod!(System.fetch_env!("SOCKET_FILE_PATH"), 0o666) end
      ]

…and it appears to work but I noticed two things: the unix socket was not removed when I tested via IEx and it will not accept websocket requests.

The unix socket not being cleaned up might be due to how IEx terminates so it may be a non-issue in reality.

My problem with websockets may have been how I was testing (via socat) so if you try it and have more luck please report back :slight_smile:

Hope that helps!

Edit: fix formatting

Thank you very much for this! I’ll check it out (including websockets / liveview) and I’ll be back™ :wink: May take a few days though

After checking - websockets/Liveview seem to work in my case but the chmod part seems ineffective. I placed the following:

	http: [
		ip: {:local, socket_path},
		port: 0,
		transport_options: [
			post_listen_callback: fn _ -> File.chmod!(System.get_env("SOCKET_FILE_PATH", "/tmp/phxapp.socket"), 0o666) end
		]
	],

in my runtime.exs. Can you spot anything obvious there? (the socket_path variable is set properly earlier)

I re-checked the linked issue thread and found “we added a post_listen_callback transport option in Ranch 2.1” there. Yet mine is 1.8:

Because cowboy >= 2.6.1 and < 2.9.0 depends on ranch ~> 1.7.1 and cowboy >= 2.9.0 depends on ranch 1.8.0, cowboy >= 2.6.1 requires ranch ~> 1.7.1 or 1.8.0.
And because plug_cowboy >= 2.2.0 depends on cowboy ~> 2.7, plug_cowboy >= 2.2.0 requires ranch ~> 1.7.1 or 1.8.0.
And because your app depends on plug_cowboy ~> 2.5, ranch ~> 1.7.1 or 1.8.0 is required.

so that explains the ineffectiveness of the chmod part. How did you resolve this? What were your set of dependencies when testing this feature?