Skip to content

Commit d14de6f

Browse files
committed
Server - add /health check
1 parent edf21ac commit d14de6f

File tree

3 files changed

+166
-98
lines changed

3 files changed

+166
-98
lines changed

server/lib/tau5/startup_info.ex

Lines changed: 155 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,155 @@
1-
defmodule Tau5.StartupInfo do
2-
@moduledoc """
3-
Reports server startup information to the parent process (GUI/tau5-node).
4-
This is done as a separate task after the supervision tree starts to ensure
5-
all endpoints are properly initialized and have their actual ports allocated.
6-
"""
7-
8-
require Logger
9-
10-
def report_server_info() do
11-
Process.sleep(1000)
12-
13-
pid = System.pid()
14-
http_port = get_http_port()
15-
heartbeat_port = Tau5.Heartbeat.get_port()
16-
mcp_port = get_mcp_port()
17-
18-
IO.puts(
19-
"[TAU5_SERVER_INFO:PID=#{pid},HTTP_PORT=#{http_port},HEARTBEAT_PORT=#{heartbeat_port},MCP_PORT=#{mcp_port}]"
20-
)
21-
22-
Logger.info(
23-
"Server started - PID: #{pid}, HTTP: #{http_port}, Heartbeat: #{heartbeat_port}, MCP: #{mcp_port}"
24-
)
25-
end
26-
27-
@doc """
28-
Reports a startup error to the parent process and then halts.
29-
"""
30-
def report_startup_error(message) do
31-
IO.puts("[TAU5_SERVER_ERROR:#{message}]")
32-
Logger.error("Server startup failed: #{message}")
33-
System.halt(1)
34-
end
35-
36-
defp get_http_port do
37-
if System.get_env("TAU5_NO_LOCAL_ENDPOINT") == "true" do
38-
0
39-
else
40-
get_actual_server_port()
41-
end
42-
end
43-
44-
defp get_mcp_port do
45-
if mcp_enabled?() do
46-
case System.get_env("TAU5_MCP_PORT") do
47-
port_str when is_binary(port_str) ->
48-
case Integer.parse(port_str) do
49-
{port, ""} -> port
50-
_ -> Application.get_env(:tau5, :mcp_port, 5555)
51-
end
52-
53-
_ ->
54-
Application.get_env(:tau5, :mcp_port, 5555)
55-
end
56-
else
57-
0
58-
end
59-
end
60-
61-
defp mcp_enabled? do
62-
case System.get_env("TAU5_MCP_ENABLED") do
63-
"true" -> true
64-
"false" -> false
65-
_ -> Application.get_env(:tau5, :mcp_enabled, true)
66-
end
67-
end
68-
69-
defp get_actual_server_port do
70-
case Tau5Web.Endpoint.server_info(:http) do
71-
{:ok, {_address, port}} when is_integer(port) and port > 0 ->
72-
Logger.debug("Got actual port from server_info: #{port}")
73-
port
74-
75-
{:error, reason} ->
76-
Logger.warning("Could not get server info: #{inspect(reason)}")
77-
get_configured_port()
78-
79-
other ->
80-
Logger.warning("Unexpected server_info response: #{inspect(other)}")
81-
get_configured_port()
82-
end
83-
end
84-
85-
defp get_configured_port do
86-
case Application.get_env(:tau5, Tau5Web.Endpoint)[:http][:port] do
87-
0 ->
88-
Logger.warning("Could not determine actual allocated port")
89-
0
90-
91-
port when is_integer(port) ->
92-
port
93-
94-
_ ->
95-
0
96-
end
97-
end
98-
end
1+
defmodule Tau5.StartupInfo do
2+
@moduledoc """
3+
Reports server startup information to the parent process (GUI/tau5-node).
4+
This is done as a separate task after the supervision tree starts to ensure
5+
all endpoints are properly initialized and have their actual ports allocated.
6+
"""
7+
8+
require Logger
9+
10+
def report_server_info() do
11+
http_port = get_http_port()
12+
13+
# Wait for Phoenix to actually be ready to serve requests before signaling GUI
14+
if http_port > 0 do
15+
wait_for_http_ready(http_port)
16+
end
17+
18+
pid = System.pid()
19+
heartbeat_port = Tau5.Heartbeat.get_port()
20+
mcp_port = get_mcp_port()
21+
22+
IO.puts(
23+
"[TAU5_SERVER_INFO:PID=#{pid},HTTP_PORT=#{http_port},HEARTBEAT_PORT=#{heartbeat_port},MCP_PORT=#{mcp_port}]"
24+
)
25+
26+
Logger.info(
27+
"Server started - PID: #{pid}, HTTP: #{http_port}, Heartbeat: #{heartbeat_port}, MCP: #{mcp_port}"
28+
)
29+
end
30+
31+
defp wait_for_http_ready(port, attempt \\ 1) do
32+
# Request the health endpoint to verify Phoenix is ready
33+
# Include session token to bypass InternalEndpointSecurity
34+
token = Application.get_env(:tau5, :session_token)
35+
url = if token do
36+
~c"http://127.0.0.1:#{port}/health?token=#{token}"
37+
else
38+
~c"http://127.0.0.1:#{port}/health"
39+
end
40+
41+
# Use full response format and disable automatic redirect following
42+
http_options = [{:timeout, 5000}, {:connect_timeout, 2000}, {:autoredirect, false}]
43+
options = [{:body_format, :binary}]
44+
45+
result = :httpc.request(:get, {url, []}, http_options, options)
46+
47+
case result do
48+
{:ok, {{_, 200, _}, _, _}} ->
49+
if attempt > 1, do: IO.write(" ready\n")
50+
Logger.debug("Phoenix ready after #{attempt * 100}ms")
51+
:ok
52+
53+
{:error, reason} when attempt < 80 ->
54+
# Show progress dots
55+
if attempt == 1, do: IO.write("Waiting for Phoenix")
56+
if rem(attempt, 10) == 0 do
57+
Logger.debug("Health check error (attempt #{attempt}): #{inspect(reason)}")
58+
end
59+
IO.write(".")
60+
61+
# Wait 100ms and retry (max 8 seconds total)
62+
Process.sleep(100)
63+
wait_for_http_ready(port, attempt + 1)
64+
65+
other when attempt < 80 ->
66+
# Show progress dots for unexpected responses
67+
if attempt == 1, do: IO.write("Waiting for Phoenix")
68+
if rem(attempt, 10) == 0 do
69+
Logger.debug("Health check unexpected response (attempt #{attempt}): #{inspect(other)}")
70+
end
71+
IO.write(".")
72+
73+
# Wait 100ms and retry (max 8 seconds total)
74+
Process.sleep(100)
75+
wait_for_http_ready(port, attempt + 1)
76+
77+
_ ->
78+
IO.write(" timeout\n")
79+
Logger.warning("Phoenix not responding after 8 seconds, last result: #{inspect(result)}")
80+
:ok
81+
end
82+
end
83+
84+
@doc """
85+
Reports a startup error to the parent process and then halts.
86+
"""
87+
def report_startup_error(message) do
88+
IO.puts("[TAU5_SERVER_ERROR:#{message}]")
89+
Logger.error("Server startup failed: #{message}")
90+
System.halt(1)
91+
end
92+
93+
defp get_http_port do
94+
if System.get_env("TAU5_NO_LOCAL_ENDPOINT") == "true" do
95+
0
96+
else
97+
get_actual_server_port()
98+
end
99+
end
100+
101+
defp get_mcp_port do
102+
if mcp_enabled?() do
103+
case System.get_env("TAU5_MCP_PORT") do
104+
port_str when is_binary(port_str) ->
105+
case Integer.parse(port_str) do
106+
{port, ""} -> port
107+
_ -> Application.get_env(:tau5, :mcp_port, 5555)
108+
end
109+
110+
_ ->
111+
Application.get_env(:tau5, :mcp_port, 5555)
112+
end
113+
else
114+
0
115+
end
116+
end
117+
118+
defp mcp_enabled? do
119+
case System.get_env("TAU5_MCP_ENABLED") do
120+
"true" -> true
121+
"false" -> false
122+
_ -> Application.get_env(:tau5, :mcp_enabled, true)
123+
end
124+
end
125+
126+
defp get_actual_server_port do
127+
case Tau5Web.Endpoint.server_info(:http) do
128+
{:ok, {_address, port}} when is_integer(port) and port > 0 ->
129+
Logger.debug("Got actual port from server_info: #{port}")
130+
port
131+
132+
{:error, reason} ->
133+
Logger.warning("Could not get server info: #{inspect(reason)}")
134+
get_configured_port()
135+
136+
other ->
137+
Logger.warning("Unexpected server_info response: #{inspect(other)}")
138+
get_configured_port()
139+
end
140+
end
141+
142+
defp get_configured_port do
143+
case Application.get_env(:tau5, Tau5Web.Endpoint)[:http][:port] do
144+
0 ->
145+
Logger.warning("Could not determine actual allocated port")
146+
0
147+
148+
port when is_integer(port) ->
149+
port
150+
151+
_ ->
152+
0
153+
end
154+
end
155+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule Tau5Web.HealthController do
2+
use Tau5Web, :controller
3+
4+
def index(conn, _params) do
5+
# Minimal health check - if we can respond, Phoenix is ready to serve
6+
text(conn, "ok")
7+
end
8+
end

server/lib/tau5_web/router.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ defmodule Tau5Web.Router do
2929
# Both routes exist, controller will handle the mode check
3030
get("/", CentralController, :index)
3131

32+
# Health check endpoint (kept for backwards compatibility)
33+
get("/health", HealthController, :index)
34+
3235
live_session :default,
3336
on_mount: Tau5Web.AccessTierHook do
3437
live("/app", MainLive)

0 commit comments

Comments
 (0)