defmodule Mix.Tasks.Phx.Gen.Release do
@shortdoc "Generates release files and optional Dockerfile for release-based deployments"
@moduledoc """
Generates release files and optional Dockerfile for release-based deployments.
The following release files are created:
* `lib/app_name/release.ex` - A release module containing tasks for running
migrations inside a release
* `rel/overlays/bin/migrate` - A migrate script for conveniently invoking
the release system migrations
* `rel/overlays/bin/server` - A server script for conveniently invoking
the release system with environment variables to start the phoenix web server
Note, the `rel/overlays` directory is copied into the release build by default when
running `mix release`.
To skip generating the migration-related files, use the `--no-ecto` flag. To
force these migration-related files to be generated, the use `--ecto` flag.
## Docker
When the `--docker` flag is passed, the following docker files are generated:
* `Dockerfile` - The Dockerfile for use in any standard docker deployment
* `.dockerignore` - A docker ignore file with standard elixir defaults
For extended release configuration, the `mix release.init`task can be used
in addition to this task. See the `Mix.Release` docs for more details.
"""
use Mix.Task
@doc false
def run(args) do
opts = parse_args(args)
if Mix.Project.umbrella?() do
Mix.raise("""
mix phx.gen.release is not supported in umbrella applications.
Run this task in your web application instead.
""")
end
app = Mix.Phoenix.otp_app()
app_namespace = Mix.Phoenix.base()
web_namespace = app_namespace |> Mix.Phoenix.web_module() |> inspect()
binding = [
app_namespace: app_namespace,
otp_app: app,
elixir_vsn: System.version(),
otp_vsn: otp_vsn(),
assets_dir_exists?: File.dir?("assets")
]
Mix.Phoenix.copy_from(paths(), "priv/templates/phx.gen.release", binding, [
{:eex, "rel/server.sh.eex", "rel/overlays/bin/server"},
{:eex, "rel/server.bat.eex", "rel/overlays/bin/server.bat"}
])
if opts.ecto do
Mix.Phoenix.copy_from(paths(), "priv/templates/phx.gen.release", binding, [
{:eex, "rel/migrate.sh.eex", "rel/overlays/bin/migrate"},
{:eex, "rel/migrate.bat.eex", "rel/overlays/bin/migrate.bat"},
{:eex, "release.ex", Mix.Phoenix.context_lib_path(app, "release.ex")}
])
end
if opts.docker do
Mix.Phoenix.copy_from(paths(), "priv/templates/phx.gen.release", binding, [
{:eex, "Dockerfile.eex", "Dockerfile"},
{:eex, "dockerignore.eex", ".dockerignore"}
])
end
File.chmod!("rel/overlays/bin/server", 0o755)
File.chmod!("rel/overlays/bin/server.bat", 0o755)
if opts.ecto do
File.chmod!("rel/overlays/bin/migrate", 0o755)
File.chmod!("rel/overlays/bin/migrate.bat", 0o755)
end
Mix.shell().info("""
Your application is ready to be deployed in a release!
See https://hexdocs.pm/mix/Mix.Tasks.Release.html for more information about Elixir releases.
#{if opts.docker, do: docker_instructions()}
Here are some useful release commands you can run in any release environment:
# To build a release
mix release
# To start your system with the Phoenix server running
_build/dev/rel/#{app}/bin/server
#{if opts.ecto, do: ecto_instructions(app)}
Once the release is running you can connect to it remotely:
_build/dev/rel/#{app}/bin/#{app} remote
To list all commands:
_build/dev/rel/#{app}/bin/#{app}
""")
if opts.ecto do
post_install_instructions("config/runtime.exs", ~r/ECTO_IPV6/, """
[warn] Conditional IPV6 support missing from runtime configuration.
Add the following to your config/runtime.exs:
maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
config :#{app}, #{app_namespace}.Repo,
...,
socket_options: maybe_ipv6
""")
end
post_install_instructions("config/runtime.exs", ~r/PHX_SERVER/, """
[warn] Conditional server startup is missing from runtime configuration.
Add the following to the top of your config/runtime.exs:
if System.get_env("PHX_SERVER") do
config :#{app}, #{web_namespace}.Endpoint, server: true
end
""")
post_install_instructions("config/runtime.exs", ~r/PHX_HOST/, """
[warn] Environment based URL export is missing from runtime configuration.
Add the following to your config/runtime.exs:
host = System.get_env("PHX_HOST") || "example.com"
config :#{app}, #{web_namespace}.Endpoint,
...,
url: [host: host, port: 443]
""")
end
defp parse_args(args) do
args
|> OptionParser.parse!(strict: [ecto: :boolean, docker: :boolean])
|> elem(0)
|> Keyword.put_new_lazy(:ecto, &ecto_sql_installed?/0)
|> Keyword.put_new(:docker, false)
|> Map.new()
end
defp ecto_instructions(app) do
"""
# To run migrations
_build/dev/rel/#{app}/bin/migrate
"""
end
defp docker_instructions do
"""
Using the generated Dockerfile, your release will be bundled into
a Docker image, ready for deployment on platforms that support Docker.
For more information about deploying with Docker see
https://hexdocs.pm/phoenix/releases.html#containers
"""
end
defp paths do
[".", :phoenix]
end
defp post_install_instructions(path, matching, msg) do
case File.read(path) do
{:ok, content} ->
unless content =~ matching, do: Mix.shell().info(msg)
{:error, _} ->
Mix.shell().info(msg)
end
end
def otp_vsn do
major = to_string(:erlang.system_info(:otp_release))
path = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"])
case File.read(path) do
{:ok, content} ->
String.trim(content)
{:error, _} ->
IO.warn("unable to read OTP minor version at #{path}. Falling back to #{major}.0")
"#{major}.0"
end
end
defp ecto_sql_installed?, do: Mix.Project.deps_paths() |> Map.has_key?(:ecto_sql)
end