adrian

adrian

Is nerves using distillery release hooks?

I’m using distillery release hooks for running ecto migrations when the app starts. I’ve followed the distillery guide but is not working. The scrips are not being copied to the device.

This is the related config in the rel/config.exs file:

  set commands: [
    migrate: "rel/commands/migrate.sh",
    seed: "rel/commands/seed.sh",
  ]
  set pre_start_hooks: "rel/hooks/pre_start"

And under rel/hooks/pre_start I have the same migrate.sh file as in rel/commands/migrate.sh:

#!/bin/sh

# exit if any subcommand or pipeline returns a non-zero status.
set +e

echo "Starting migrations!"

release_ctl eval --mfa "Ui.ReleaseTasks.migrate/1" --argv -- "$@"
command
echo "Ending migrations!"

Thanks.

Adrián

Most Liked

ConnorRigby

ConnorRigby

Nerves Core Team

Nerves strips out any runtime components of Distillery. Check this post i wrote a while back to run Ecto migrations on Nerves devices.

mobileoverlord

mobileoverlord

Co-author of Nerves

Nerves does not support using distillery release hooks. This is because Nerves uses a program called erlinit to boot erlang as early and as safely as possible. Writing embedded systems using Nerves means that your application code will live mostly in Elixir / Erlang. This means that its really important that the VM comes up and stays running to prevent bricking the device. To make the boot process more reliable we created shoehorn, an OTP app that is included in Nerves projects by default.

Shoehorn provides two major advantages.

  1. If your application or any of its dependencies fail to start on boot, the VM can continue to run.
  2. Applications can be put into a priority run level to be initialized before any other dependencies and your app.

The second point is the one where you can perform actions similar to that of Distillery release hooks. Using Blinky as an example, we can see how the initial config.exs file is generated:

config :shoehorn,
  init: [:nerves_runtime, :nerves_init_gadget],
  app: Mix.Project.config()[:app]

The option :init takes a list of OTP applications, and MFA’s to execute before the main application start. This is an ordered list. the default [:nerves_runtime, :nerves_init_gadget] means that :nerves_runtime and its deps will start first, followed by :nerves_init_gadget and its deps, followed by the main app declared in the key :app. You could add other OTP application names to this list or MFA’s such as init: [{IO, :puts, ["init_1"]}]. Shoehorn has an example app that performs MFA’s on init here

adrian

adrian

Thanks a lot for your detailed answer @mobileoverlord!

What I’m trying to do is to run migrations for the Phoenix UI. I’ve added the migration release task to shoehorn but I get this error:

** (RuntimeError) connect raised KeyError exception: key :database not found.The exception details are hidden, as they may contain sensitive data such as database credentials. You may set :show_sensitive_data_on_connection_error to true if you wish to see all of the details
    (elixir) lib/keyword.ex:389: Keyword.fetch!/2
    (postgrex) lib/postgrex/protocol.ex:90: Postgrex.Protocol.connect/1
    (db_connection) lib/db_connection/connection.ex:66: DBConnection.Connection.connect/2
    (connection) lib/connection.ex:622: Connection.enter_connect/5
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: nil
State: Postgrex.Protocol
[error] GenServer #PID<0.649.0> terminating

I guess this is because the Ui app has not started yet.

I start the apps before running the migrations, this is the script based on the distillery recommendations:

defmodule Ui.ReleaseTasks do # More info on: https://embedded-elixir.com/post/2017-09-22-using-ecto-and-sqlite3-with-nerves/ https://github.com/bitwalker/distillery/blob/master/docs/guides/running_migrations.md 
  @start_apps [
    :crypto,
    :ssl,
    :postgrex,
    :ecto,
    :logger,
    # If using Ecto 3.0 or higher
    :ecto_sql
  ]

  @repos Application.get_env(:ui, :ecto_repos, [])

  def migrate() do
    repos_pids = start_services()

    run_migrations()

    stop_services()
  end

  def seed(_argv) do
    start_services()

    run_migrations()

    run_seeds()

    # Stop servicej
    stop_services()
  end

  defp start_services do
    IO.puts("Starting dependencies..")
    # Start apps necessary for executing migrations
    Enum.each(@start_apps, &Application.ensure_all_started/1)

    # Start the Repo(s) for app
    IO.puts("Starting repos..")

    # Switch pool_size to 2 for ecto > 3.0
    Enum.map(@repos, & &1.start_link(pool_size: 2))
  end

  defp stop_services do
    IO.puts("Success!")
    :init.stop()
    IO.puts("Stopped!")
  end

  defp run_migrations do
    Enum.each(@repos, &run_migrations_for/1)
  end

  defp run_migrations_for(repo) do
    app = Keyword.get(repo.config(), :otp_app)
    IO.puts("Running migrations for #{app}")
    migrations_path = priv_path_for(repo, "migrations")
    Ecto.Migrator.run(repo, migrations_path, :up, all: true)
  end

  defp run_seeds do
    Enum.each(@repos, &run_seeds_for/1)
  end

  defp run_seeds_for(repo) do
    # Run the seed script if it exists
    seed_script = priv_path_for(repo, "seeds.exs")

    if File.exists?(seed_script) do
      IO.puts("Running seed script..")
      Code.eval_file(seed_script)
    end
  end

  defp priv_path_for(repo, filename) do
    app = Keyword.get(repo.config(), :otp_app)

    repo_underscore =
      repo
      |> Module.split()
      |> List.last()
      |> Macro.underscore()

    priv_dir = "#{:code.priv_dir(app)}"

    Path.join([priv_dir, repo_underscore, filename])
  end
end

I think it fails because the Ui app has not started yet.

I’m not 100% sure about this, but I think the Ui is started with the app because it’s a dependency, but could I move the Ui start to soehorn instead of being done automatically? After that I could put the migrations script and remove the start_services and stop_services functions.

Does it make sense? Could it fail because starting up the db takes time and the migrations will run immediately?

@ConnorRigby the code I use is pretty similar to yours. Start the apps, migrate and stop.

I added to the Ui application.ex

  def start(_type, _args) do
    import Supervisor.Spec

    # Migrate on startup
    Ui.ReleaseTasks.migrate()
...

but it doesn’t work either. The migrations work but after that, the Ui won’t start:

00:00:16.706 [error] Supervisor 'Elixir.Ui.Supervisor' had child 'Elixir.Ui.Repo' started with 'Elixir.Ui.Repo':start_link() at undefined exit with reason {already_started,<0.720.0>} in context start_error
00:00:16.706 [error] CRASH REPORT Process <0.718.0> with 0 neighbours exited with reason: {{shutdown,{failed_to_start_child,'Elixir.Ui.Repo',{already_started,<0.720.0>}}},{'Elixir.Ui.Application',start,[normal,[]]}} in application_master:init/4 line 138

The repos were already started, I tried to stop them but somehow I still get this message.

Where Next?

Popular in Questions Top

chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement