How to make a website "on a maintance" via a router.ex?

I have a number of websites in phoenix and a “master” one. I want to be able to switch all of the websites to maintance mode via REST API by sending a certain request from the master one. And not by manually creating a “maintenance.html” file in an nginx directory, for each website one by one by logging in into it via ssh. But via REST API. As a result any url requested in a website would be handled by

  get("/*path", PageController, :maintenance_mode)

by Phoenix, not nginx, and which, the route, otherwise would be inactive.

And I want to be able to switch it on and off.

Therefore, there would have to be a condition in router.ex, right?

In the normal mode this would be active:

  get("/*path", ErrorController, :not_found)

How would I implement it?

If you want this to work for all routes then I would implement this by adding a plug to the endpoint. Inside the plug you’d check if the site is in maintenance mode (via an in-memory store or database value). If so then render the maintenance page and halt the Plug.Conn.

How do I add such a plug? One that would render a maintanance page or text “maintanance” and also halt all other routes and requests, by a condition? Note that in defmodule MyAppWeb.Router do I have multiple pipelines and scopes.

Put in your MyAppWeb.Endpoint, as high as you need to:

plug MyAppWeb.Maintenance

And then create module

defmodule MyAppWeb.Maintenance do
  @behaviour Plug

  def begin, do: :persistent_term.put(__MODULE__, true)
  def finish, do: :persistent_term.erase(__MODULE__)

  @impl true
  def init(_), do: []

  @impl true
  def call(conn, _opts) do
    case :persistent_term.get(__MODULE__, false) do
      false -> conn
      true -> conn |> Plug.Conn.send_resp(503, "Maintenance") |> Plug.Conn.halt()
    end
  end
end

Now you can start your maintenance by calling MyAppWeb.Maintenance.begin() and end it via MyAppWeb.Maintenance.finish().

13 Likes