<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://sgeos.github.io/feed/elixir.xml" rel="self" type="application/atom+xml" /><link href="https://sgeos.github.io/" rel="alternate" type="text/html" /><updated>2026-07-12T01:04:06+00:00</updated><id>https://sgeos.github.io/feed/elixir.xml</id><title type="html">Brendan A R Sechter’s Development Blog | Elixir</title><subtitle>A personal technical notebook covering systems programming, systems philosophy, tooling, mathematics, and emerging software paradigms.
</subtitle><author><name>Brendan Sechter</name></author><entry><title type="html">Storing Elixir Release Configuration in Environment Variables with Distillery</title><link href="https://sgeos.github.io/phoenix/elixir/erlang/ecto/distillery/postgresql/mysql/2016/09/18/storing-elixir-release-configuration-in-environment-variables-with-distillery.html" rel="alternate" type="text/html" title="Storing Elixir Release Configuration in Environment Variables with Distillery" /><published>2016-09-18T06:31:36+00:00</published><updated>2016-09-18T06:31:36+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/erlang/ecto/distillery/postgresql/mysql/2016/09/18/storing-elixir-release-configuration-in-environment-variables-with-distillery</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/erlang/ecto/distillery/postgresql/mysql/2016/09/18/storing-elixir-release-configuration-in-environment-variables-with-distillery.html"><![CDATA[<!-- A45 -->
<script>console.log("A45");</script>

<p>This post covers storing settings like database configuration in
environment variables with <a href="https://github.com/bitwalker/distillery">distillery</a>.
A <a href="https://sgeos.github.io/phoenix/elixir/erlang/ecto/exrm/postgresql/mysql/2016/09/11/storing-elixir-release-configuration-in-environment-variables.html">prior post</a> covered this with the <a href="https://github.com/bitwalker/exrm">Elixir Release Manager (exrm)</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-09-18 06:31:36 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 12.0-CURRENT <span class="c">#0 r304324: Thu Aug 18 13:27:23 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.13.0
Elixir: 1.3.2
OTP:    19.0.7
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.2.1
<span class="nv">$ </span><span class="nb">cat </span>mix.lock | <span class="nb">grep </span>distillery | <span class="nb">cut</span> <span class="nt">-d</span><span class="s2">" "</span> <span class="nt">-f</span> 3,6 | <span class="nb">sed</span> <span class="s1">'s/[",]//g'</span>
distillery: 0.9.9</code></pre></figure>

<h2 id="creating-a-sample-project">Creating a Sample Project</h2>

<p>Create a new Phoenix project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new phoenix_environment_settings <span class="nt">--no-brunch</span>
<span class="nb">cd </span>phoenix_environment_settings
mix ecto.create</code></pre></figure>

<p>Create a simple JSON memo endpoint.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.gen.json Memo memos title:string body:string</code></pre></figure>

<p>Revise <strong>web/router.ex</strong>.
Uncomment the “/api” scope and add the “/memos” route.</p>

<p><strong>web/router.ex</strong>.partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  scope <span class="s2">"/api"</span>, PhoenixEnvironmentSettings <span class="k">do
    </span>pipe_through :api

    resources <span class="s2">"/memos"</span>, MemoController, except: <span class="o">[</span>:new, :edit]
  end</code></pre></figure>

<p>Run the migration.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix ecto.migrate</code></pre></figure>

<p>Tests should pass.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix <span class="nb">test</span></code></pre></figure>

<p>Start the server.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.server</code></pre></figure>

<p>POST and GET a memo to make sure the server works.
A <a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">prior post</a> covers a shell script
for conveniently interacting with this sample app.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "New Title", "body": "This is the new memo body."}}'</span> http://localhost:4000/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos/1</code></pre></figure>

<h2 id="environment-variable-definitions">Environment Variable Definitions</h2>

<p>Define the following environment variables.
Feel free to use different values and a static value for <strong>SECRET_KEY_BASE</strong>.
Note that depending on how your database is configured,
<strong>DB_PASSWORD</strong> may be unnecessary for connections from <strong>localhost</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">NODE_NAME</span><span class="o">=</span>leaf_node
<span class="nb">export </span><span class="nv">COOKIE</span><span class="o">=</span>thin_mints
<span class="nb">export </span><span class="nv">DB_USER</span><span class="o">=</span>phoenix_environment_settings
<span class="nb">export </span><span class="nv">DB_PASSWORD</span><span class="o">=</span>phoenix_environment_settings_password
<span class="nb">export </span><span class="nv">DB_NAME</span><span class="o">=</span>phoenix_environment_settings_prod
<span class="nb">export </span><span class="nv">DB_HOST</span><span class="o">=</span>localhost
<span class="nb">export </span><span class="nv">HOST</span><span class="o">=</span>host.example.org
<span class="nb">export </span><span class="nv">PORT</span><span class="o">=</span>7777
<span class="nb">export </span><span class="nv">SECRET_KEY_BASE</span><span class="o">=</span><span class="si">$(</span>elixir <span class="nt">-e</span> <span class="s2">":crypto.strong_rand_bytes(48) |&gt; Base.encode64 |&gt; IO.puts"</span><span class="si">)</span></code></pre></figure>

<h2 id="configuring-the-application">Configuring the Application</h2>

<p>Set server to true in <strong>config/prod.exs</strong>.
Also add a version entry.
Replace static configuration with dynamic configuration that
will be pulled in from environment variables.
None of this needs to be kept out version control,
so it is safe to delete <strong>config/prod.secret.exs</strong>.
Those settings have been merged in.</p>

<p><strong>config/prod.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Config</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">"PORT"</span><span class="p">}],</span>
  <span class="ss">url:</span> <span class="p">[</span><span class="ss">host:</span> <span class="s2">"${HOST}"</span><span class="p">,</span> <span class="ss">port:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">"PORT"</span><span class="p">}],</span>
  <span class="ss">cache_static_manifest:</span> <span class="s2">"priv/static/manifest.json"</span><span class="p">,</span>
  <span class="ss">server:</span> <span class="no">true</span><span class="p">,</span>
  <span class="ss">root:</span> <span class="s2">"."</span><span class="p">,</span>
  <span class="ss">version:</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Project</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="ss">:version</span><span class="p">]</span>

<span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">level:</span> <span class="ss">:info</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">secret_key_base:</span> <span class="s2">"${SECRET_KEY_BASE}"</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="s2">"${DB_USER}"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="s2">"${DB_PASSWORD}"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="s2">"${DB_NAME}"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="s2">"${DB_HOST}"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">20</span></code></pre></figure>

<p>Alternatively, keep hard coded values in <strong>config/prod.secret.exs</strong>
when running local release builds and use a version with dynamic
environment variable settings in a real production environment.</p>

<p><strong>config/prod.secret.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Config</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">secret_key_base:</span> <span class="s2">"${SECRET_KEY_BASE}"</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="s2">"${DB_USER}"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="s2">"${DB_PASSWORD}"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="s2">"${DB_NAME}"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="s2">"${DB_HOST}"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">20</span></code></pre></figure>

<p>Optionally, add dynamic configuration with a default values to <strong>config/dev.exs</strong>.
The environment variable replacement above is <strong>only</strong> suitable for releases.
The interpreted solution below is <strong>only</strong> suitable for development with <strong>mix</strong>.</p>

<p><strong>config/dev.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"PORT"</span><span class="p">)</span> <span class="o">||</span> <span class="mi">4000</span><span class="p">],</span>

<span class="c1"># ... at the end of the file ...</span>

<span class="c1"># Configure your database</span>
<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_USER"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"postgres"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_PASSWORD"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"postgres"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_NAME"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"phoenix_environment_settings_dev"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_HOST"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"localhost"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">10</span></code></pre></figure>

<h2 id="generating-a-release">Generating a Release</h2>

<p>Add <a href="https://github.com/bitwalker/distillery">distillery</a> to <strong>mix.exs</strong> as a project dependency.</p>

<p><strong>mix.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[{</span><span class="ss">:phoenix</span><span class="p">,</span> <span class="s2">"~&gt; 1.2.1"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_pubsub</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_ecto</span><span class="p">,</span> <span class="s2">"~&gt; 3.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:postgrex</span><span class="p">,</span> <span class="s2">"&gt;= 0.0.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_html</span><span class="p">,</span> <span class="s2">"~&gt; 2.6"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_live_reload</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">,</span> <span class="ss">only:</span> <span class="ss">:dev</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:gettext</span><span class="p">,</span> <span class="s2">"~&gt; 0.11"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:distillery</span><span class="p">,</span> <span class="s2">"~&gt; 0.9"</span><span class="p">},</span> <span class="c1"># this line is new</span>
     <span class="p">{</span><span class="ss">:cowboy</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">}]</span>
  <span class="k">end</span></code></pre></figure>

<p>Install and initialize distillery.
This will create the <strong>rel/</strong> directory.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix deps.get
mix deps.compile
mix release.init</code></pre></figure>

<p>Environment variables will be used as knobs to configure the app.
Note that the <strong>REPLACE_OS_VARS=true</strong> environment variable needs to
be defined to use environment variables for dynamic configuration.</p>

<p>The <strong>rel/vm.args</strong> file is primarily used to configure the erlang VM.
It can also be used to define application configuration parameters.
Application configuration parameters defined in this file can be passed
into the program as atoms or integers.
Note that the location of this file can be <a href="https://hexdocs.pm/distillery/runtime-configuration.html">configured</a>
with the <strong>RELEASE_CONFIG_DIR</strong> environment variable.
Add the following to <strong>rel/vm.args</strong>.</p>

<p><strong>rel/vm.args</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">## Name of the node</span>
<span class="nt">-name</span> <span class="k">${</span><span class="nv">NODE_NAME</span><span class="k">}</span>

<span class="c">## Cookie for distributed erlang</span>
<span class="nt">-setcookie</span> <span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span>

<span class="c">## App Settings</span>
<span class="nt">-phoenix_environment_settings</span> port <span class="k">${</span><span class="nv">PORT</span><span class="k">}</span></code></pre></figure>

<p>Alternatively, <strong>rel/sys.config</strong> can be used to pass in application configuration parameters.
This file is written in Erlang.</p>

<p><strong>rel/sys.config</strong></p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="p">[</span>
  <span class="p">{</span><span class="n">phoenix_environment_settings</span><span class="p">,</span> <span class="p">[</span>
    <span class="p">{</span><span class="n">port</span><span class="p">,</span> <span class="s">"${PORT}"</span><span class="p">}</span>
  <span class="p">]}</span>
<span class="p">].</span></code></pre></figure>

<p>The Elixir <strong>config/config.exs</strong> file is probably a better place to
define non-VM settings for an Elixir application.
The <strong>distillery</strong> <a href="https://hexdocs.pm/distillery/runtime-configuration.html">documentation</a> seems to indicate that a release
can either use <strong>sys.config</strong> or <strong>config.exs</strong> but not both.
The exact <strong>config.exs</strong> settings for this project were covered in
the <strong>Configuring the Application</strong> section above.</p>

<h2 id="adding-release-tasks">Adding Release Tasks</h2>

<p>After deploying a release with <strong>distillery</strong>, <strong>mix</strong> is no longer available.
Define a module for release tasks with a <code class="language-plaintext highlighter-rouge">mix ecto.migrate</code> equivalent.
Note that this is an Erlang module written in Elixir.</p>

<p><strong>lib/release_tasks.ex</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="ss">:release_tasks</span> <span class="k">do</span>
  <span class="k">def</span> <span class="n">migrate</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">ensure_all_started</span><span class="p">(</span><span class="ss">:phoenix_environment_settings</span><span class="p">)</span>
    <span class="n">path</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">app_dir</span><span class="p">(</span><span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="s2">"priv/repo/migrations"</span><span class="p">)</span>
    <span class="no">Ecto</span><span class="o">.</span><span class="no">Migrator</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="ss">:up</span><span class="p">,</span> <span class="ss">all:</span> <span class="no">true</span><span class="p">)</span>
    <span class="ss">:init</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>After generating a release, this task can be run as follows.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings <span class="nb">command </span>release_tasks migrate</code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">mix ecto.create</code> and <code class="language-plaintext highlighter-rouge">mix ecto.drop</code> tasks are run less frequently
so it may just makes sense to just manually create or drop the database
and user.  PostgreSQL commands follow.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># PostgreSQL `mix ecto.create` equivalent</span>
psql <span class="nt">-c</span> <span class="s2">"CREATE USER </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2"> WITH PASSWORD '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
createdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span>
psql <span class="nt">-c</span> <span class="s2">"GRANT ALL PRIVILEGES ON DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2"> to </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">;"</span>

<span class="c"># PostgreSQL `mix ecto.drop` equivalent</span>
dropdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span>
dropuser <span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span>

<span class="c"># PostgresSQL interactive terminal</span>
<span class="nv">PGPASSWORD</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">"</span> psql <span class="nt">-U</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>The MySQL commands look like this.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># MySQL `mix ecto.create` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span>
mysql <span class="nt">-e</span> <span class="s2">"GRANT ALL PRIVILEGES ON </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">.* TO '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
mysql <span class="nt">-e</span> <span class="s2">"FLUSH PRIVILEGES;"</span>

<span class="c"># MySQL `mix ecto.drop` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost';"</span>

<span class="c"># MySQL interactive terminal</span>
mysql <span class="nt">-u</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-p</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>Note that the password security in the above commands is less than ideal.
Also, an existing superuser and password may need to be explicily
specified when creating the new user and database.</p>

<h2 id="adding-custom-commands-for-release-tasks">Adding Custom Commands for Release Tasks</h2>

<p>Distillery supports custom commands.
To wrap the above release tasks in custom commands,
first create the <strong>rel/commands</strong> directory.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">mkdir </span>rel/commands</code></pre></figure>

<p>Next add a command for <code class="language-plaintext highlighter-rouge">ecto.migrate</code>.</p>

<p><strong>rel/commands/ecto_migrate</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/usr/bin/env sh</span>

<span class="c"># mix ecto.migrate` equivalent</span>
<span class="s2">"</span><span class="k">${</span><span class="nv">SCRIPT</span><span class="k">}</span><span class="s2">"</span> <span class="nb">command </span>release_tasks migrate</code></pre></figure>

<p>The PostgreSQL versions of the <code class="language-plaintext highlighter-rouge">ecto.create</code> and <code class="language-plaintext highlighter-rouge">ecto.drop</code> commands follow.</p>

<p><strong>rel/commands/ecto_create</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/usr/bin/env sh</span>

<span class="c"># PostgreSQL `mix ecto.create` equivalent</span>
psql <span class="nt">-c</span> <span class="s2">"CREATE USER </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2"> WITH PASSWORD '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span> <span class="o">&amp;&amp;</span>
createdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span> <span class="o">&amp;&amp;</span>
psql <span class="nt">-c</span> <span class="s2">"GRANT ALL PRIVILEGES ON DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2"> to </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">;"</span> <span class="o">&amp;&amp;</span>
<span class="nb">echo</span> <span class="s2">"The database '</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">' and role '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">' have been created."</span></code></pre></figure>

<p><strong>rel/commands/ecto_drop</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/usr/bin/env sh</span>

<span class="c"># PostgreSQL `mix ecto.drop` equivalent</span>
dropdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span> <span class="o">&amp;&amp;</span>
dropuser <span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span> <span class="o">&amp;&amp;</span>
<span class="nb">echo</span> <span class="s2">"The database '</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">' and role '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">' have been dropped."</span></code></pre></figure>

<p>Alternatively, the MySQL versions of <code class="language-plaintext highlighter-rouge">ecto.create</code> and <code class="language-plaintext highlighter-rouge">ecto.drop</code> look like this.</p>

<p><strong>rel/commands/ecto_create</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/usr/bin/env sh</span>

<span class="c"># MySQL `mix ecto.create` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span> <span class="o">&amp;&amp;</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span> <span class="o">&amp;&amp;</span>
mysql <span class="nt">-e</span> <span class="s2">"GRANT ALL PRIVILEGES ON </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">.* TO '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span> <span class="o">&amp;&amp;</span>
mysql <span class="nt">-e</span> <span class="s2">"FLUSH PRIVILEGES;"</span> <span class="o">&amp;&amp;</span>
<span class="nb">echo</span> <span class="s2">"The database '</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">' and role '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">' have been created."</span></code></pre></figure>

<p><strong>rel/commands/ecto_drop</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/usr/bin/env sh</span>

<span class="c"># MySQL `mix ecto.drop` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span> <span class="o">&amp;&amp;</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost';"</span> <span class="o">&amp;&amp;</span>
<span class="nb">echo</span> <span class="s2">"The database '</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">' and role '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">' have been dropped."</span></code></pre></figure>

<p>Finally, modify <strong>rel/config.exs</strong> so that the custom commands are present.
The following version is a rewrite.</p>

<p><strong>rel/config.exs</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">use Mix.Releases.Config,
    default_release: :default,
    default_environment: :prod

environment :prod <span class="k">do
  </span><span class="nb">set </span>include_erts: <span class="nb">true
  set </span>include_src: <span class="nb">false
  set </span>commands: <span class="o">[</span>
    <span class="s2">"ecto.migrate"</span>: <span class="s2">"rel/commands/ecto_migrate"</span>,
    <span class="s2">"ecto.create"</span>: <span class="s2">"rel/commands/ecto_create"</span>,
    <span class="s2">"ecto.drop"</span>: <span class="s2">"rel/commands/ecto_drop"</span>
  <span class="o">]</span>
end

release :phoenix_environment_settings <span class="k">do
  </span><span class="nb">set </span>version: current_version<span class="o">(</span>:phoenix_environment_settings<span class="o">)</span>
end</code></pre></figure>

<h2 id="running-the-release">Running the Release</h2>

<p>Build the release with the configuration files.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># rm -rf rel/phoenix_environment_settings/ # optional, no stale release files</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix compile
<span class="c"># brunch build --production # if using brunch</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix phoenix.digest
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release <span class="nt">--env</span><span class="o">=</span>prod</code></pre></figure>

<p>Run the <code class="language-plaintext highlighter-rouge">ecto.create</code> and <code class="language-plaintext highlighter-rouge">ecto.migrate</code> custom commands defined in the above sections.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings ecto.create
<span class="nv">REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings ecto.migrate</code></pre></figure>

<p>Start the release.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings start</code></pre></figure>

<p>Make sure the server responds.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Memo A", "body": "Alpha memo body."}}'</span> <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Memo B", "body": "Beta memo body."}}'</span> <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span>
curl <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span></code></pre></figure>

<p>Stop the release.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings start</code></pre></figure>

<h2 id="custom-application-settings">Custom Application Settings</h2>

<p>Add this <a href="https://gist.github.com/bitwalker/a4f73b33aea43951fe19b242d06da7b9">configuration gist</a> to <strong>lib/config.ex</strong>.
This <a href="https://hexdocs.pm/distillery/runtime-configuration.html">configuration wrapper</a> allows the same convenient
<code class="language-plaintext highlighter-rouge">{:system, "VARIABLE", "default"}</code> convention to be used with both <strong>mix</strong> and releases.
Note that this will not help configure things like <strong>PhoenixEnvironmentSettings.Repo</strong>
because they were not written to get settings via this module.
The linked to version has the typespecs and documentation.</p>

<p><strong>lib/config.ex</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">defmodule Config <span class="k">do
  </span>def get<span class="o">(</span>app, key, default <span class="se">\\</span> nil<span class="o">)</span> when is_atom<span class="o">(</span>app<span class="o">)</span> and is_atom<span class="o">(</span>key<span class="o">)</span> <span class="k">do
    case</span> Application.get_env<span class="o">(</span>app, key<span class="p">)</span> <span class="k">do</span>
      <span class="o">{</span>:system, env_var<span class="o">}</span> -&gt;
        <span class="k">case</span> System.get_env<span class="o">(</span>env_var<span class="p">)</span> <span class="k">do
          </span>nil -&gt; default
          val -&gt; val
        end
      <span class="o">{</span>:system, env_var, preconfigured_default<span class="o">}</span> -&gt;
        <span class="k">case</span> System.get_env<span class="o">(</span>env_var<span class="p">)</span> <span class="k">do
          </span>nil -&gt; preconfigured_default
          val -&gt; val
        end
      nil -&gt;
        default
      val -&gt;
        val
    end
  end

  def get_integer<span class="o">(</span>app, key, default <span class="se">\\</span> nil<span class="o">)</span> <span class="k">do
    case</span> get<span class="o">(</span>app, key, nil<span class="p">)</span> <span class="k">do
      </span>nil -&gt; default
      n when is_integer<span class="o">(</span>n<span class="o">)</span> -&gt; n
      n -&gt;
        <span class="k">case</span> Integer.parse<span class="o">(</span>n<span class="p">)</span> <span class="k">do</span>
          <span class="o">{</span>i, _<span class="o">}</span> -&gt; i
          :error -&gt; default
        end
    end
  end
end</code></pre></figure>

<p>Add these lines to <strong>config/config.exs</strong>.</p>

<p><strong>config/config.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">config :phoenix_environment_settings,
  ecto_repos: <span class="o">[</span>PhoenixEnvironmentSettings.Repo],
  welcome_message: <span class="o">{</span>:system, <span class="s2">"WELCOME_MESSAGE"</span>, <span class="s2">"Hello, world!"</span><span class="o">}</span>,
  magic_number: <span class="o">{</span>:system, <span class="s2">"MAGIC_NUMBER"</span>, 42<span class="o">}</span></code></pre></figure>

<p>Access the settings in <code class="language-plaintext highlighter-rouge">iex -S mix</code> like this.
You should get the default values.</p>

<p><strong>iex -S mix</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="no">Config</span><span class="o">.</span><span class="n">get</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="ss">:welcome_message</span>
<span class="no">Config</span><span class="o">.</span><span class="n">get_integer</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="ss">:magic_number</span></code></pre></figure>

<p>Define the environment variables and run the commands in <strong>iex</strong> again.
You should get the environment variable values this time.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">WELCOME_MESSAGE</span><span class="o">=</span><span class="s2">"Welcome. Try, but there is no escape."</span>
<span class="nb">export </span><span class="nv">MAGIC_NUMBER</span><span class="o">=</span><span class="nt">-1</span></code></pre></figure>

<p>This is useful for defining environment variable knobs to control run time behavior.
It is not a solution for problems that rely on compile time behavior,
like using environment variables to specify <a href="https://gist.github.com/chrismccord/e0eaefe30d2ecd85b4ac">dynamic routes</a>.</p>

<h2 id="other-considerations">Other Considerations</h2>

<p>Note that when using <strong>REPLACE_OS_VARS=true</strong>, the environment variables in <strong>rel/sys.conf</strong> or
<strong>config/config.exs</strong> will always be replaced with strings.
The following almost certainly does not work as expected.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">DB_POOL_SIZE</span><span class="o">=</span>20</code></pre></figure>

<p><strong>config/prod.exs</strong> or
<strong>config/prod.secret.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  pool_size: <span class="s2">"</span><span class="k">${</span><span class="nv">DB_POOL_SIZE</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>The following will work in development, but not production.</p>

<p><strong>config/dev.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  pool_size: <span class="o">(</span>System.get_env<span class="o">(</span><span class="s2">"DB_POOL_SIZE"</span><span class="o">)</span> <span class="o">||</span> <span class="s2">"10"</span><span class="o">)</span> |&gt; String.to_integer</code></pre></figure>

<p>If integers or atoms need to be passed in directly, use <strong>vm.args</strong>.
The author could not figure out how to pass <strong>DB_POOL_SIZE</strong> to Repo via <strong>vm.args</strong>.</p>

<h2 id="next-steps">Next Steps</h2>

<p>Consider looking into <a href="https://github.com/bitwalker/conform">conform</a>, a library for working with init-style configuration.
Also consider looking into <a href="https://github.com/boldpoker/edeliver">edeliver</a> for deployment.</p>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://github.com/bitwalker/distillery">Distillery</a></li>
  <li><a href="https://hexdocs.pm/distillery/runtime-configuration.html">Distillery, Configuration</a></li>
  <li><a href="https://hexdocs.pm/distillery/runtime-configuration.html">Distillery, Runtime Configuration</a></li>
  <li><a href="https://github.com/bitwalker/exrm">Elixir Release Manager (exrm)</a></li>
  <li><a href="https://exrm.readme.io/docs/release-configuration">Elixir Release Manager Configuration</a></li>
  <li><a href="http://blog.plataformatec.com.br/2016/05/how-to-config-environment-variables-with-elixir-and-exrm/">Elixir Release Manager, How to config environment variables with Elixir and Exrm</a></li>
  <li><a href="http://blog.plataformatec.com.br/2016/04/running-migration-in-an-exrm-release/">Elixir Release Manager, Running migration in an Exrm release</a></li>
  <li><a href="https://gist.github.com/antipax/90cc36d29c2a2a5d4629">Elixir Release Manager, How to run Ecto migrations from an exrm release</a></li>
  <li><a href="https://gist.github.com/bitwalker/a4f73b33aea43951fe19b242d06da7b9">Elixir, Useful Config Wrapper Gist</a></li>
  <li><a href="http://sheldonkreger.com/understanding-config-in-elixir.html">Elixir, Understanding Config in Elixir</a></li>
  <li><a href="http://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/erlang/ecto/exrm/postgresql/mysql/2016/09/11/storing-elixir-release-configuration-in-environment-variables.html">Elixir, Storing Elixir Release Configuration in Environment Variables with exrm</a></li>
  <li><a href="https://github.com/bitwalker/conform">Elixir, conform</a></li>
  <li><a href="https://github.com/boldpoker/edeliver">Elixir, edeliver</a></li>
  <li><a href="http://erlang.org/doc/man/erl.html">Erlang, man erl</a></li>
  <li><a href="http://erlang.org/doc/man/config.html">Erlang, man config</a></li>
  <li><a href="https://gist.github.com/chrismccord/e0eaefe30d2ecd85b4ac">Phoenix, Dynamic Dispatch Gist</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">Phoenix, A Shell Script for Working with Phoenix JSON APIs</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/erlang/freebsd/2016/03/21/phoenix-as-a-service-on-freebsd.html">Phoenix as a Service on FreeBSD</a></li>
  <li><a href="https://github.com/bitwalker/exrm/issues/67#issuecomment-183457937">Mix, Accessing Mix tasks from release</a></li>
  <li><a href="https://gist.github.com/sgeos/1fed5eb24d80b97a338249dd95d55082">Mix, exrm PostgreSQL/MySQL release equivalents for <code class="language-plaintext highlighter-rouge">mix ecto.create</code> and <code class="language-plaintext highlighter-rouge">mix ecto.drop</code>.</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="erlang" /><category term="ecto" /><category term="distillery" /><category term="postgresql" /><category term="mysql" /></entry><entry><title type="html">Elixir/Erlang, Running OTP Observer Remotely</title><link href="https://sgeos.github.io/elixir/erlang/observer/2016/09/16/elixir_erlang_running_otp_observer_remotely.html" rel="alternate" type="text/html" title="Elixir/Erlang, Running OTP Observer Remotely" /><published>2016-09-16T14:17:43+00:00</published><updated>2016-09-16T14:17:43+00:00</updated><id>https://sgeos.github.io/elixir/erlang/observer/2016/09/16/elixir_erlang_running_otp_observer_remotely</id><content type="html" xml:base="https://sgeos.github.io/elixir/erlang/observer/2016/09/16/elixir_erlang_running_otp_observer_remotely.html"><![CDATA[<!-- A44 -->
<script>console.log("A44");</script>

<p>This post covers running the OTP’s observer on a node running on a remote machine.
It is a step-by-step of <a href="https://gist.github.com/pnc/9e957e17d4f9c6c81294">this gist</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-09-16 14:17:43 +0000

<span class="c"># localhost</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 15.6.0: Mon Aug 29 20:21:34 PDT 2016<span class="p">;</span> root:xnu-3248.60.11~1/RELEASE_X86_64 x86_64
<span class="nv">$ </span>mix hex.info
Hex:    0.13.0
Elixir: 1.3.2
OTP:    19.0
<span class="k">*</span> snip <span class="k">*</span>

<span class="c"># remotehost</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 12.0-CURRENT <span class="c">#0 r304324: Thu Aug 18 13:27:23 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.13.0
Elixir: 1.3.2
OTP:    19.0.7
<span class="k">*</span> snip <span class="k">*</span></code></pre></figure>

<h2 id="instructions">Instructions</h2>

<p>On the remote host, start a node with known name and cookie values.
<strong>iex</strong> or <strong>erl</strong> will be used for this example.</p>

<p><strong>sh</strong> remotehost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">IF</span><span class="o">=</span>em0
<span class="nv">HOST</span><span class="o">=</span><span class="si">$(</span>ifconfig <span class="k">${</span><span class="nv">IF</span><span class="k">}</span> | <span class="nb">grep</span> <span class="s1">'inet '</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">' '</span> <span class="nt">-f</span> 2<span class="si">)</span>
<span class="nv">NAME</span><span class="o">=</span><span class="s2">"wild_exhibisionist@</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">COOKIE</span><span class="o">=</span><span class="s2">"banana_chocolate_chip_nut_butter"</span>
<span class="nb">printf</span> <span class="s2">"NAME='</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">COOKIE='</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">"</span>
iex <span class="nt">--name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--cookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span>
<span class="c"># OR</span>
erl <span class="nt">-name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-setcookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>From the localhost, <strong>ssh</strong> into the remote host and run <strong>epmd</strong>.</p>

<p><strong>sh</strong> localhost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># use your own remote host</span>
<span class="nv">REMOTE_HOST</span><span class="o">=</span>192.168.1.14
ssh <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"epmd -names"</span></code></pre></figure>

<p>With the previously started node running, the output should look something like this.</p>

<p><strong>sh</strong> localhost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">epmd: up and running on port 4369 with data:
name wild_exhibisionist at port 48341</code></pre></figure>

<p>Note the ports that <strong>empd</strong> and the node you want to debug are using.
Reconnect to the remote host with these ports forwarded.</p>

<p><strong>sh</strong> localhost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># below NODE_PORT setting assumes there is only one node running</span>
<span class="nv">EPMD_PORT</span><span class="o">=</span><span class="si">$(</span>ssh <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"epmd -names"</span> | <span class="nb">grep </span>epmd | <span class="nb">sed</span> <span class="s1">'s/[^0-9]//g'</span><span class="si">)</span>
<span class="nv">NODE_PORT</span><span class="o">=</span><span class="si">$(</span>ssh <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"epmd -names"</span> | <span class="nb">grep </span>name | <span class="nb">sed</span> <span class="s1">'s/[^0-9]//g'</span><span class="si">)</span>
<span class="nb">printf</span> <span class="s2">"REMOTE_HOST='</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">EPMD_PORT='</span><span class="k">${</span><span class="nv">EPMD_PORT</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">NODE_PORT='</span><span class="k">${</span><span class="nv">NODE_PORT</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">"</span>
ssh <span class="nt">-L</span> <span class="s2">"</span><span class="k">${</span><span class="nv">EPMD_PORT</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">EPMD_PORT</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-L</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NODE_PORT</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">NODE_PORT</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_HOST</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>Start a hidden node running the observer app.</p>

<p><strong>sh</strong> localhost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># both interfaces probably need to be on the same network</span>
<span class="nv">IF</span><span class="o">=</span>en0
<span class="nv">HOST</span><span class="o">=</span><span class="si">$(</span>ifconfig <span class="k">${</span><span class="nv">IF</span><span class="k">}</span> | <span class="nb">grep</span> <span class="s1">'inet '</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">' '</span> <span class="nt">-f</span> 2<span class="si">)</span>
<span class="nv">NAME</span><span class="o">=</span><span class="s2">"observer@</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">"</span>
<span class="c"># use the same cookie value</span>
<span class="nv">COOKIE</span><span class="o">=</span><span class="s2">"banana_chocolate_chip_nut_butter"</span>
<span class="nb">printf</span> <span class="s2">"NAME='</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">COOKIE='</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">'</span><span class="se">\n</span><span class="s2">"</span>
iex <span class="nt">--name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--cookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--hidden</span> <span class="nt">-e</span> <span class="s2">":observer.start"</span>
<span class="c"># OR</span>
erl <span class="nt">-name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-setcookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-hidden</span> <span class="nt">-run</span> observer</code></pre></figure>

<p>In <em>observer</em>, go to <em>Nodes</em> -&gt; <em>Connect Node</em> and enter the name of the remote node.</p>

<p>If you are having trouble connecting, try to manually connect
the nodes from the <strong>iex</strong> or <strong>erl</strong> prompt.
If the nodes do not connect manually, solve that problem.
After the nodes have been manually connected, go to <em>Nodes</em> -&gt; <em>Connect Node</em> and
enter the name of the remote node in <em>observer</em>.</p>

<p><strong>iex</strong> localhost</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="no">Node</span><span class="o">.</span><span class="n">connect</span> <span class="ss">:"wild_exhibisionist@192.168.1.14"</span></code></pre></figure>

<p><strong>iex</strong> remotehost</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="no">Node</span><span class="o">.</span><span class="n">connect</span> <span class="ss">:"observer@192.168.1.2"</span></code></pre></figure>

<p><strong>erl</strong> localhost</p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="nn">net_kernel</span><span class="p">:</span><span class="nf">connect</span><span class="p">(</span><span class="n">'wild_exhibisionist@192.168.1.14'</span><span class="p">).</span></code></pre></figure>

<p><strong>erl</strong> remotehost</p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="nn">net_kernel</span><span class="p">:</span><span class="nf">connect</span><span class="p">(</span><span class="n">'observer@192.168.1.2'</span><span class="p">).</span></code></pre></figure>

<p>Use the following to get a remote shell.</p>

<p><strong>sh</strong> localhost</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">REMOTE_NODE</span><span class="o">=</span><span class="s2">"wild_exhibisionist@192.168.1.14"</span>
iex <span class="nt">--name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--cookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--remsh</span> <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_NODE</span><span class="k">}</span><span class="s2">"</span>
<span class="c"># OR</span>
erl <span class="nt">-name</span> <span class="s2">"</span><span class="k">${</span><span class="nv">NAME</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-setcookie</span> <span class="s2">"</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-remsh</span> <span class="s2">"</span><span class="k">${</span><span class="nv">REMOTE_NODE</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>Note that <strong>:runtime_tools</strong> must be in the applications list to observe an app released with <em>exrm</em>.</p>

<p><strong>mix.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="k">def</span> <span class="n">application</span> <span class="k">do</span>
    <span class="p">[</span><span class="ss">mod:</span> <span class="p">{</span><span class="no">MyApp</span><span class="p">,</span> <span class="p">[]},</span>
     <span class="ss">applications:</span> <span class="p">[</span><span class="ss">:runtime_tools</span><span class="p">]]</span> <span class="c1"># add to this list</span>
  <span class="k">end</span></code></pre></figure>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://gist.github.com/pnc/9e957e17d4f9c6c81294">Erlang, Using OTP’s observer (appmon replacement) remotely</a></li>
  <li><a href="http://erlang.org/doc/man/epmd.html">Erlang, epmd</a></li>
  <li><a href="http://jjw.in/server/install-erlang-with-wxwidgets-macports">Erlang, Install erlang with wxwidgets (macports)</a></li>
  <li><a href="http://smyck.net/2014/02/28/erlang-r16-64bit-on-os-x-10-9-with-wxwidgets/">Erlang R16 64Bit on OS X 10.9 with wxWidgets</a></li>
  <li><a href="http://ebanshi.cc/questions/1453208/fixing-erlang-15b-observer-from-macports-fails-to-run">Erlang, Fixing erlang 15B Observer (from macports); fails to run</a></li>
  <li><a href="http://stackoverflow.com/questions/26474591/connecting-erlang-nodes-when-an-internal-and-external-ip-address-are-at-play">Erlang, Connecting Erlang nodes when an internal and external IP address are at play</a></li>
  <li><a href="https://github.com/elixir-lang/elixir/blob/master/bin/iex">Elixir, iex Command Line Options List</a></li>
  <li><a href="http://stackoverflow.com/questions/32449234/observer-is-not-available-elixir">Elixir, Observer is not available</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/09/multiplayer-elixir.html">Elixir, Multiplayer Elixir</a></li>
  <li><a href="http://unix.stackexchange.com/questions/14160/ssh-tunneling-error-channel-1-open-failed-administratively-prohibited-open">UNIX, SSH tunneling error: “channel 1: open failed: administratively prohibited: open failed”</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="elixir" /><category term="erlang" /><category term="observer" /></entry><entry><title type="html">Storing Elixir Release Configuration in Environment Variables with exrm</title><link href="https://sgeos.github.io/phoenix/elixir/erlang/ecto/exrm/postgresql/mysql/2016/09/11/storing-elixir-release-configuration-in-environment-variables.html" rel="alternate" type="text/html" title="Storing Elixir Release Configuration in Environment Variables with exrm" /><published>2016-09-11T18:09:35+00:00</published><updated>2016-09-11T18:09:35+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/erlang/ecto/exrm/postgresql/mysql/2016/09/11/storing-elixir-release-configuration-in-environment-variables</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/erlang/ecto/exrm/postgresql/mysql/2016/09/11/storing-elixir-release-configuration-in-environment-variables.html"><![CDATA[<!-- A43 -->
<script>console.log("A43");</script>

<p>This post covers storing settings like database configuration in environment variables.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-09-11 18:09:35 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 12.0-CURRENT <span class="c">#0 r304324: Thu Aug 18 13:27:23 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.13.0
Elixir: 1.3.2
OTP:    19.0.5
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.2.1
<span class="nv">$ </span><span class="nb">cat </span>mix.lock | <span class="nb">grep </span>exrm | <span class="nb">cut</span> <span class="nt">-d</span><span class="s2">" "</span> <span class="nt">-f</span> 3,6 | <span class="nb">sed</span> <span class="s1">'s/[",]//g'</span>
exrm: 1.0.8</code></pre></figure>

<h2 id="creating-a-sample-project">Creating a Sample Project</h2>

<p>Create a new Phoenix project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new phoenix_environment_settings <span class="nt">--no-brunch</span>
<span class="nb">cd </span>phoenix_environment_settings
mix ecto.create</code></pre></figure>

<p>Create a simple JSON memo endpoint.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.gen.json Memo memos title:string body:string</code></pre></figure>

<p>Revise <strong>web/router.ex</strong>.
Uncomment the “/api” scope and add the “/memos” route.</p>

<p><strong>web/router.ex</strong>.partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  scope <span class="s2">"/api"</span>, PhoenixEnvironmentSettings <span class="k">do
    </span>pipe_through :api

    resources <span class="s2">"/memos"</span>, MemoController, except: <span class="o">[</span>:new, :edit]
  end</code></pre></figure>

<p>Run the migration.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix ecto.migrate</code></pre></figure>

<p>Tests should pass.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix <span class="nb">test</span></code></pre></figure>

<p>Start the server.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.server</code></pre></figure>

<p>POST and GET a memo to make sure the server works.
A <a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">prior post</a> covers a shell script
for conveniently interacting with this sample app.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "New Title", "body": "This is the new memo body."}}'</span> http://localhost:4000/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos/1</code></pre></figure>

<h2 id="environment-variable-definitions">Environment Variable Definitions</h2>

<p>Define the following environment variables.
Feel free to use different values and a static value for <strong>SECRET_KEY_BASE</strong>.
Note that depending on how your database is configured,
<strong>DB_PASSWORD</strong> may be unnecessary for connections from <strong>localhost</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">NODE_NAME</span><span class="o">=</span>leaf_node
<span class="nb">export </span><span class="nv">COOKIE</span><span class="o">=</span>thin_mints
<span class="nb">export </span><span class="nv">DB_USER</span><span class="o">=</span>phoenix_environment_settings
<span class="nb">export </span><span class="nv">DB_PASSWORD</span><span class="o">=</span>phoenix_environment_settings_password
<span class="nb">export </span><span class="nv">DB_NAME</span><span class="o">=</span>phoenix_environment_settings_prod
<span class="nb">export </span><span class="nv">DB_HOST</span><span class="o">=</span>localhost
<span class="nb">export </span><span class="nv">HOST</span><span class="o">=</span>host.example.org
<span class="nb">export </span><span class="nv">PORT</span><span class="o">=</span>7777
<span class="nb">export </span><span class="nv">SECRET_KEY_BASE</span><span class="o">=</span><span class="si">$(</span>elixir <span class="nt">-e</span> <span class="s2">":crypto.strong_rand_bytes(48) |&gt; Base.encode64 |&gt; IO.puts"</span><span class="si">)</span></code></pre></figure>

<h2 id="configuring-the-application">Configuring the Application</h2>

<p>Set server to true in <strong>config/prod.exs</strong>.
Replace static configuration with dynamic configuration that
will be pulled in from environment variables.
None of this needs to be kept out version control,
so it is safe to delete <strong>config/prod.secret.exs</strong>.
Those settings have been merged in.</p>

<p><strong>config/prod.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Config</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">"PORT"</span><span class="p">}],</span>
  <span class="ss">url:</span> <span class="p">[</span><span class="ss">host:</span> <span class="s2">"${HOST}"</span><span class="p">,</span> <span class="ss">port:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">"PORT"</span><span class="p">}],</span>
  <span class="ss">cache_static_manifest:</span> <span class="s2">"priv/static/manifest.json"</span><span class="p">,</span>
  <span class="ss">server:</span> <span class="no">true</span><span class="p">,</span>
  <span class="ss">root:</span> <span class="s2">"."</span><span class="p">,</span>
  <span class="ss">version:</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Project</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="ss">:version</span><span class="p">]</span>

<span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">level:</span> <span class="ss">:info</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">secret_key_base:</span> <span class="s2">"${SECRET_KEY_BASE}"</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="s2">"${DB_USER}"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="s2">"${DB_PASSWORD}"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="s2">"${DB_NAME}"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="s2">"${DB_HOST}"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">20</span></code></pre></figure>

<p>Alternatively, keep hard coded values in <strong>config/prod.secret.exs</strong>
when running local release builds and use a version with dynamic
environment variable settings in a real production environment.</p>

<p><strong>config/prod.secret.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Config</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">secret_key_base:</span> <span class="s2">"${SECRET_KEY_BASE}"</span>

<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="s2">"${DB_USER}"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="s2">"${DB_PASSWORD}"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="s2">"${DB_NAME}"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="s2">"${DB_HOST}"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">20</span></code></pre></figure>

<p>Optionally, add dynamic configuration with a default values to <strong>config/dev.exs</strong>.
The environment variable replacement above is <strong>only</strong> suitable for releases.
The interpreted solution below is <strong>only</strong> suitable for development with <strong>mix</strong>.</p>

<p><strong>config/dev.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"PORT"</span><span class="p">)</span> <span class="o">||</span> <span class="mi">4000</span><span class="p">],</span>

<span class="c1"># ... at the end of the file ...</span>

<span class="c1"># Configure your database</span>
<span class="n">config</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">Postgres</span><span class="p">,</span>
  <span class="ss">username:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_USER"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"postgres"</span><span class="p">,</span>
  <span class="ss">password:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_PASSWORD"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"postgres"</span><span class="p">,</span>
  <span class="ss">database:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_NAME"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"phoenix_environment_settings_dev"</span><span class="p">,</span>
  <span class="ss">hostname:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"DB_HOST"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"localhost"</span><span class="p">,</span>
  <span class="ss">pool_size:</span> <span class="mi">10</span></code></pre></figure>

<h2 id="adding-release-tasks">Adding Release Tasks</h2>

<p>After deploying a release with <strong>exrm</strong>, <strong>mix</strong> is no longer available.
Define a module for release tasks with a <code class="language-plaintext highlighter-rouge">mix ecto.migrate</code> equivalent.
Note that this is an Erlang module written in Elixir.</p>

<p><strong>lib/release_tasks.ex</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="ss">:release_tasks</span> <span class="k">do</span>
  <span class="k">def</span> <span class="n">migrate</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">ensure_all_started</span><span class="p">(</span><span class="ss">:phoenix_environment_settings</span><span class="p">)</span>
    <span class="n">path</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">app_dir</span><span class="p">(</span><span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="s2">"priv/repo/migrations"</span><span class="p">)</span>
    <span class="no">Ecto</span><span class="o">.</span><span class="no">Migrator</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="no">PhoenixEnvironmentSettings</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="ss">:up</span><span class="p">,</span> <span class="ss">all:</span> <span class="no">true</span><span class="p">)</span>
    <span class="ss">:init</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>After generating a release, this task can be run as follows.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings <span class="nb">command </span>release_tasks migrate</code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">mix ecto.create</code> and <code class="language-plaintext highlighter-rouge">mix ecto.drop</code> tasks are run less frequently
so it probably just makes sense to just manually create or drop the database
and user.  PostgreSQL commands follow.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># PostgreSQL `mix ecto.create` equivalent</span>
psql <span class="nt">-c</span> <span class="s2">"CREATE USER </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2"> WITH PASSWORD '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
createdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span>
psql <span class="nt">-c</span> <span class="s2">"GRANT ALL PRIVILEGES ON DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2"> to </span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">;"</span>

<span class="c"># PostgreSQL `mix ecto.drop` equivalent</span>
dropdb <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span>
dropuser <span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span>

<span class="c"># PostgresSQL interactive terminal</span>
<span class="nv">PGPASSWORD</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">"</span> psql <span class="nt">-U</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>The MySQL commands look like this.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># MySQL `mix ecto.create` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
mysql <span class="nt">-e</span> <span class="s2">"CREATE DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span>
mysql <span class="nt">-e</span> <span class="s2">"GRANT ALL PRIVILEGES ON </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">.* TO '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost' IDENTIFIED BY '</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">';"</span>
mysql <span class="nt">-e</span> <span class="s2">"FLUSH PRIVILEGES;"</span>

<span class="c"># MySQL `mix ecto.drop` equivalent</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP DATABASE </span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">;"</span>
mysql <span class="nt">-e</span> <span class="s2">"DROP USER '</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">'@'localhost';"</span>

<span class="c"># MySQL interactive terminal</span>
mysql <span class="nt">-u</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_USER</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-p</span><span class="s2">"</span><span class="k">${</span><span class="nv">DB_PASSWORD</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">DB_NAME</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>Run the <code class="language-plaintext highlighter-rouge">mix ecto.create</code> equivalent now.</p>

<p>Note that the password security in the above commands is less than ideal.
Also, an existing superuser and password may need to be explicily
specified when creating the new user and database.</p>

<h2 id="generating-a-release">Generating a Release</h2>

<p>Add the <a href="https://github.com/bitwalker/exrm">elixir release manager (exrm)</a> to <strong>mix.exs</strong> as a project dependency.</p>

<p><strong>mix.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[{</span><span class="ss">:phoenix</span><span class="p">,</span> <span class="s2">"~&gt; 1.2.1"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_pubsub</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_ecto</span><span class="p">,</span> <span class="s2">"~&gt; 3.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:postgrex</span><span class="p">,</span> <span class="s2">"&gt;= 0.0.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_html</span><span class="p">,</span> <span class="s2">"~&gt; 2.6"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_live_reload</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">,</span> <span class="ss">only:</span> <span class="ss">:dev</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:gettext</span><span class="p">,</span> <span class="s2">"~&gt; 0.11"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:exrm</span><span class="p">,</span> <span class="s2">"~&gt; 1.0.8"</span><span class="p">},</span> <span class="c1"># this line is new</span>
     <span class="p">{</span><span class="ss">:cowboy</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">}]</span>
  <span class="k">end</span></code></pre></figure>

<p>Install exrm and build a release.
This will create the <strong>rel/</strong> directory.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix deps.get
mix deps.compile
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix compile
<span class="c"># brunch build --production # if using brunch</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix phoenix.digest
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release</code></pre></figure>

<p>Run the migration task defined in the <strong>Adding Release Tasks</strong> section.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings <span class="nb">command </span>release_tasks migrate</code></pre></figure>

<p>Environment variables will be used as knobs to configure the app.
Note that the <strong>RELX_REPLACE_OS_VARS=true</strong> environment variable needs to
be defined to use environment variables for dynamic configuration.</p>

<p>The <strong>rel/vm.args</strong> file is primarily used to configure the erlang VM.
It can also be used to define application configuration parameters.
Application configuration parameters defined in this file can be passed
into the program as atoms or integers.
Note that the location of this file can be <a href="https://exrm.readme.io/docs/release-configuration">configured</a>
with the <strong>RELEASE_CONFIG_DIR</strong> environment variable.
Add the following to <strong>rel/vm.args</strong>.</p>

<p><strong>rel/vm.args</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">## Name of the node</span>
<span class="nt">-name</span> <span class="k">${</span><span class="nv">NODE_NAME</span><span class="k">}</span>

<span class="c">## Cookie for distributed erlang</span>
<span class="nt">-setcookie</span> <span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span>

<span class="c">## App Settings</span>
<span class="nt">-phoenix_environment_settings</span> port <span class="k">${</span><span class="nv">PORT</span><span class="k">}</span></code></pre></figure>

<p>Alternatively, <strong>rel/sys.config</strong> can be used to pass in application configuration parameters.
This file is written in Erlang.</p>

<p><strong>rel/sys.config</strong></p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="p">[</span>
  <span class="p">{</span><span class="n">phoenix_environment_settings</span><span class="p">,</span> <span class="p">[</span>
    <span class="p">{</span><span class="n">port</span><span class="p">,</span> <span class="s">"${PORT}"</span><span class="p">}</span>
  <span class="p">]}</span>
<span class="p">].</span></code></pre></figure>

<p>The Elixir <strong>config/config.exs</strong> file is probably a better place to
define non-VM settings for an Elixir application.
It is ultimately merged with <strong>rel/sys.config</strong>.
The exact settings for this project were covered in
the <strong>Configuring the Application</strong> section above.</p>

<p>Rebuild the release with the configuration files.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">MIX_ENV</span><span class="o">=</span>prod mix compile
<span class="c"># brunch build --production # if using brunch</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix phoenix.digest
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release</code></pre></figure>

<p>Start the release in the console.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span>rel/phoenix_environment_settings/bin/phoenix_environment_settings console</code></pre></figure>

<p>Make sure the server responds.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Memo A", "body": "Alpha memo body."}}'</span> <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Memo B", "body": "Beta memo body."}}'</span> <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span>
curl <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">/api/memos"</span></code></pre></figure>

<p>Exit the console with ^C.</p>

<h2 id="custom-application-settings">Custom Application Settings</h2>

<p>Add this <a href="https://gist.github.com/bitwalker/a4f73b33aea43951fe19b242d06da7b9">configuration gist</a> to <strong>lib/config.ex</strong>.
This <a href="https://hexdocs.pm/distillery/runtime-configuration.html">configuration wrapper</a> allows the same convenient
<code class="language-plaintext highlighter-rouge">{:system, "VARIABLE", "default"}</code> convention to be used with both <strong>mix</strong> and releases.
Note that this will not help configure things like <strong>PhoenixEnvironmentSettings.Repo</strong>
because they were not written to get settings via this module.
The linked to version has the typespecs and documentation.</p>

<p><strong>lib/config.ex</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">defmodule Config <span class="k">do
  </span>def get<span class="o">(</span>app, key, default <span class="se">\\</span> nil<span class="o">)</span> when is_atom<span class="o">(</span>app<span class="o">)</span> and is_atom<span class="o">(</span>key<span class="o">)</span> <span class="k">do
    case</span> Application.get_env<span class="o">(</span>app, key<span class="p">)</span> <span class="k">do</span>
      <span class="o">{</span>:system, env_var<span class="o">}</span> -&gt;
        <span class="k">case</span> System.get_env<span class="o">(</span>env_var<span class="p">)</span> <span class="k">do
          </span>nil -&gt; default
          val -&gt; val
        end
      <span class="o">{</span>:system, env_var, preconfigured_default<span class="o">}</span> -&gt;
        <span class="k">case</span> System.get_env<span class="o">(</span>env_var<span class="p">)</span> <span class="k">do
          </span>nil -&gt; preconfigured_default
          val -&gt; val
        end
      nil -&gt;
        default
      val -&gt;
        val
    end
  end

  def get_integer<span class="o">(</span>app, key, default <span class="se">\\</span> nil<span class="o">)</span> <span class="k">do
    case</span> get<span class="o">(</span>app, key, nil<span class="p">)</span> <span class="k">do
      </span>nil -&gt; default
      n when is_integer<span class="o">(</span>n<span class="o">)</span> -&gt; n
      n -&gt;
        <span class="k">case</span> Integer.parse<span class="o">(</span>n<span class="p">)</span> <span class="k">do</span>
          <span class="o">{</span>i, _<span class="o">}</span> -&gt; i
          :error -&gt; default
        end
    end
  end
end</code></pre></figure>

<p>Add these lines to <strong>config/config.exs</strong>.</p>

<p><strong>config/config.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">config :phoenix_environment_settings,
  ecto_repos: <span class="o">[</span>PhoenixEnvironmentSettings.Repo],
  welcome_message: <span class="o">{</span>:system, <span class="s2">"WELCOME_MESSAGE"</span>, <span class="s2">"Hello, world!"</span><span class="o">}</span>,
  magic_number: <span class="o">{</span>:system, <span class="s2">"MAGIC_NUMBER"</span>, 42<span class="o">}</span></code></pre></figure>

<p>Access the settings in <code class="language-plaintext highlighter-rouge">iex -S mix</code> like this.
You should get the default values.</p>

<p><strong>iex -S mix</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="no">Config</span><span class="o">.</span><span class="n">get</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="ss">:welcome_message</span>
<span class="no">Config</span><span class="o">.</span><span class="n">get_integer</span> <span class="ss">:phoenix_environment_settings</span><span class="p">,</span> <span class="ss">:magic_number</span></code></pre></figure>

<p>Define the environment variables and run the commands in <strong>iex</strong> again.
You should get the environment variable values this time.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">WELCOME_MESSAGE</span><span class="o">=</span><span class="s2">"Welcome. Try, but there is no escape."</span>
<span class="nb">export </span><span class="nv">MAGIC_NUMBER</span><span class="o">=</span><span class="nt">-1</span></code></pre></figure>

<p>This is useful for defining environment variable knobs to control run time behavior.
It is not a solution for problems that rely on compile time behavior,
like using environment variables to specify <a href="https://gist.github.com/chrismccord/e0eaefe30d2ecd85b4ac">dynamic routes</a>.</p>

<h2 id="other-considerations">Other Considerations</h2>

<p>Note that when using <strong>RELX_REPLACE_OS_VARS=true</strong>, the environment variables in <strong>rel/sys.conf</strong> and
<strong>config/config.exs</strong> will always be replaced with strings.
The following almost certainly does not work as expected.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">export </span><span class="nv">DB_POOL_SIZE</span><span class="o">=</span>20</code></pre></figure>

<p><strong>config/prod.exs</strong> or
<strong>config/prod.secret.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  pool_size: <span class="s2">"</span><span class="k">${</span><span class="nv">DB_POOL_SIZE</span><span class="k">}</span><span class="s2">"</span></code></pre></figure>

<p>The following will work in development, but not production.</p>

<p><strong>config/dev.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">  pool_size: <span class="o">(</span>System.get_env<span class="o">(</span><span class="s2">"DB_POOL_SIZE"</span><span class="o">)</span> <span class="o">||</span> <span class="s2">"10"</span><span class="o">)</span> |&gt; String.to_integer</code></pre></figure>

<p>If integers or atoms need to be passed in directly, use <strong>vm.args</strong>.
The author could not figure out how to pass <strong>DB_POOL_SIZE</strong> to Repo via <strong>vm.args</strong>.</p>

<h2 id="next-steps">Next Steps</h2>

<p>Consider looking into <a href="https://github.com/bitwalker/distillery">distillery</a>, the “new exrm” written in pure Elixir.
Also consider looking into <a href="https://github.com/bitwalker/conform">conform</a>, a library for working with init-style configuration.
For deployment, <a href="https://github.com/boldpoker/edeliver">edeliver</a> is worth looking at.</p>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://github.com/bitwalker/exrm">Elixir Release Manager (exrm)</a></li>
  <li><a href="https://exrm.readme.io/docs/release-configuration">Elixir Release Manager Configuration</a></li>
  <li><a href="http://blog.plataformatec.com.br/2016/05/how-to-config-environment-variables-with-elixir-and-exrm/">Elixir Release Manager, How to config environment variables with Elixir and Exrm</a></li>
  <li><a href="http://blog.plataformatec.com.br/2016/04/running-migration-in-an-exrm-release/">Elixir Release Manager, Running migration in an Exrm release</a></li>
  <li><a href="https://gist.github.com/antipax/90cc36d29c2a2a5d4629">Elixir Release Manager, How to run Ecto migrations from an exrm release</a></li>
  <li><a href="https://gist.github.com/bitwalker/a4f73b33aea43951fe19b242d06da7b9">Elixir, Useful Config Wrapper Gist</a></li>
  <li><a href="http://sheldonkreger.com/understanding-config-in-elixir.html">Elixir, Understanding Config in Elixir</a></li>
  <li><a href="http://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD</a></li>
  <li><a href="https://github.com/bitwalker/conform">Elixir, conform</a></li>
  <li><a href="https://github.com/boldpoker/edeliver">Elixir, edeliver</a></li>
  <li><a href="http://erlang.org/doc/man/erl.html">Erlang, man erl</a></li>
  <li><a href="http://erlang.org/doc/man/config.html">Erlang, man config</a></li>
  <li><a href="https://gist.github.com/chrismccord/e0eaefe30d2ecd85b4ac">Phoenix, Dynamic Dispatch Gist</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">Phoenix, A Shell Script for Working with Phoenix JSON APIs</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/erlang/freebsd/2016/03/21/phoenix-as-a-service-on-freebsd.html">Phoenix as a Service on FreeBSD</a></li>
  <li><a href="https://github.com/bitwalker/exrm/issues/67#issuecomment-183457937">Mix, Accessing Mix tasks from release</a></li>
  <li><a href="https://gist.github.com/sgeos/1fed5eb24d80b97a338249dd95d55082">Mix, exrm PostgreSQL/MySQL release equivalents for <code class="language-plaintext highlighter-rouge">mix ecto.create</code> and <code class="language-plaintext highlighter-rouge">mix ecto.drop</code>.</a></li>
  <li><a href="https://github.com/bitwalker/distillery">Distillery</a></li>
  <li><a href="https://hexdocs.pm/distillery/runtime-configuration.html">Distillery, Runtime Configuration</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="erlang" /><category term="ecto" /><category term="exrm" /><category term="postgresql" /><category term="mysql" /></entry><entry><title type="html">Phoenix/Elixir, Adding prod.secret.exs.example to a git Repository</title><link href="https://sgeos.github.io/phoenix/elixir/git/2016/07/18/phoenix-adding-prod-secret-exs-example-to-git-repository.html" rel="alternate" type="text/html" title="Phoenix/Elixir, Adding prod.secret.exs.example to a git Repository" /><published>2016-07-18T02:18:42+00:00</published><updated>2016-07-18T02:18:42+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/git/2016/07/18/phoenix-adding-prod-secret-exs-example-to-git-repository</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/git/2016/07/18/phoenix-adding-prod-secret-exs-example-to-git-repository.html"><![CDATA[<!-- A38 -->
<script>console.log("A38");</script>

<p><strong>prod.secret.exs</strong> is not added to the git repository by default.
This is a good thing.
The problem is that when a repository is cloned, <strong>prod.secret.exs</strong> is missing.
The file can usually be copied over, but this is a hassle when possible.
I’m sure there are people out there who can type up a new <strong>prod.secret.exs</strong> from scratch.
I want an easier path.</p>

<p>This post covers adding a <strong>prod.secret.exs.example</strong> file to a project.
This file can be used to regenerate <strong>prod.secret.exs</strong> with a new
<strong>secret_key_base</strong>.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-07-18 02:18:42 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-ALPHA6 <span class="c">#0 r302384: Thu Jul  7 22:40:47 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.12.1
Elixir: 1.3.2
OTP:    18.3.4.1

Built with: Elixir 1.2.5 and OTP 18.3.3
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.1.4</code></pre></figure>

<h2 id="instructions">Instructions</h2>

<p>Create a new project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new memo_api
<span class="nb">cd </span>memo_api</code></pre></figure>

<p>Create a git repository and add the project to it.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"Initial commit."</span></code></pre></figure>

<p>Copy <strong>config/prod.secret.exs</strong> to <strong>config/prod.secret.exs.example</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">cp </span>config/prod.secret.exs config/prod.secret.exs.example</code></pre></figure>

<p>Change the <strong>secret_key_base</strong> to a value that is not sensitive.
It should be a value that can easily be replaced by <strong>sed</strong>.</p>

<p><strong>config/prod.secret.exs.example</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1"># Regenerate config/prod.secret.exs with the following commands</span>
<span class="c1">#   $ SECRET_KEY_BASE=$(elixir -e ":crypto.strong_rand_bytes(48) |&gt; Base.encode64 |&gt; IO.puts")</span>
<span class="c1">#   $ sed "s|SECRET+KEY+BASE|$SECRET_KEY_BASE|" config/prod.secret.exs.example &gt;config/prod.secret.exs</span>
<span class="n">config</span> <span class="ss">:memo_api</span><span class="p">,</span> <span class="no">MemoApi</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">secret_key_base:</span> <span class="s2">"SECRET+KEY+BASE"</span></code></pre></figure>

<p>Add <strong>config/prod.secret.exs.example</strong> to git.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">git add config/prod.secret.exs.example
git commit <span class="nt">-m</span> <span class="s2">"Added config/prod.secret.exs.example."</span></code></pre></figure>

<p><strong>config/prod.secret.exs</strong> can be regenerated with the following commands</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">SECRET_KEY_BASE</span><span class="o">=</span><span class="si">$(</span>elixir <span class="nt">-e</span> <span class="s2">":crypto.strong_rand_bytes(48) |&gt; Base.encode64 |&gt; IO.puts"</span><span class="si">)</span>
<span class="nb">sed</span> <span class="s2">"s|SECRET+KEY+BASE|</span><span class="nv">$SECRET_KEY_BASE</span><span class="s2">|"</span> config/prod.secret.exs.example <span class="o">&gt;</span>config/prod.secret.exs</code></pre></figure>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://github.com/ohr486/tokyoex_handson_demo/tree/master/config">Github, ohr486/tokyoex_handson_demo/config</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="git" /></entry><entry><title type="html">Phoenix as a Service on FreeBSD</title><link href="https://sgeos.github.io/phoenix/elixir/erlang/freebsd/2016/03/21/phoenix-as-a-service-on-freebsd.html" rel="alternate" type="text/html" title="Phoenix as a Service on FreeBSD" /><published>2016-03-21T15:48:51+00:00</published><updated>2016-03-21T15:48:51+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/erlang/freebsd/2016/03/21/phoenix-as-a-service-on-freebsd</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/erlang/freebsd/2016/03/21/phoenix-as-a-service-on-freebsd.html"><![CDATA[<!-- A29 -->
<script>console.log("A29");</script>

<p>In this post a Phoenix app will be installed as a service on FreeBSD.
The goals are the same as the <a href="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD post</a>.</p>

<ul>
  <li>The app needs to be able to be rebuilt and the service
restarted to reflect changes on a development machine.</li>
  <li>The service needs to automatically start when the machine is booted.</li>
  <li>The service sould work like any other service.</li>
</ul>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service phoenix_service start
service phoenix_service stop
service phoenix_service restart
service phoenix_service status</code></pre></figure>

<p>This post assumes Phoenix, Elixir and PostgreSQL are <a href="https://sgeos.github.io/phoenix/elixir/postgresql/freebsd/2016/03/19/installing-phoenix-elixir-and-postgresql-on-freebsd.html">already installed on FreeBSD</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-03-21 15:48:51 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-CURRENT <span class="c">#0 r296925: Wed Mar 16 20:53:04 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.11.3
Elixir: 1.2.3
OTP:    18.2.4
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.1.4</code></pre></figure>

<h2 id="creating-a-sample-project">Creating a Sample Project</h2>

<p>Create a new Phoenix project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new phoenix_service <span class="nt">--no-brunch</span>
<span class="nb">cd </span>phoenix_service
mix ecto.create</code></pre></figure>

<p>This sample project needs to be simple.
Let’s make a JSON memo service.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.gen.json Memo memos title:string body:string</code></pre></figure>

<p>Revise the <strong>web/router.ex</strong> file.
The “/api” scope needs to be uncommented and the “/memos” route needs to be added.</p>

<p><strong>web/router.ex file</strong></p>

<figure class="highlight"><pre><code class="language-elixer" data-lang="elixer">defmodule PhoenixService.Router do
  use PhoenixService.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", PhoenixService do
    pipe_through :browser # Use the default browser stack
  
    get "/", PageController, :index
  end

  scope "/api", PhoenixService do
    pipe_through :api

    resources "/memos", MemoController, except: [:new, :edit]
  end
end</code></pre></figure>

<p>Run the migration.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix ecto.migrate</code></pre></figure>

<p>Set server to true in <strong>config/prod.exs</strong>.
Also make sure a dynamic port configuration is used.</p>

<p><strong>config/prod.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="n">config</span> <span class="ss">:phoenix_service</span><span class="p">,</span> <span class="no">PhoenixService</span><span class="o">.</span><span class="no">Endpoint</span><span class="p">,</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="p">{</span><span class="ss">:system</span><span class="p">,</span> <span class="s2">"PORT"</span><span class="p">}],</span> <span class="c1"># dynamic port configuration</span>
  <span class="ss">url:</span> <span class="p">[</span><span class="ss">host:</span> <span class="s2">"example.com"</span><span class="p">,</span> <span class="ss">port:</span> <span class="mi">80</span><span class="p">],</span>
  <span class="ss">cache_static_manifest:</span> <span class="s2">"priv/static/manifest.json"</span><span class="p">,</span> <span class="c1"># added comma</span>
  <span class="ss">server:</span> <span class="no">true</span> <span class="c1"># this line is new</span></code></pre></figure>

<p>Optionally, add a dynamic port configuration with a default value to <strong>config/dev.exs</strong>.
The above build time solution is suitable for releases.
The interpreted solution below is suitable for development.</p>

<p><strong>config/dev.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="c1"># http: [port: 4000], # old line 10</span>
  <span class="ss">http:</span> <span class="p">[</span><span class="ss">port:</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"PORT"</span><span class="p">)</span> <span class="o">||</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:phoenix_service</span><span class="p">,</span> <span class="ss">:port</span><span class="p">)</span> <span class="o">||</span> <span class="mi">4000</span><span class="p">],</span></code></pre></figure>

<p>Tests should pass</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix <span class="nb">test</span></code></pre></figure>

<p>Start the server.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.server</code></pre></figure>

<p>POST and GET a memo to make sure the server works.
A <a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">prior post</a> covers a shell script for conveniently
interacting with this sample app.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "New Title", "body": "This is the new memo body."}}'</span> http://localhost:4000/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos/1</code></pre></figure>

<h2 id="generating-a-release">Generating a Release</h2>

<p>Now that the Phoenix app is working, it is time to build a release.
Add the <a href="https://github.com/bitwalker/exrm">elixir release manager (exrm)</a> to <strong>mix.exs</strong> as a project dependency.</p>

<p><strong>mix.exs</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[{</span><span class="ss">:phoenix</span><span class="p">,</span> <span class="s2">"~&gt; 1.1.4"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:postgrex</span><span class="p">,</span> <span class="s2">"&gt;= 0.0.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_ecto</span><span class="p">,</span> <span class="s2">"~&gt; 2.0"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_html</span><span class="p">,</span> <span class="s2">"~&gt; 2.4"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:phoenix_live_reload</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">,</span> <span class="ss">only:</span> <span class="ss">:dev</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:gettext</span><span class="p">,</span> <span class="s2">"~&gt; 0.9"</span><span class="p">},</span>
     <span class="p">{</span><span class="ss">:exrm</span><span class="p">,</span> <span class="s2">"~&gt; 1.0.2"</span><span class="p">},</span> <span class="c1"># this line is new</span>
     <span class="p">{</span><span class="ss">:cowboy</span><span class="p">,</span> <span class="s2">"~&gt; 1.0"</span><span class="p">}]</span>
  <span class="k">end</span></code></pre></figure>

<p>Install exrm and build a release.
This will create the <strong>rel/</strong> directory.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix deps.get
mix deps.compile
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix ecto.create
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix ecto.migrate
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix compile
<span class="c"># brunch build --production # if using brunch</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix phoenix.digest
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release</code></pre></figure>

<p>The rc script will use environment variable knobs to configure the app.
Note that the <strong>RELX_REPLACE_OS_VARS=true</strong> environment variable needs
to be defined to use environment variables for dynamic configuration.</p>

<p>The <strong>vm.args</strong> file is primarily used to configure the erlang VM.
It can also be used to define application configure parameters.
Application configuration parameters defined in this file can be
passed into the program as atoms or integers.
Note that the location of this file can be <a href="https://exrm.readme.io/docs/release-configuration">configured</a>
with the <strong>RELEASE_CONFIG_DIR</strong> environment variable.
Add the following to <strong>rel/vm.args</strong>.</p>

<p><strong>rel/vm.args</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">## Name of the node</span>
<span class="nt">-name</span> <span class="k">${</span><span class="nv">NODE_NAME</span><span class="k">}</span>

<span class="c">## Cookie for distributed erlang</span>
<span class="nt">-setcookie</span> <span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span>

<span class="c">## App Settings</span>
<span class="nt">-phoenix_service</span> port <span class="k">${</span><span class="nv">PORT</span><span class="k">}</span></code></pre></figure>

<p>Alternatively, <strong>sys.config</strong> can be used to pass in
application configuration parameters.
In this file, application configuration parameters
defined with environment variables must be strings.
Pass the port setting in as above or add the following
to <strong>rel/sys.config</strong>.
The app module should work with either solution.
Adding both files will not break anything.
Note that <strong>rel/sys.config</strong> is written in Erlang.</p>

<p><strong>rel/sys.config</strong></p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="p">[</span>
  <span class="p">{</span><span class="n">phoenix_service</span><span class="p">,</span> <span class="p">[</span>
    <span class="p">{</span><span class="n">port</span><span class="p">,</span> <span class="s">"${PORT}"</span><span class="p">}</span>
  <span class="p">]}</span>
<span class="p">].</span></code></pre></figure>

<p>Rebuild the release with the configuration files.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release</code></pre></figure>

<p>Start the release in the console.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true </span><span class="nv">PORT</span><span class="o">=</span>7777 rel/phoenix_service/bin/phoenix_service console</code></pre></figure>

<p>Make sure the server responds.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">curl http://localhost:7777/api/memos</code></pre></figure>

<p>Exit the console with ^C.</p>

<h2 id="installing-the-release-as-a-service">Installing the Release as a Service</h2>

<p>Now that the release is working, it is time to set it up as a service.
Perform the initial install.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">su
sh
<span class="nv">PROJECT</span><span class="o">=</span>phoenix_service
<span class="nv">INSTALL_DIR</span><span class="o">=</span>/usr/local/opt
<span class="nv">VERSION</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat </span>rel/<span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nv">INSTALL_TAR</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/rel/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.tar.gz
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>
<span class="o">(</span><span class="nb">cd</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span><span class="p">;</span> <span class="nb">tar</span> <span class="nt">-xf</span> <span class="nv">$INSTALL_TAR</span><span class="o">)</span>
pw adduser <span class="nv">$PROJECT</span> <span class="nt">-d</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span> <span class="nt">-s</span> /usr/sbin/nologin <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$PROJECT</span><span class="s2"> system service user"</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span></code></pre></figure>

<p>An rc script defines the the service.</p>

<ul>
  <li><strong>phoenix_service_run()</strong> is called from the other functions.
It configures and calls the release.
<strong>HOME</strong> is set to the installation directory to force the
erlang cookie file to be written there regardless of the
<strong>phoenix_service_user</strong> setting.</li>
  <li><strong>phoenix_service_status()</strong> echoes a user friendly
message if the release can be pinged.</li>
  <li><strong>extra_commands</strong> is used to add the <strong>console</strong> and
<strong>remote_console</strong> commands.
<strong>console</strong> is used to start a new service and attach a console to it.
<strong>remote_console()</strong> is used to connect a console to the service
if it is already running.</li>
  <li>Add <strong>shutdown</strong> to the keyword list if the service needs to
gracefull shutdown when the machine restarts.</li>
  <li>The rest is standard rc configuration.</li>
</ul>

<p>Add following to <strong>/usr/local/etc/rc.d/phoenix_service</strong></p>

<p><strong>/usr/local/etc/rc.d/phoenix_service</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>
<span class="c">#</span>
<span class="c"># PROVIDE: phoenix_service</span>
<span class="c"># REQUIRE: networking</span>
<span class="c"># KEYWORD:</span>
 
<span class="nb">.</span> /etc/rc.subr
 
<span class="nv">name</span><span class="o">=</span><span class="s2">"phoenix_service"</span>
<span class="nv">rcvar</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_enable"</span>
<span class="nv">install_dir</span><span class="o">=</span><span class="s2">"/usr/local/opt/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">version</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nb">command</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">/bin/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span>
 
<span class="nv">start_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_start"</span>
<span class="nv">stop_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_stop"</span>
<span class="nv">status_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_status"</span>
<span class="nv">console_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_console"</span>
<span class="nv">remote_console_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_remote_console"</span>
<span class="nv">extra_commands</span><span class="o">=</span><span class="s2">"console remote_console"</span>

load_rc_config <span class="nv">$name</span>
: <span class="k">${</span><span class="nv">phoenix_service_enable</span>:<span class="p">=</span><span class="s2">"no"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">phoenix_service_port</span>:<span class="p">=</span><span class="s2">"4000"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">phoenix_service_user</span>:<span class="p">=</span><span class="k">${</span><span class="nv">name</span><span class="k">}}</span>
: <span class="k">${</span><span class="nv">phoenix_service_node_name</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">@127.0.0.1"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">phoenix_service_cookie</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">phoenix_service_config_dir</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">/releases/</span><span class="k">${</span><span class="nv">version</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2"> start"</span><span class="k">}</span>

phoenix_service_run<span class="o">()</span>
<span class="o">{</span>
  <span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
  <span class="nv">HOME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">RELEASE_CONFIG_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">phoenix_service_config</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">NODE_NAME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">phoenix_service_node_name</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">COOKIE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">phoenix_service_cookie</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">PORT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">phoenix_service_port</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  su <span class="nt">-m</span> <span class="s2">"</span><span class="nv">$phoenix_service_user</span><span class="s2">"</span> <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$command</span><span class="s2"> </span><span class="nv">$1</span><span class="s2">"</span>
<span class="o">}</span>

phoenix_service_start<span class="o">()</span>
<span class="o">{</span>
  phoenix_service_run start
<span class="o">}</span>

phoenix_service_stop<span class="o">()</span>
<span class="o">{</span>
  phoenix_service_run stop
<span class="o">}</span>

phoenix_service_status<span class="o">()</span>
<span class="o">{</span>
  <span class="nv">ping_result</span><span class="o">=</span><span class="sb">`</span>phoenix_service_run ping<span class="sb">`</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ping_result</span><span class="k">}</span><span class="s2">"</span>
  <span class="k">case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ping_result</span><span class="k">}</span><span class="s2">"</span> <span class="k">in</span>
    <span class="k">*</span>pong<span class="k">*</span><span class="p">)</span>
      <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2"> is running."</span>
      <span class="p">;;</span>
  <span class="k">esac</span>
<span class="o">}</span>

phoenix_service_console<span class="o">()</span>
<span class="o">{</span>
  phoenix_service_run console
<span class="o">}</span>

phoenix_service_remote_console<span class="o">()</span>
<span class="o">{</span>
  phoenix_service_run remote_console
<span class="o">}</span>

load_rc_config <span class="nv">$name</span>
run_rc_command <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span></code></pre></figure>

<p>Make <strong>/usr/local/etc/rc.d/phoenix_service</strong> read only and executable.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod </span>555 /usr/local/etc/rc.d/phoenix_service</code></pre></figure>

<p>Enable and configure the service in <strong>/etc/rc.conf</strong>.</p>

<p><strong>/etc/rc.conf</strong> lines to add</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">phoenix_service_enable</span><span class="o">=</span><span class="s2">"YES"</span>
<span class="nv">phoenix_service_port</span><span class="o">=</span>8248
<span class="nv">phoenix_service_node_name</span><span class="o">=</span><span class="s2">"suzaku"</span>
<span class="nv">phoenix_service_cookie</span><span class="o">=</span><span class="s2">"cookie-of-the-southern-flame"</span></code></pre></figure>

<p>The service can now be started.  If the service is enabled, it will automatically start when the machine boots.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service phoenix_service start
<span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Service Running", "body": "The Phoenix service is running on '</span><span class="s2">"</span><span class="si">$(</span><span class="nb">hostname</span><span class="si">)</span><span class="s2">"</span><span class="s1">'."}}'</span> http://localhost:8248/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:8248/api/memos/1</code></pre></figure>

<h3 id="optional-adding-a-release-to-the-systemwide-path">Optional: Adding a Release to the Systemwide Path</h3>

<p>Adding a release to the systemwide path is not necessary, but it can be convenient.
Create a directory for the convenience pass through script.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/bin</code></pre></figure>

<p><strong>PORT</strong>, <strong>NODE_NAME</strong> and <strong>COOKIE</strong> need default values because <strong>vm.args</strong> has no useful default fallbacks.
There is no good way to automatically select a port, so 8080 is a hard coded default.
Add the following script to <strong>/usr/local/opt/bin/phoenix_service</strong>.</p>

<p><strong>/usr/local/opt/bin/phoenix_service</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>

<span class="nv">SCRIPT</span><span class="o">=</span><span class="si">$(</span><span class="nb">realpath</span> <span class="nv">$0</span><span class="si">)</span>
<span class="nv">BASENAME</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$SCRIPT</span><span class="si">)</span>
<span class="nv">BASEDIR</span><span class="o">=</span><span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$SCRIPT</span><span class="si">)</span>
<span class="nv">COMMAND</span><span class="o">=</span><span class="si">$(</span><span class="nb">realpath</span> <span class="nv">$BASEDIR</span>/../<span class="nv">$BASENAME</span>/bin/<span class="nv">$BASENAME</span><span class="si">)</span>

: <span class="k">${</span><span class="nv">NODE_NAME</span>:<span class="p">=</span><span class="k">${</span><span class="nv">BASENAME</span><span class="k">}}</span>
: <span class="k">${</span><span class="nv">COOKIE</span>:<span class="p">=</span><span class="k">${</span><span class="nv">BASENAME</span><span class="k">}}</span>
: <span class="k">${</span><span class="nv">PORT</span>:<span class="p">=</span><span class="s2">"8080"</span><span class="k">}</span>

<span class="nv">NODE_NAME</span><span class="o">=</span><span class="k">${</span><span class="nv">NODE_NAME</span><span class="k">}</span> <span class="se">\</span>
<span class="nv">COOKIE</span><span class="o">=</span><span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span> <span class="se">\</span>
<span class="nv">PORT</span><span class="o">=</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span> <span class="se">\</span>
<span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
<span class="nv">$COMMAND</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span></code></pre></figure>

<p>Make the script executable.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x <span class="nv">$INSTALL_DIR</span>/bin/<span class="nv">$PROJECT</span></code></pre></figure>

<p>Add <strong>/usr/local/opt/bin</strong> to the global path in <strong>/etc/profile</strong> for <strong>sh</strong>,
and <strong>/etc/csh.cshrc</strong> for <strong>csh</strong>.
Consider updating the root path in <strong>/root/.cshrc</strong>.</p>

<p><strong>/usr/local/opt/bin</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">PATH</span><span class="o">=</span>/usr/local/opt/bin:<span class="nv">$PATH</span>
<span class="nb">export </span>PATH</code></pre></figure>

<p><strong>/etc/csh.cshrc</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-csh" data-lang="csh">set path=(/usr/local/opt/bin $path)</code></pre></figure>

<p><strong>/root/.cshrc</strong> partial listing</p>

<figure class="highlight"><pre><code class="language-csh" data-lang="csh">set path = (/usr/local/opt/bin /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin $HOME/bin)</code></pre></figure>

<p>Update the path in the current shell if necessary.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># sh bash</span>
<span class="nb">source</span> /etc/csh.cshrc
<span class="c"># csh tcsh</span>
<span class="nb">.</span> /etc/profile</code></pre></figure>

<p>Fix permissions if you want to be able to run as any user.
This may have security implications.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/bin/<span class="nv">$PROJECT</span>
<span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/bin/nodetool
<span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.sh
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log/<span class="k">*</span>.<span class="k">*</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/tmp/erl_pipes/<span class="nv">$PROJECT</span>
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/tmp/erl_pipes/<span class="nv">$PROJECT</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/
<span class="nb">chmod </span>666 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/<span class="k">*</span>.<span class="k">*</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span></code></pre></figure>

<p>The release can now be conveniently controlled.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># start service</span>
<span class="nv">NODE_NAME</span><span class="o">=</span>rudra <span class="nv">COOKIE</span><span class="o">=</span>treasure <span class="nv">PORT</span><span class="o">=</span>5678 phoenix_service start
<span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Service Running", "body": "The Phoenix service is running on '</span><span class="s2">"</span><span class="si">$(</span><span class="nb">hostname</span><span class="si">)</span><span class="s2">"</span><span class="s1">'."}}'</span> http://localhost:5678/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:5678/api/memos/1
<span class="c"># stop service</span>
<span class="nv">NODE_NAME</span><span class="o">=</span>rudra <span class="nv">COOKIE</span><span class="o">=</span>treasure phoenix_service stop</code></pre></figure>

<p>Setup complete.  Switch from root to a normal user.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">exit</span> <span class="c"># sh</span>
<span class="nb">exit</span> <span class="c"># su</span></code></pre></figure>

<h3 id="updating">Updating</h3>

<p>Casual updates on a development machine can be performed as follows.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># brunch build --production # if using brunch</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix phoenix.digest <span class="c"># if static assets could have changed</span>
<span class="nv">MIX_ENV</span><span class="o">=</span>prod mix release
su
sh
<span class="nv">PROJECT</span><span class="o">=</span>phoenix_service
<span class="nv">INSTALL_DIR</span><span class="o">=</span>/usr/local/opt
<span class="nv">VERSION</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat </span>rel/<span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nv">INSTALL_TAR</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/rel/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.tar.gz
<span class="o">(</span><span class="nb">cd</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span><span class="p">;</span> <span class="se">\</span>
<span class="nb">tar</span> <span class="nt">-xf</span> <span class="nv">$INSTALL_TAR</span><span class="o">)</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>
service <span class="nv">$PROJECT</span> restart
<span class="nb">exit</span> <span class="c"># sh</span>
<span class="nb">exit</span> <span class="c"># su</span></code></pre></figure>

<p>Note that the path permissions will need to be fixed again if
you added the release to the systemwide path and want to be able
to run it as any user.</p>

<h3 id="troubleshooting">Troubleshooting</h3>

<p>Make sure to set server to true in <strong>config/prod.exs</strong>.
If the server is mysteriously not working, start the release
with console to see error messages.</p>

<h3 id="what-next">What Next?</h3>

<p>Consider looking into <a href="https://github.com/boldpoker/edeliver">edeliver</a> for deployment.
“edeliver is based on deliver and provides a bash script to build and deploy
Elixir and Erlang applications and perform hot-code upgrades.”</p>

<h2 id="references">References:</h2>
<ul>
  <li><a href="http://www.phoenixframework.org/docs/deployment">Phoenix, Deployment</a></li>
  <li><a href="http://learnwithjeff.com/blog/2015/10/03/building-a-json-api-with-phoenix/">Phoenix, Building a JSON API With Phoenix</a></li>
  <li><a href="https://renatomoya.github.io/2015/05/09/Building-a-versioned-REST-API-with-Phoenix-Framework.html">Phoenix, Building a versioned REST API with Phoenix Framework</a></li>
  <li><a href="https://github.com/phoenixframework/phoenix/issues/962">Phoenix, Default (4000) port busy</a></li>
  <li><a href="http://stackoverflow.com/questions/30540466/start-phoenix-app-with-cowboy-server-on-different-port">Phoenix, Start Phoenix app with cowboy server on different port</a></li>
  <li><a href="https://github.com/phoenixframework/phoenix/issues/354">Phoenix, does not evaluate Env config overrides at runtime</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/postgresql/freebsd/2016/03/19/installing-phoenix-elixir-and-postgresql-on-freebsd.html">Phoenix, Installing Phoenix, Elixir and PostgreSQL on FreeBSD</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">Phoenix, A Shell Script for Working with Phoenix JSON APIs</a></li>
  <li><a href="https://github.com/bitwalker/exrm">Elixir Release Manager</a></li>
  <li><a href="https://exrm.readme.io/docs/release-configuration">Elixir, exrm, Release Configuration</a></li>
  <li><a href="https://github.com/bitwalker/exrm/issues/42">Elixir, exrm, Where should I place app.conf and vm.args</a></li>
  <li><a href="https://hex.pm/packages?search=exrm">Elixir, exrm, Packages on Hex</a></li>
  <li><a href="https://github.com/boldpoker/edeliver">Elixir, edeliver</a></li>
  <li><a href="http://elixir-lang.org/docs/stable/mix/Mix.Config.html">Elixir, Mix.Config</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD</a></li>
  <li><a href="https://github.com/adamkittelson/apathy-drive-ex/blob/master/config/deploy.rb">Ruby, adamkittelson’s Cap File</a></li>
  <li><a href="https://jasonk2600.wordpress.com/2010/01/11/installing-postgresql-on-freebsd/">PostgreSQL, Installing PostgreSQL on FreeBSD</a></li>
  <li><a href="http://www.grymoire.com/Unix/Sh.html">Sh - the Bourne Shell</a></li>
  <li><a href="https://forums.freebsd.org/threads/9120/">FreeBSD, KDE4 localization</a></li>
  <li><a href="https://sgeos.github.io/freebsd/ion/dtn/2016/02/15/ion-dtn-as-a-service-on-freebsd.html">ION DTN as a Service on FreeBSD</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="erlang" /><category term="freebsd" /></entry><entry><title type="html">Installing Phoenix, Elixir and PostgreSQL on FreeBSD</title><link href="https://sgeos.github.io/phoenix/elixir/postgresql/freebsd/2016/03/19/installing-phoenix-elixir-and-postgresql-on-freebsd.html" rel="alternate" type="text/html" title="Installing Phoenix, Elixir and PostgreSQL on FreeBSD" /><published>2016-03-19T07:35:02+00:00</published><updated>2016-03-19T07:35:02+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/postgresql/freebsd/2016/03/19/installing-phoenix-elixir-and-postgresql-on-freebsd</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/postgresql/freebsd/2016/03/19/installing-phoenix-elixir-and-postgresql-on-freebsd.html"><![CDATA[<!-- A28 -->
<script>console.log("A28");</script>

<p>This post covers installing Phoenix, Elixir and PostgreSQL on a clean installation of FreeBSD.
The post assumes FreeBSD is installed and you have created an unprivileged user.
FreeBSD installation instructions can be found <a href="https://www.freebsd.org/doc/handbook/bsdinstall.html">here</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-03-19 07:35:02 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-CURRENT <span class="c">#0 r296925: Wed Mar 16 20:53:04 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>psql <span class="nt">--version</span>
psql <span class="o">(</span>PostgreSQL<span class="o">)</span> 9.4.6
<span class="nv">$ </span>mix hex.info
Hex:    0.11.3
Elixir: 1.2.3
OTP:    18.2.4
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.1.4</code></pre></figure>

<h2 id="bootstrapping-the-system">Bootstrapping the system</h2>

<p>Login as root and install <strong>portmaster</strong>.
If you are using packages instead of ports, you will need to
install packages instead of using <strong>portmaster</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">portsnap fetch extract
<span class="o">(</span><span class="nb">cd</span> /usr/ports/ports-mgmt/portmaster <span class="o">&amp;&amp;</span> make config-recursive <span class="o">&amp;&amp;</span> make config-recursive <span class="o">&amp;&amp;</span> make <span class="nb">install </span>clean<span class="o">)</span>
portmaster security/sudo</code></pre></figure>

<p>The following utilities may also be useful.
Note that <strong>ftp/curl</strong> is used at the end of the post.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">portmaster ports-mgmt/portupgrade devel/git shells/bash editors/vim ftp/curl ftp/wget</code></pre></figure>

<h2 id="installing-postgresql">Installing PostgreSQL</h2>

<p>Install <strong>databases/postgresql94-server</strong> as root.
If you would rather install a different version, do that instead.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">portmaster databases/postgresql94-server</code></pre></figure>

<p>Initialize the database.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service postgresql initdb</code></pre></figure>

<p>If you want to enable remote connections,
add the following line to <strong>/usr/local/pgsql/data/postgresql.conf</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">listen_addresses <span class="o">=</span> <span class="s1">'*'</span></code></pre></figure>

<p>If you want to change the port PostgreSQL listens on,
add the following line to <strong>/usr/local/pgsql/data/postgresql.conf</strong>.
Replace 5432 with the port you want to use.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">port <span class="o">=</span> 5432                            <span class="c"># (change requires restart)</span></code></pre></figure>

<p>To use password hash authentication,
add the following line to <strong>/usr/local/pgsql/data/pg_hba.conf</strong>.
Replace 192.168.0.0/16 with your own network.
Consider changing “trust” to “md5” for local and loopback IP address connections.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">host    all             all             192.168.0.0/16          md5</code></pre></figure>

<p>Enable PostgreSQL in <strong>/etc/rc.conf</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">echo</span> <span class="s1">'postgresql_enable="YES"'</span> <span class="o">&gt;&gt;</span> /etc/rc.conf</code></pre></figure>

<p>Start PostgreSQL.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service postgresql start</code></pre></figure>

<p>Add a PostgreSQL super-user with database and role creation privileges.
Replace username with your unprivileged FreeBSD user login.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">su pgsql
createuser <span class="nt">-sdrP</span> username</code></pre></figure>

<p>You should now be able to start <strong>psql</strong> as your non-root user.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">psql</code></pre></figure>

<h2 id="installing-elixir-and-phoenix">Installing Elixir and Phoenix</h2>

<p>Install Elixir and Phoenix.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">sudo </span>portmaster lang/elixir
mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez</code></pre></figure>

<p>Optionally, install <strong>npm</strong> and <strong>brunch</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">sudo </span>portmaster www/node www/npm
<span class="nb">sudo </span>npm <span class="nb">install</span> <span class="nt">-g</span> brunch</code></pre></figure>

<h1 id="creating-a-sample-project">Creating a Sample Project</h1>

<p>Create a new Phoenix project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new phoenix_service <span class="nt">--no-brunch</span>
<span class="nb">cd </span>phoenix_service
mix ecto.create</code></pre></figure>

<p>Create a simple JSON memo service.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.gen.json Memo memos title:string body:string</code></pre></figure>

<p>Revise the <strong>web/router.ex</strong> file.
The “/api” scope needs to be uncommented and the “/memos” route needs to be added.</p>

<p><strong>web/router.ex file</strong></p>

<figure class="highlight"><pre><code class="language-elixer" data-lang="elixer">defmodule PhoenixService.Router do
  use PhoenixService.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", PhoenixService do
    pipe_through :browser # Use the default browser stack
  
    get "/", PageController, :index
  end

  scope "/api", PhoenixService do
    pipe_through :api

    resources "/memos", MemoController, except: [:new, :edit]
  end
end</code></pre></figure>

<p>Run the migration.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix ecto.migrate</code></pre></figure>

<p>Tests should pass.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix <span class="nb">test</span></code></pre></figure>

<h1 id="running-the-sample-project">Running the Sample Project</h1>

<p>Start the server.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.server</code></pre></figure>

<p>The following <strong>curl</strong> commands can be used to interact with the running JSON API.
My <a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">previous post</a> covers a shell script for interacting with this sample app.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "New Title", "body": "This is the new memo body."}}'</span> http://localhost:4000/api/memos
<span class="c"># PATCH id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PATCH <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Patched Title"}}'</span> http://localhost:4000/api/memos/1
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PATCH <span class="nt">-d</span> <span class="s1">'{"memo": {"body": "Patched memo body."}}'</span> http://localhost:4000/api/memos/1
<span class="c"># PUT id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PUT <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Updated Title", "body": "Updated memo body."}}'</span> http://localhost:4000/api/memos/1
<span class="c"># GET all</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos/1
<span class="c"># DELETE id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> DELETE http://localhost:4000/api/memos/1</code></pre></figure>

<h2 id="references">References:</h2>
<ul>
  <li><a href="http://www.phoenixframework.org/docs/deployment">Phoenix, Deployment</a></li>
  <li><a href="http://learnwithjeff.com/blog/2015/10/03/building-a-json-api-with-phoenix/">Phoenix, Building a JSON API With Phoenix</a></li>
  <li><a href="https://renatomoya.github.io/2015/05/09/Building-a-versioned-REST-API-with-Phoenix-Framework.html">Phoenix, Building a versioned REST API with Phoenix Framework</a></li>
  <li><a href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html">Phoenix, A Shell Script for Working with Phoenix JSON APIs</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD</a></li>
  <li><a href="https://jasonk2600.wordpress.com/2010/01/11/installing-postgresql-on-freebsd/">PostgreSQL, Installing PostgreSQL on FreeBSD</a></li>
  <li><a href="http://www.rhyous.com/2010/08/27/installing-and-using-postgresql-on-freebsd/">PostgreSQL, Installing and Using Postgresql on FreeBSD</a></li>
  <li><a href="http://www.freebsddiary.org/postgresql.php">PostgreSQL, installation and configuration</a></li>
  <li><a href="http://www.grymoire.com/Unix/Sh.html">Sh - the Bourne Shell</a></li>
  <li><a href="https://www.freebsd.org/doc/handbook/bsdinstall.html">FreeBSD, Installing FreeBSD</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="postgresql" /><category term="freebsd" /></entry><entry><title type="html">A Shell Script for Working with Phoenix JSON APIs</title><link href="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html" rel="alternate" type="text/html" title="A Shell Script for Working with Phoenix JSON APIs" /><published>2016-03-19T00:55:41+00:00</published><updated>2016-03-19T00:55:41+00:00</updated><id>https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis</id><content type="html" xml:base="https://sgeos.github.io/phoenix/elixir/sh/2016/03/19/a-shell-script-for-working-with-phoenix-json-apis.html"><![CDATA[<!-- A27 -->
<script>console.log("A27");</script>

<p>I started writing another post, but wound up writing a non-trivial script for
working with Phoenix JSON APIs.
That script is covered in this post.
It is written for the FreeBSD Bourne shell, but it should be compatible with <strong>bash</strong>.</p>

<p>This post also serves as a <strong>getopts</strong> example.
<strong>getopts</strong> was used instead of <strong>getopt</strong> because <strong>getopt</strong> does not support
command line arguments that contain spaces.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-03-19 00:55:41 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-CURRENT <span class="c">#0 r296925: Wed Mar 16 20:53:04 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>mix hex.info
Hex:    0.11.3
Elixir: 1.2.3
OTP:    18.2.4
<span class="k">*</span> snip <span class="k">*</span>
<span class="nv">$ </span>mix phoenix.new <span class="nt">-v</span>
Phoenix v1.1.4</code></pre></figure>

<h2 id="instructions">Instructions</h2>

<p>Create a new Phoenix project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.new phoenix_service <span class="nt">--no-brunch</span>
<span class="nb">cd </span>phoenix_service
mix ecto.create</code></pre></figure>

<p>This example will use a JSON memo service.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.gen.json Memo memos title:string body:string</code></pre></figure>

<p>Revise the <strong>web/router.ex</strong> file.
The “/api” scope needs to be uncommented and the “/memos” route needs to be added.</p>

<p><strong>web/router.ex file</strong></p>

<figure class="highlight"><pre><code class="language-elixer" data-lang="elixer">defmodule PhoenixService.Router do
  use PhoenixService.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", PhoenixService do
    pipe_through :browser # Use the default browser stack
  
    get "/", PageController, :index
  end

  scope "/api", PhoenixService do
    pipe_through :api

    resources "/memos", MemoController, except: [:new, :edit]
  end
end</code></pre></figure>

<p>Run the migration.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix ecto.migrate</code></pre></figure>

<p>Tests should pass.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix <span class="nb">test</span></code></pre></figure>

<h1 id="running-the-sample-project">Running the Sample Project</h1>

<p>Start the server.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix phoenix.server</code></pre></figure>

<p>The following <strong>curl</strong> commands can be used to interact with the JSON API.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># POST new</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> POST <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "New Title", "body": "This is the new memo body."}}'</span> http://localhost:4000/api/memos
<span class="c"># PATCH id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PATCH <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Patched Title"}}'</span> http://localhost:4000/api/memos/1
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PATCH <span class="nt">-d</span> <span class="s1">'{"memo": {"body": "Patched memo body."}}'</span> http://localhost:4000/api/memos/1
<span class="c"># PUT id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> PUT <span class="nt">-d</span> <span class="s1">'{"memo": {"title": "Updated Title", "body": "Updated memo body."}}'</span> http://localhost:4000/api/memos/1
<span class="c"># GET all</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos
<span class="c"># GET id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> http://localhost:4000/api/memos/1
<span class="c"># DELETE id 1</span>
curl <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="nt">-X</span> DELETE http://localhost:4000/api/memos/1</code></pre></figure>

<p>A script is easier to use if you need to make more than a couple of <strong>curl</strong> calls.
It can also be extended to support authentication and everything else your API may need.</p>

<p>Create a script to GET, POST, PATCH, PUT and DELETE memos.
Note that a Bourne Shell tutorial can be found <a href="http://www.grymoire.com/Unix/Sh.html">here</a>.</p>

<p><strong>memo_api.sh</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>

reset<span class="o">()</span> <span class="o">{</span>
  <span class="nv">HOST</span><span class="o">=</span>http://localhost:4000
  <span class="nv">SCOPE</span><span class="o">=</span>api
  <span class="nv">ROUTE</span><span class="o">=</span>memos
  <span class="nv">METHOD</span><span class="o">=</span><span class="s2">"GET"</span>
  <span class="nv">HEADERS</span><span class="o">=</span><span class="s2">"Content-Type: application/json"</span>
  <span class="nv">ID</span><span class="o">=</span><span class="s2">""</span>
  <span class="nv">TITLE</span><span class="o">=</span><span class="s2">""</span>
  <span class="nv">BODY</span><span class="o">=</span><span class="s2">""</span>
<span class="o">}</span>

usage<span class="o">()</span> <span class="o">{</span>
  reset
  <span class="nb">echo</span> <span class="s2">"Usage:  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> [options]"</span>
  <span class="nb">echo</span> <span class="s2">"Options:"</span>
  <span class="nb">echo</span> <span class="s2">"  -o HOST : set URL host, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -s SCOPE : set URL scope, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -r ROUTE : set URL route, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -X METHOD : set HTTP method, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -H HEADERS : set HTTP headers, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -i ID : set memo id, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">ID</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -t TITLE : set memo title, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">TITLE</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -b BODY : set memo body, defaults to </span><span class="se">\"</span><span class="k">${</span><span class="nv">BODY</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  -h : display this help"</span>
  <span class="nb">echo</span> <span class="s2">"Examples:"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X GET"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X GET -i 7"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X POST -t </span><span class="se">\"</span><span class="s2">Memo Title</span><span class="se">\"</span><span class="s2"> -b </span><span class="se">\"</span><span class="s2">Memo body here.</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X PATCH -t </span><span class="se">\"</span><span class="s2">Patched title.</span><span class="se">\"</span><span class="s2"> -i 7"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X PATCH -b </span><span class="se">\"</span><span class="s2">Patched body.</span><span class="se">\"</span><span class="s2"> -i 7"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X PUT -t </span><span class="se">\"</span><span class="s2">New Title</span><span class="se">\"</span><span class="s2"> -b </span><span class="se">\"</span><span class="s2">New body.</span><span class="se">\"</span><span class="s2"> -i 7"</span>
  <span class="nb">echo</span> <span class="s2">"  </span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2"> -X DELETE -i 7"</span>
  <span class="nb">exit</span> <span class="k">${</span><span class="nv">1</span><span class="k">}</span>
<span class="o">}</span>

reset
<span class="k">while </span><span class="nb">getopts</span> <span class="s2">"o:s:r:X:H:i:t:b:h"</span> opt
<span class="k">do
  case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">opt</span><span class="k">}</span><span class="s2">"</span> <span class="k">in
    </span>o<span class="p">)</span> <span class="nv">HOST</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    s<span class="p">)</span> <span class="nv">SCOPE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    r<span class="p">)</span> <span class="nv">ROUTE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    X<span class="p">)</span> <span class="nv">METHOD</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    H<span class="p">)</span> <span class="nv">HEADERS</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    i<span class="p">)</span> <span class="nv">ID</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    t<span class="p">)</span> <span class="nv">TITLE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    b<span class="p">)</span> <span class="nv">BODY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">OPTARG</span><span class="k">}</span><span class="s2">"</span> <span class="p">;;</span>
    h<span class="p">)</span> usage 1 <span class="p">;;</span>
    <span class="se">\?</span><span class="p">)</span> usage 2 <span class="p">;;</span>
  <span class="k">esac</span>
<span class="k">done
</span><span class="nb">shift</span> <span class="si">$(</span><span class="nb">expr</span> <span class="k">${</span><span class="nv">OPTIND</span><span class="k">}</span> - 1<span class="si">)</span>

<span class="k">case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span><span class="s2">"</span> <span class="k">in
  </span>GET<span class="p">)</span>
    curl <span class="nt">-H</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-X</span> <span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}${</span><span class="nv">ID</span>:+<span class="s2">"/</span><span class="k">${</span><span class="nv">ID</span><span class="k">}</span><span class="s2">"</span><span class="k">}</span><span class="s2">"</span>
    <span class="p">;;</span>
  POST<span class="p">)</span>
    <span class="nv">PAYLOAD</span><span class="o">=</span><span class="s1">'{"memo": {"title": "'</span><span class="s2">"</span><span class="k">${</span><span class="nv">TITLE</span><span class="k">:-</span><span class="p">(no title)</span><span class="k">}</span><span class="s2">"</span><span class="s1">'", "body": "'</span><span class="s2">"</span><span class="k">${</span><span class="nv">BODY</span><span class="k">:-</span><span class="p">(no body)</span><span class="k">}</span><span class="s2">"</span><span class="s1">'"}}'</span>
    curl <span class="nt">-H</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-X</span> <span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span> <span class="nt">-d</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PAYLOAD</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}</span><span class="s2">"</span> 
    <span class="p">;;</span>
  PUT<span class="p">)</span>
    <span class="nv">PAYLOAD</span><span class="o">=</span><span class="s1">'{"memo": {"title": "'</span><span class="s2">"</span><span class="k">${</span><span class="nv">TITLE</span><span class="k">:-</span><span class="p">(no title)</span><span class="k">}</span><span class="s2">"</span><span class="s1">'", "body": "'</span><span class="s2">"</span><span class="k">${</span><span class="nv">BODY</span><span class="k">:-</span><span class="p">(no body)</span><span class="k">}</span><span class="s2">"</span><span class="s1">'"}}'</span>
    curl <span class="nt">-H</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-X</span> <span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span> <span class="nt">-d</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PAYLOAD</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ID</span>:?<span class="s1">'No ID specified.'</span><span class="k">}</span><span class="s2">"</span> 
    <span class="p">;;</span>
  PATCH<span class="p">)</span>
    <span class="c"># if defined replace individual fields with</span>
    <span class="c"># JSON fragments followed by a comma and space</span>
    <span class="nv">TITLE</span><span class="o">=</span><span class="k">${</span><span class="nv">TITLE</span>:+<span class="s2">"</span><span class="se">\"</span><span class="s2">title</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="k">${</span><span class="nv">TITLE</span><span class="k">}</span><span class="se">\"</span><span class="s2">, "</span><span class="k">}</span>
    <span class="nv">BODY</span><span class="o">=</span><span class="k">${</span><span class="nv">BODY</span>:+<span class="s2">"</span><span class="se">\"</span><span class="s2">body</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="k">${</span><span class="nv">BODY</span><span class="k">}</span><span class="se">\"</span><span class="s2">, "</span><span class="k">}</span>
    <span class="c"># strip trailing comma and space</span>
    <span class="nv">PAYLOAD</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">TITLE</span><span class="k">}${</span><span class="nv">BODY</span><span class="k">}</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="s1">'s/, $//g'</span><span class="si">)</span><span class="s2">"</span>
    <span class="c"># complete JSON payload</span>
    <span class="nv">PAYLOAD</span><span class="o">=</span><span class="s2">"{</span><span class="se">\"</span><span class="s2">memo</span><span class="se">\"</span><span class="s2">: {</span><span class="k">${</span><span class="nv">PAYLOAD</span><span class="k">}</span><span class="s2">}}"</span>
    curl <span class="nt">-H</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-X</span> <span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span> <span class="nt">-d</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PAYLOAD</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ID</span>:?<span class="s1">'No ID specified.'</span><span class="k">}</span><span class="s2">"</span> 
    <span class="p">;;</span>
  DELETE<span class="p">)</span>
    curl <span class="nt">-H</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HEADERS</span><span class="k">}</span><span class="s2">"</span> <span class="nt">-X</span> <span class="k">${</span><span class="nv">METHOD</span><span class="k">}</span> <span class="s2">"</span><span class="k">${</span><span class="nv">HOST</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">SCOPE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ROUTE</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">ID</span>:?<span class="s1">'No ID specified.'</span><span class="k">}</span><span class="s2">"</span>
    <span class="p">;;</span>
  <span class="k">*</span><span class="p">)</span>
    usage 2
    <span class="p">;;</span>
<span class="k">esac</span>
<span class="nb">echo</span> <span class="s2">""</span></code></pre></figure>

<p>The reset function resets the variables to their initial state.
It is called before parsing command line arguments and displaying the usage.</p>

<p>The usage function displays usage, options and examples.
It also terminates the script.</p>

<p>The <strong>getopts</strong> loop parses command line arguments into variables.</p>

<p>The case statement at the end uses the HTTP method to select a <strong>curl</strong> call.</p>

<ul>
  <li>The GET method works with or without an id.</li>
  <li>The POST method creates a payload and posts it.</li>
  <li>PUT is like post except the verb is different and it requires an id.</li>
  <li>PATCH is the most complicated.
It builds a payload and strips the trailing comma.
Then it sends the payload to a URL with an id.</li>
  <li>DELETE requires an id.</li>
</ul>

<p>The script can be used as follows.
Note that trying to delete a memo that does not exist will fail with a wall of HTML.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x memo_api.sh
./memo_api.sh <span class="nt">-X</span> POST <span class="nt">-t</span> <span class="s2">"Memo Title"</span> <span class="nt">-b</span> <span class="s2">"Memo body here."</span>
./memo_api.sh <span class="nt">-X</span> GET <span class="nt">-i</span> 1
./memo_api.sh <span class="nt">-X</span> POST <span class="nt">-t</span> <span class="s2">"Another Memo"</span> <span class="nt">-b</span> <span class="s2">"This memo's body."</span>
./memo_api.sh <span class="nt">-X</span> PATCH <span class="nt">-t</span> <span class="s2">"Patched title."</span> <span class="nt">-i</span> 2
./memo_api.sh <span class="nt">-X</span> PATCH <span class="nt">-b</span> <span class="s2">"Patched body."</span> <span class="nt">-i</span> 1
./memo_api.sh <span class="nt">-X</span> PUT <span class="nt">-t</span> <span class="s2">"New Title"</span> <span class="nt">-b</span> <span class="s2">"New body."</span> <span class="nt">-i</span> 2
./memo_api.sh <span class="nt">-X</span> GET
./memo_api.sh <span class="nt">-X</span> DELETE <span class="nt">-i</span> 1</code></pre></figure>

<p>This script could be customized to work with other APIs.</p>

<h2 id="references">References:</h2>
<ul>
  <li><a href="http://www.shelldorado.com/goodcoding/cmdargs.html">sh, Handling Command Line Arguments</a></li>
  <li><a href="http://www.grymoire.com/Unix/Sh.html">sh, the Bourne Shell</a></li>
  <li><a href="http://stackoverflow.com/questions/15758814/turning-multiple-lines-into-one-line-with-comma-separated-perl-sed-awk">sh, Turning multiple lines into one line with comma separated</a></li>
  <li><a href="https://sgeos.github.io/sh/2016/02/23/bourne-shell-counting-character-occurrence-in-unicode-text-file.html">sh, Bourne Shell: Counting Character Occurrence in a Unicode Text File</a></li>
  <li><a href="http://www.phoenixframework.org/docs/deployment">Phoenix, Deployment</a></li>
  <li><a href="http://learnwithjeff.com/blog/2015/10/03/building-a-json-api-with-phoenix/">Phoenix, Building a JSON API With Phoenix</a></li>
  <li><a href="https://renatomoya.github.io/2015/05/09/Building-a-versioned-REST-API-with-Phoenix-Framework.html">Phoenix, Building a versioned REST API with Phoenix Framework</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html">Elixir as a Service on FreeBSD</a></li>
  <li><a href="http://stackoverflow.com/questions/24241893/rest-api-patch-or-put">REST API PATCH or PUT</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="phoenix" /><category term="elixir" /><category term="sh" /></entry><entry><title type="html">Working with mix project iex Code Snippets Stored in Files</title><link href="https://sgeos.github.io/elixir/iex/2016/03/14/working_with_mix_project_iex_code_snippets_stored_in_files.html" rel="alternate" type="text/html" title="Working with mix project iex Code Snippets Stored in Files" /><published>2016-03-14T17:08:09+00:00</published><updated>2016-03-14T17:08:09+00:00</updated><id>https://sgeos.github.io/elixir/iex/2016/03/14/working_with_mix_project_iex_code_snippets_stored_in_files</id><content type="html" xml:base="https://sgeos.github.io/elixir/iex/2016/03/14/working_with_mix_project_iex_code_snippets_stored_in_files.html"><![CDATA[<!-- A24 -->
<script>console.log("A24");</script>

<p><strong>iex</strong> can be used for interactively experimenting with <strong>elixir</strong>.
If this experimentation becomes elaborate, it can be useful to store the commands in a file.
This post covers working with files that store <strong>elixir</strong> code snippets.
Attention is paid to code that depends on <strong>mix</strong> project modules.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2016-03-14 17:08:09 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-CURRENT <span class="c">#0 r296709: Sat Mar 12 21:18:38 JST 2016     root@mirage.sennue.com:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>elixir <span class="nt">--version</span>
Erlang/OTP 18 <span class="o">[</span>erts-7.2.1] <span class="o">[</span><span class="nb">source</span><span class="o">]</span> <span class="o">[</span>64-bit] <span class="o">[</span>async-threads:10] <span class="o">[</span>hipe] <span class="o">[</span>kernel-poll:false]

Elixir 1.2.3</code></pre></figure>

<h2 id="instructions">Instructions</h2>

<p>A single line can be run from the command line.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">elixir <span class="nt">-e</span> <span class="s1">'IO.puts("Hello, mix run!")'</span> <span class="c"># general elixir code</span>
mix run <span class="nt">-e</span> <span class="s1">'IO.puts("Hello, mix run!")'</span> <span class="c"># project specific code</span></code></pre></figure>

<p>Something more elaborate probably wants to live in a file.</p>

<p><strong>snippets/hello.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1">#!/usr/bin/env elixir</span>

<span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"Hello, Elixir!"</span></code></pre></figure>

<p>The above file be run from the command line as is, but it will
not have acces to modules in the <strong>mix</strong> project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x snippets/hello.exs
./snippets/hello.exs</code></pre></figure>

<p>Run the file with <strong>mix run</strong> if any <strong>mix</strong> project modules are referenced.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix run snippets/hello.exs</code></pre></figure>

<p>Note that <strong>c</strong> or <strong>Code.load_file</strong> can be used to load the file in <strong>iex</strong>.</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="n">c</span> <span class="s2">"snippets/hello.exs"</span>
<span class="no">Code</span><span class="o">.</span><span class="n">load_file</span> <span class="s2">"snippets/hello.exs"</span></code></pre></figure>

<p><strong>Code.load_file</strong> can be used to load one file from another file.
This is fragile if using relative paths.</p>

<p><strong>snippets/indirect.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1">#!/usr/bin/env elixir</span>

<span class="no">Code</span><span class="o">.</span><span class="n">load_file</span> <span class="s2">"snippets/hello.exs"</span></code></pre></figure>

<p>To run <strong>mix</strong> code snippets directly from the command line,
add the following file to some place on your path.
For example, <code class="language-plaintext highlighter-rouge">$(which mix)-snippet</code>.</p>

<p><strong>mix-snippet</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>

mix run <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span></code></pre></figure>

<p>Make <strong>mix-snippet</strong> executable.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x mix-snippet <span class="c"># use the actual path</span></code></pre></figure>

<p>The <strong>mix-snippet</strong> shebang can now be used.</p>

<p><strong>snippets/hello-mix-snippet.exs</strong></p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1">#!/usr/bin/env mix-snippet</span>

<span class="no">IO</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"Hello, mix-snippet!"</span></code></pre></figure>

<p>Code snippets that depend on the <strong>mix</strong> project can now
be executed from the command line.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x snippets/mix-snippet-hello.exs
./snippets/mix-snippet-hello.exs</code></pre></figure>

<h2 id="references">References:</h2>
<ul>
  <li><a href="http://elixir-lang.org/docs/stable/mix/Mix.Tasks.Run.html">Elixir, Mix.Tasks.Run</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/08/single-file-elixir-programs.html">Elixir, Single File Elixir Programs</a></li>
  <li><a href="http://elixir-lang.org/crash-course.html">Elixir, Erlang/Elixir Syntax: A Crash Course</a></li>
  <li><a href="https://bitbucket.org/vincit/mebe/src/b33c48c8db76ae4943a5ae193eb13eeaa7fa4311/refresh.exp?fileviewer=file-view-default">Elixir, expect iex</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="elixir" /><category term="iex" /></entry><entry><title type="html">Elixir, Executing AST</title><link href="https://sgeos.github.io/elixir/metaprogramming/ast/2016/01/31/elixir-execute-ast.html" rel="alternate" type="text/html" title="Elixir, Executing AST" /><published>2016-01-31T19:49:00+00:00</published><updated>2016-01-31T19:49:00+00:00</updated><id>https://sgeos.github.io/elixir/metaprogramming/ast/2016/01/31/elixir-execute-ast</id><content type="html" xml:base="https://sgeos.github.io/elixir/metaprogramming/ast/2016/01/31/elixir-execute-ast.html"><![CDATA[<!-- A8 -->
<script>console.log("A8");</script>

<p>Inspecting AST is a good way to learn about elixir macros.
Executing AST can also be instructional, but it is a little more nuanced than
directly executing elixir code.
This post covers executing AST with <strong>Code.eval_quoted()</strong>.
Note that outside of experimentation, executing AST with <strong>Code.eval_quoted()</strong>
is probably the wrong thing to do.</p>

<p>Thanks to everyone in <a href="irc://irc.freenode.net/elixir-lang">#elixir-lang on Freenode</a> who answered my
questions about executing AST.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date 
</span>February  1, 2016 at 04:49:00 AM JST
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
FreeBSD 11.0-CURRENT <span class="c">#0 r287598: Thu Sep 10 14:45:48 JST 2015     root@:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>elixir <span class="nt">--version</span>
Erlang/OTP 18 <span class="o">[</span>erts-7.2.1] <span class="o">[</span><span class="nb">source</span><span class="o">]</span> <span class="o">[</span>64-bit] <span class="o">[</span>async-threads:10] <span class="o">[</span>hipe] <span class="o">[</span>kernel-poll:false]
Elixir 1.2.2</code></pre></figure>

<h2 id="instructions">Instructions</h2>
<p>This is a single file example of a naive solution that does <strong>not</strong> work.
Put the following in <strong>ast.exs</strong>.</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1">#!/usr/bin/env elixir</span>

<span class="k">defmodule</span> <span class="no">MacroLibrary</span> <span class="k">do</span>
  <span class="c1"># a simple macro that delegates work to a complex function</span>
  <span class="k">defmacro</span> <span class="n">process_macro</span><span class="p">(</span><span class="n">input</span><span class="p">)</span> <span class="k">do</span>
    <span class="kn">quote</span> <span class="k">do</span>
      <span class="n">process_function</span><span class="p">(</span><span class="kn">unquote</span><span class="p">(</span><span class="n">input</span><span class="p">))</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="c1"># the complex function called by the macro</span>
  <span class="k">def</span> <span class="n">process_function</span><span class="p">(</span><span class="n">input</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">input</span>
    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">each</span><span class="p">(</span><span class="o">&amp;</span><span class="no">IO</span><span class="o">.</span><span class="n">puts</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">defmodule</span> <span class="no">Script</span> <span class="k">do</span>
  <span class="kn">import</span> <span class="no">MacroLibrary</span> <span class="c1"># important</span>

  <span class="k">def</span> <span class="n">main</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="k">do</span>
    <span class="c1"># call the macro directly</span>
    <span class="n">process_macro</span><span class="p">([</span><span class="s2">"Args:"</span> <span class="o">|</span> <span class="n">args</span><span class="p">])</span>
    <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span>

    <span class="c1"># macro AST</span>
    <span class="n">ast</span> <span class="o">=</span> <span class="kn">quote</span> <span class="k">do</span>
      <span class="n">process_macro</span><span class="p">([</span><span class="s2">"Args:"</span> <span class="o">|</span> <span class="n">args</span><span class="p">])</span> <span class="c1"># BROKEN line 28</span>
    <span class="k">end</span>

    <span class="c1"># display the AST</span>
    <span class="n">ast</span>
    <span class="o">|&gt;</span> <span class="no">Macro</span><span class="o">.</span><span class="n">to_string</span>
    <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span>

    <span class="c1"># display the expanded AST</span>
    <span class="n">ast</span>
    <span class="o">|&gt;</span> <span class="no">Macro</span><span class="o">.</span><span class="n">expand</span><span class="p">(</span><span class="n">__ENV__</span><span class="p">)</span>
    <span class="o">|&gt;</span> <span class="no">Macro</span><span class="o">.</span><span class="n">to_string</span>
    <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span>

    <span class="c1"># execute the AST</span>
    <span class="p">{</span><span class="n">result</span><span class="p">,</span> <span class="n">_binding</span><span class="p">}</span> <span class="o">=</span> <span class="n">ast</span>
    <span class="o">|&gt;</span> <span class="no">Code</span><span class="o">.</span><span class="n">eval_quoted</span> <span class="c1"># BROKEN line 44</span>

    <span class="c1"># display the result</span>
    <span class="n">result</span>
    <span class="o">|&gt;</span> <span class="no">IO</span><span class="o">.</span><span class="n">puts</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="c1"># run the script</span>
<span class="no">Script</span><span class="o">.</span><span class="n">main</span><span class="p">(</span><span class="no">System</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span></code></pre></figure>

<p>Make the script executable.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">chmod</span> +x ast.exs</code></pre></figure>

<p>This is the expected outputed.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span>./ast.exs a b c
Args:
a
b
c
ok
process_macro<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
process_function<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
Args:
a
b
c
ok</code></pre></figure>

<p>This is the actual output.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span>./ast.exs a b c
Args:
a
b
c
ok
process_macro<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
process_function<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
<span class="k">**</span> <span class="o">(</span>CompileError<span class="o">)</span> nofile:1: undefined <span class="k">function </span>process_function/1
    expanding macro: MacroLibrary.process_macro/1
    nofile:1: <span class="o">(</span>file<span class="o">)</span></code></pre></figure>

<p>What happened?</p>

<p>Directly invoking the macro works as expected.
The naive expectation is that the AST should just execute because the code
is the same as the direct invocation.</p>

<p>The problem is that context is missing.
The AST evaluates as <strong>process_macro([“Args:” | args])</strong> which expands to
<strong>process_function([“Args:” | args])</strong>.
<strong>process_function</strong> is defined in the <strong>MacroLibrary</strong> module.
The macro library is included in the <strong>Script</strong> module.
That information is stored in the current context.
Without the context, <strong>Code.eval_quoted</strong> does not know where to find
<strong>process_function</strong>.</p>

<p>Change line 44 to the following:</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">    <span class="o">|&gt;</span> <span class="no">Code</span><span class="o">.</span><span class="n">eval_quoted</span><span class="p">([],</span> <span class="n">__ENV__</span><span class="p">)</span> <span class="c1"># BROKEN line 44</span></code></pre></figure>

<p>There is another problem.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span>./ast.exs a b c
Args:
a
b
c
ok
process_macro<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
process_function<span class="o">([</span><span class="s2">"Args:"</span> | args]<span class="o">)</span>
<span class="k">**</span> <span class="o">(</span>CompileError<span class="o">)</span> ast.exs:44: undefined <span class="k">function </span>args/0
    expanding macro: MacroLibrary.process_macro/1
    ast.exs:44: Script <span class="o">(</span>module<span class="o">)</span></code></pre></figure>

<p>When executing the macro directly, <strong>args</strong> resolves properly.
Bindings are not part of the environment context and need to be passed in
separately.</p>

<p>Change line 44 to the following:</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">    <span class="o">|&gt;</span> <span class="no">Code</span><span class="o">.</span><span class="n">eval_quoted</span><span class="p">([</span><span class="ss">args:</span> <span class="n">args</span><span class="p">],</span> <span class="n">__ENV__</span><span class="p">)</span> <span class="c1"># CORRECT line 44</span></code></pre></figure>

<p>This did not fix the problem.
The macro can be directly invoked with <strong>process_macro([“Args:” | args])</strong>,
but executed AST needs to use <strong>var!()</strong> to access variables.
This means that the direct macro call and the executable AST need to be
slightly different.</p>

<p>Change line 28 to the following:</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">      <span class="n">process_macro</span><span class="p">([</span><span class="s2">"Args:"</span> <span class="o">|</span> <span class="n">var!</span><span class="p">(</span><span class="n">args</span><span class="p">)])</span> <span class="c1"># CORRECT line 28</span></code></pre></figure>

<p>The example finally works.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span>./ast.exs a b c
Args:
a
b
c
ok
process_macro<span class="o">([</span><span class="s2">"Args:"</span> | var!<span class="o">(</span>args<span class="o">)])</span>
process_function<span class="o">([</span><span class="s2">"Args:"</span> | var!<span class="o">(</span>args<span class="o">)])</span>
Args:
a
b
c
ok</code></pre></figure>

<p>AST needs to be defined and executed as below.</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1"># direct invocation</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">process_macro</span><span class="p">([</span><span class="s2">"Args:"</span> <span class="o">|</span> <span class="n">args</span><span class="p">])</span>

<span class="c1"># indirect invocation</span>
<span class="n">ast</span> <span class="o">=</span> <span class="kn">quote</span> <span class="k">do</span>
  <span class="n">process_macro</span><span class="p">([</span><span class="s2">"Args:"</span> <span class="o">|</span> <span class="n">var!</span><span class="p">(</span><span class="n">args</span><span class="p">)])</span>
<span class="k">end</span>

<span class="p">{</span><span class="n">result</span><span class="p">,</span> <span class="n">_binding</span><span class="p">}</span> <span class="o">=</span> <span class="n">ast</span>
<span class="o">|&gt;</span> <span class="no">Code</span><span class="o">.</span><span class="n">eval_quoted</span><span class="p">([</span><span class="ss">args:</span> <span class="n">args</span><span class="p">],</span> <span class="n">__ENV__</span><span class="p">)</span></code></pre></figure>

<h2 id="references">References:</h2>
<ul>
  <li><a href="https://pragprog.com/book/cmelixir/metaprogramming-elixir">Elixir, Metaprogramming Elixir</a></li>
  <li><a href="http://elixir-lang.org/getting-started/meta/macros.html">Elixir, Macros</a></li>
  <li><a href="http://elixir-lang.org/docs/master/elixir/Code.html">Elixir, Code</a></li>
  <li><a href="https://sgeos.github.io/elixir/erlang/2016/01/08/single-file-elixir-programs.html">Elixir, Single File Elixir Programs</a></li>
  <li><a href="irc://irc.freenode.net/elixir-lang">IRC #elixir-lang on Freenode</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="elixir" /><category term="metaprogramming" /><category term="ast" /></entry><entry><title type="html">Elixir as a Service on FreeBSD</title><link href="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html" rel="alternate" type="text/html" title="Elixir as a Service on FreeBSD" /><published>2016-01-16T22:36:31+00:00</published><updated>2016-01-16T22:36:31+00:00</updated><id>https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd</id><content type="html" xml:base="https://sgeos.github.io/elixir/erlang/2016/01/16/elixir-as-a-service_on_freebsd.html"><![CDATA[<!-- A6 -->
<script>console.log("A6");</script>

<p>Ease of deployment is one of the things I seriously consider when evaluating technology solutions.
In this post, a modified version of the Elixir <a href="http://elixir-lang.org/getting-started/mix-otp/task-and-gen-tcp.html">getting started echo server</a> will be installed as a
FreeBSD service.  There are three goals.</p>

<ul>
  <li>The app needs to be able to be rebuilt and the service restarted to reflect changes on a development machine.</li>
  <li>The service needs to automatically start when the machine is booted.</li>
  <li>The service sould work like any other service.</li>
</ul>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service elixir_echo start
service elixir_echo stop
service elixir_echo restart
service elixir_echo status</code></pre></figure>

<p>The steps are a little involved, but ultimately straightforward.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date
</span>January 17, 2016 at 07:37:31 PM JST
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-a</span>
FreeBSD mirage.sennue.com 11.0-CURRENT FreeBSD 11.0-CURRENT <span class="c">#0 r287598: Thu Sep 10 14:45:48 JST 2015     root@:/usr/obj/usr/src/sys/MIRAGE_KERNEL  amd64</span>
<span class="nv">$ </span>elixir <span class="nt">--version</span>
Erlang/OTP 18 <span class="o">[</span>erts-7.2.1] <span class="o">[</span><span class="nb">source</span><span class="o">]</span> <span class="o">[</span>64-bit] <span class="o">[</span>async-threads:10] <span class="o">[</span>hipe] <span class="o">[</span>kernel-poll:false]
Elixir 1.2.0</code></pre></figure>

<h2 id="instructions">Instructions</h2>
<p>elixir needs to be installed.  The tooling to install dependencies for elixir apps comes with the port.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">sudo </span>portmaster lang/elixir</code></pre></figure>

<p>First, start a new project for the echo service.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix new <span class="nt">--sup</span> elixir_echo
<span class="nb">cd </span>elixir_echo</code></pre></figure>

<p>Next, add a modified version of the <a href="http://elixir-lang.org/getting-started/mix-otp/task-and-gen-tcp.html">echo server</a> from the Elixir getting started section.
This version loads the port from environment variables or the app configuration environment.
The default fallback is port 4040.  It also gracefully handles closed sockets.
The code goes in <strong>lib/echo_elixir.ex</strong>.</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">ElixirEcho</span> <span class="k">do</span>
  <span class="kn">use</span> <span class="no">Application</span>
  <span class="kn">require</span> <span class="no">Logger</span>

  <span class="nv">@default_port</span> <span class="mi">4040</span>

  <span class="nv">@doc</span> <span class="no">false</span>
  <span class="k">def</span> <span class="n">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
    <span class="kn">import</span> <span class="no">Supervisor</span><span class="o">.</span><span class="no">Spec</span>

    <span class="n">port</span> <span class="o">=</span> <span class="s2">"PORT"</span> <span class="o">|&gt;</span> <span class="n">get_env</span><span class="p">(</span><span class="nv">@default_port</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="n">to_integer</span><span class="p">(</span><span class="nv">@default_port</span><span class="p">)</span>

    <span class="n">children</span> <span class="o">=</span> <span class="p">[</span>
      <span class="n">supervisor</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="no">Supervisor</span><span class="p">,</span> <span class="p">[[</span><span class="ss">name:</span> <span class="no">ElixirEcho</span><span class="o">.</span><span class="no">TaskSupervisor</span><span class="p">]]),</span>
      <span class="n">worker</span><span class="p">(</span><span class="no">Task</span><span class="p">,</span> <span class="p">[</span><span class="no">ElixirEcho</span><span class="p">,</span> <span class="ss">:accept</span><span class="p">,</span> <span class="p">[</span><span class="n">port</span><span class="p">]])</span>
    <span class="p">]</span>

    <span class="n">opts</span> <span class="o">=</span> <span class="p">[</span><span class="ss">strategy:</span> <span class="ss">:one_for_one</span><span class="p">,</span> <span class="ss">name:</span> <span class="no">ElixirEcho</span><span class="o">.</span><span class="no">Supervisor</span><span class="p">]</span>
    <span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="nv">@doc</span> <span class="sd">"""
  Starts accepting connections on the given `port`.
  """</span>
  <span class="k">def</span> <span class="n">accept</span><span class="p">(</span><span class="n">port</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">socket</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="n">port</span><span class="p">,</span>
                      <span class="p">[</span><span class="ss">:binary</span><span class="p">,</span> <span class="ss">packet:</span> <span class="ss">:line</span><span class="p">,</span> <span class="ss">active:</span> <span class="no">false</span><span class="p">,</span> <span class="ss">reuseaddr:</span> <span class="no">true</span><span class="p">])</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span> <span class="s2">"Accepting connections on port </span><span class="si">#{</span><span class="n">port</span><span class="si">}</span><span class="s2">"</span>
    <span class="n">loop_acceptor</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">loop_acceptor</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">client</span><span class="p">}</span> <span class="o">=</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">accept</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">pid</span><span class="p">}</span> <span class="o">=</span> <span class="no">Task</span><span class="o">.</span><span class="no">Supervisor</span><span class="o">.</span><span class="n">start_child</span><span class="p">(</span><span class="no">ElixirEcho</span><span class="o">.</span><span class="no">TaskSupervisor</span><span class="p">,</span> <span class="k">fn</span> <span class="o">-&gt;</span> <span class="n">serve</span><span class="p">(</span><span class="n">client</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span>
    <span class="ss">:ok</span> <span class="o">=</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">controlling_process</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">pid</span><span class="p">)</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span> <span class="s2">"Opened connection"</span>
    <span class="n">loop_acceptor</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">serve</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">status</span> <span class="o">=</span> <span class="n">socket</span>
    <span class="o">|&gt;</span> <span class="n">read_line</span><span class="p">()</span>
    <span class="o">|&gt;</span> <span class="n">write_line</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>

    <span class="k">case</span> <span class="n">status</span> <span class="k">do</span>
      <span class="ss">:ok</span> <span class="o">-&gt;</span>
        <span class="n">serve</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
      <span class="ss">:closed</span> <span class="o">-&gt;</span>
        <span class="no">Logger</span><span class="o">.</span><span class="n">info</span> <span class="s2">"Closed connection"</span>
        <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span>
        <span class="ss">:ok</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">read_line</span><span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">data</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="n">data</span>
      <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="ss">:closed</span><span class="p">}</span> <span class="o">-&gt;</span>
        <span class="ss">:closed</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">defp</span> <span class="n">write_line</span><span class="p">(</span><span class="ss">:closed</span><span class="p">,</span> <span class="n">_socket</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:closed</span>
  <span class="k">defp</span> <span class="n">write_line</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="n">socket</span><span class="p">)</span> <span class="k">do</span>
    <span class="ss">:gen_tcp</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">socket</span><span class="p">,</span> <span class="n">line</span><span class="p">)</span>
    <span class="no">Logger</span><span class="o">.</span><span class="n">info</span> <span class="s2">"Echo: </span><span class="si">#{</span><span class="n">line</span> <span class="o">|&gt;</span> <span class="no">String</span><span class="o">.</span><span class="n">strip</span><span class="si">}</span><span class="s2">"</span>
    <span class="ss">:ok</span>
  <span class="k">end</span>

  <span class="c1"># Utility Functions</span>

  <span class="k">def</span> <span class="n">get_env</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">default</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="p">(</span>
      <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="o">||</span>
      <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:elixir_echo</span><span class="p">,</span> <span class="n">name</span> <span class="o">|&gt;</span> <span class="no">String</span><span class="o">.</span><span class="n">downcase</span> <span class="o">|&gt;</span> <span class="no">String</span><span class="o">.</span><span class="n">to_atom</span><span class="p">)</span>
    <span class="p">)</span> <span class="o">|&gt;</span> <span class="n">to_string</span>
    <span class="k">do</span>
      <span class="s2">""</span> <span class="o">-&gt;</span> <span class="n">default</span> <span class="o">|&gt;</span> <span class="n">to_string</span>
      <span class="n">result</span> <span class="o">-&gt;</span> <span class="n">result</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">def</span> <span class="n">to_integer</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">_default</span><span class="p">)</span> <span class="ow">when</span> <span class="n">is_integer</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">x</span>
  <span class="k">def</span> <span class="n">to_integer</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">default</span><span class="p">)</span> <span class="ow">when</span> <span class="n">is_binary</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">case</span> <span class="n">x</span> <span class="o">|&gt;</span> <span class="no">Integer</span><span class="o">.</span><span class="n">parse</span> <span class="k">do</span>
      <span class="p">{</span><span class="n">result</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">result</span>
      <span class="ss">:error</span> <span class="o">-&gt;</span> <span class="n">default</span>
    <span class="k">end</span>
  <span class="k">end</span>
  <span class="k">def</span> <span class="n">to_integer</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">default</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="n">default</span>
<span class="k">end</span></code></pre></figure>

<p>Add the <a href="https://github.com/bitwalker/exrm">elixir release manager (exrm)</a> to <strong>mix.exs</strong> as a project dependency.</p>

<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir">  <span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
    <span class="p">[{</span><span class="ss">:exrm</span><span class="p">,</span> <span class="s2">"~&gt; 1.0.0-rc7"</span><span class="p">}]</span>
  <span class="k">end</span></code></pre></figure>

<p>Install exrm and build a release.  This will create the <strong>rel/</strong> directory.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix deps.get
mix deps.compile
mix release</code></pre></figure>

<p>The rc script will use environment variable knobs to configure the app.
Note that the <strong>RELX_REPLACE_OS_VARS=true</strong> environment variable needs to be defined to use environment variables for dynamic configuration.</p>

<p>The <strong>vm.args</strong> file is primarily used to configure the erlang VM.
It can also be used to define application configure parameters.
Application configuration parameters defined in this file can be passed into the program as atoms or integers.
Add the following to <strong>rel/vm.args</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">## Name of the node</span>
<span class="nt">-name</span> <span class="k">${</span><span class="nv">NODE_NAME</span><span class="k">}</span>

<span class="c">## Cookie for distributed erlang</span>
<span class="nt">-setcookie</span> <span class="k">${</span><span class="nv">COOKIE</span><span class="k">}</span>

<span class="c">## App Settings</span>
<span class="nt">-elixir_echo</span> port <span class="k">${</span><span class="nv">PORT</span><span class="k">}</span></code></pre></figure>

<p>Alternatively, <strong>sys.config</strong> can be used to pass in application configuration parameters.
In this file, application configuration parameters defined with environment variables must be strings.
Pass the port setting in as above or add the following to <strong>rel/sys.config</strong>.
The app module was written to work with either solution.
Adding both files will not break anything.</p>

<figure class="highlight"><pre><code class="language-erlang" data-lang="erlang"><span class="p">[</span>
  <span class="p">{</span><span class="n">elixir_echo</span><span class="p">,</span> <span class="p">[</span>
    <span class="p">{</span><span class="n">port</span><span class="p">,</span> <span class="s">"${PORT}"</span><span class="p">}</span>
  <span class="p">]}</span>
<span class="p">].</span></code></pre></figure>

<p>Build a release with the configuration files and do the initial install.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix release
su
sh
<span class="nv">PROJECT</span><span class="o">=</span>elixir_echo
<span class="nv">INSTALL_DIR</span><span class="o">=</span>/usr/local/opt
<span class="nv">VERSION</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat </span>rel/<span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nv">INSTALL_TAR</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/rel/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.tar.gz
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>
<span class="o">(</span><span class="nb">cd</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span><span class="p">;</span> <span class="se">\</span>
<span class="nb">tar</span> <span class="nt">-xf</span> <span class="nv">$INSTALL_TAR</span><span class="o">)</span>
pw adduser <span class="nv">$PROJECT</span> <span class="nt">-d</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span> <span class="nt">-s</span> /usr/sbin/nologin <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$PROJECT</span><span class="s2"> system service user"</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span></code></pre></figure>

<p>An rc script defines the the service.
<strong>elixir_echo_run()</strong> is called from the other functions.
It configures and calls the release.
<strong>HOME</strong> is set to the installation directory to force the erlang cookie file to be written there regardless of <strong>elixir_echo_user</strong> setting.
<strong>elixir_echo_status()</strong> echoes a user friendly message if the release can be pinged.
Add <strong>shutdown</strong> to the keyword list if the service needs to gracefull shutdown when the machine restarts.
The rest is standard rc configuration.
Add the rc script to <strong>/usr/local/etc/rc.d/elixir_echo</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>
<span class="c">#</span>
<span class="c"># PROVIDE: elixir_echo</span>
<span class="c"># REQUIRE: networking</span>
<span class="c"># KEYWORD:</span>
 
<span class="nb">.</span> /etc/rc.subr
 
<span class="nv">name</span><span class="o">=</span><span class="s2">"elixir_echo"</span>
<span class="nv">rcvar</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_enable"</span>
<span class="nv">install_dir</span><span class="o">=</span><span class="s2">"/usr/local/opt/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">version</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nb">command</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">/bin/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span>
 
<span class="nv">start_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_start"</span>
<span class="nv">stop_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_stop"</span>
<span class="nv">status_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">_status"</span>

load_rc_config <span class="nv">$name</span>
: <span class="k">${</span><span class="nv">elixir_echo_enable</span>:<span class="p">=</span><span class="s2">"no"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">elixir_echo_port</span>:<span class="p">=</span><span class="s2">"4040"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">elixir_echo_user</span>:<span class="p">=</span><span class="k">${</span><span class="nv">name</span><span class="k">}}</span>
: <span class="k">${</span><span class="nv">elixir_echo_node_name</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">@127.0.0.1"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">elixir_echo_cookie</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2">"</span><span class="k">}</span>
: <span class="k">${</span><span class="nv">elixir_echo_config_dir</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">/releases/</span><span class="k">${</span><span class="nv">version</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2"> start"</span><span class="k">}</span>

elixir_echo_run<span class="o">()</span>
<span class="o">{</span>
  <span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
  <span class="nv">HOME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">install_dir</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">RELEASE_CONFIG_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">elixir_echo_config</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">NODE_NAME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">elixir_echo_node_name</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">COOKIE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">elixir_echo_cookie</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nv">PORT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">elixir_echo_port</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
  su <span class="nt">-m</span> <span class="s2">"</span><span class="nv">$elixir_echo_user</span><span class="s2">"</span> <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$command</span><span class="s2"> </span><span class="nv">$1</span><span class="s2">"</span>
<span class="o">}</span>

elixir_echo_start<span class="o">()</span>
<span class="o">{</span>
  elixir_echo_run start
<span class="o">}</span>

elixir_echo_stop<span class="o">()</span>
<span class="o">{</span>
  elixir_echo_run stop
<span class="o">}</span>

elixir_echo_status<span class="o">()</span>
<span class="o">{</span>
  <span class="nv">ping_result</span><span class="o">=</span><span class="sb">`</span>elixir_echo_run ping<span class="sb">`</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ping_result</span><span class="k">}</span><span class="s2">"</span>
  <span class="k">case</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ping_result</span><span class="k">}</span><span class="s2">"</span> <span class="k">in</span>
    <span class="k">*</span>pong<span class="k">*</span><span class="p">)</span>
      <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">name</span><span class="k">}</span><span class="s2"> is running."</span>
      <span class="p">;;</span>
  <span class="k">esac</span>
<span class="o">}</span>

load_rc_config <span class="nv">$name</span>
run_rc_command <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span></code></pre></figure>

<p>Enable and configure the service in <strong>/etc/rc.conf</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">elixir_echo_enable</span><span class="o">=</span><span class="s2">"YES"</span>
<span class="nv">elixir_echo_port</span><span class="o">=</span>8255
<span class="nv">elixir_echo_node_name</span><span class="o">=</span><span class="s2">"parrot"</span>
<span class="nv">elixir_echo_cookie</span><span class="o">=</span><span class="s2">"cracker"</span></code></pre></figure>

<p>The service can now be started.  If the service is enabled, it will automatically start when the machine boots.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">service elixir_echo start
telnet 127.0.0.1 8255 <span class="c"># make sure it works</span></code></pre></figure>

<h3 id="optional-adding-a-release-to-the-systemwide-path">Optional: Adding a Release to the Systemwide Path</h3>

<p>Adding a release to the systemwide path is not necessary, but it can be convenient.
The pass through script can be pointed at the development install instead of the service install if you want to build with exrm <strong>mix release –dev</strong>.</p>

<p>Create a directory for the convenience pass through script.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/bin</code></pre></figure>

<p><strong>NODE_NAME</strong> and <strong>COOKIE</strong> need default values because <strong>vm.args</strong> has no useful default fallbacks.
<strong>PORT</strong> has a fallback.
Add the script to <strong>/usr/local/opt/bin/elixir_echo</strong></p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c">#!/bin/sh</span>
<span class="nv">SCRIPT</span><span class="o">=</span><span class="si">$(</span><span class="nb">realpath</span> <span class="nv">$0</span><span class="si">)</span>
<span class="nv">BASENAME</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$SCRIPT</span><span class="si">)</span>
<span class="nv">BASEDIR</span><span class="o">=</span><span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$SCRIPT</span><span class="si">)</span>
<span class="nv">COMMAND</span><span class="o">=</span><span class="si">$(</span><span class="nb">realpath</span> <span class="nv">$BASEDIR</span>/../<span class="nv">$BASENAME</span>/bin/<span class="nv">$BASENAME</span><span class="si">)</span>

: <span class="k">${</span><span class="nv">NODE_NAME</span>:<span class="p">=</span><span class="k">${</span><span class="nv">BASENAME</span><span class="k">}}</span>
: <span class="k">${</span><span class="nv">COOKIE</span>:<span class="p">=</span><span class="k">${</span><span class="nv">BASENAME</span><span class="k">}}</span>
<span class="nv">NODE_NAME</span><span class="o">=</span><span class="nv">$NODE_NAME</span> <span class="nv">COOKIE</span><span class="o">=</span><span class="nv">$COOKIE</span> <span class="nv">RELX_REPLACE_OS_VARS</span><span class="o">=</span><span class="nb">true</span> <span class="nv">$COMMAND</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span></code></pre></figure>

<p>Make the script executable.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod</span> +x <span class="nv">$INSTALL_DIR</span>/bin/<span class="nv">$PROJECT</span></code></pre></figure>

<p>Add <strong>/usr/local/opt/bin</strong> to the global path in <strong>/etc/profile</strong> for <strong>sh</strong>.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">PATH</span><span class="o">=</span>/usr/local/opt/bin:<span class="nv">$PATH</span>
<span class="nb">export </span>PATH</code></pre></figure>

<p>Add <strong>/usr/local/opt/bin</strong> to the global path in <strong>/etc/csh.cshrc</strong> for <strong>csh</strong>.
Consider updating the root path in <strong>/root/.cshrc</strong>.</p>

<figure class="highlight"><pre><code class="language-csh" data-lang="csh">set path=(/usr/local/opt/bin $path)</code></pre></figure>

<p>Update the path in the current shell if necessary.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># sh bash</span>
<span class="nb">source</span> /etc/csh.cshrc
<span class="c"># csh tcsh</span>
<span class="nb">.</span> /etc/profile</code></pre></figure>

<p>Fix permissions if you want to be able to run as any user.  This has security implications.
It may make more sense to point the script at the development release if it is being used as a development convenience.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/bin/<span class="nv">$PROJECT</span>
<span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/bin/nodetool
<span class="nb">chmod </span>755 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.sh
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/log/<span class="k">*</span>.<span class="k">*</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/tmp/erl_pipes/<span class="nv">$PROJECT</span>
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/tmp/erl_pipes/<span class="nv">$PROJECT</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/
<span class="nb">chmod </span>777 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/
<span class="nb">chmod </span>666 <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>/running-config/<span class="k">*</span>.<span class="k">*</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span></code></pre></figure>

<p>The release can now be conveniently controlled.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">NODE_NAME</span><span class="o">=</span>canary <span class="nv">COOKIE</span><span class="o">=</span>sesame <span class="nv">PORT</span><span class="o">=</span>5678 elixir_echo start
telnet 127.0.0.1 5678 <span class="c"># make sure it works</span></code></pre></figure>

<h3 id="setup-complete">Setup Complete</h3>

<p>Switch from root to a normal user.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">exit</span> <span class="c"># sh</span>
<span class="nb">exit</span> <span class="c"># su</span></code></pre></figure>

<h3 id="updating">Updating</h3>

<p>Casual updates on a development machine can be performed as follows.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">mix release
su
sh
<span class="nv">PROJECT</span><span class="o">=</span>elixir_echo
<span class="nv">INSTALL_DIR</span><span class="o">=</span>/usr/local/opt
<span class="nv">VERSION</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat </span>rel/<span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span>/releases/start_erl.data | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">' '</span> <span class="nt">-f2</span><span class="si">)</span>
<span class="nv">INSTALL_TAR</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/rel/<span class="nv">$PROJECT</span>/releases/<span class="nv">$VERSION</span>/<span class="nv">$PROJECT</span>.tar.gz
<span class="o">(</span><span class="nb">cd</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span><span class="p">;</span> <span class="se">\</span>
<span class="nb">tar</span> <span class="nt">-xf</span> <span class="nv">$INSTALL_TAR</span><span class="o">)</span>
<span class="nb">chown</span> <span class="nt">-R</span> <span class="nv">$PROJECT</span>:<span class="nv">$PROJECT</span> <span class="nv">$INSTALL_DIR</span>/<span class="nv">$PROJECT</span>
service <span class="nv">$PROJECT</span> restart
<span class="nb">exit</span> <span class="c"># sh</span>
<span class="nb">exit</span> <span class="c"># su</span></code></pre></figure>

<h3 id="troubleshooting">Troubleshooting</h3>

<p>If configuration looks like it should be working, but nothing changes, try deleting the <strong>running-config</strong> directory.
Sometimes rebooting fixes problems, like killing zombie nodes with strange default names and cookie values.</p>

<h3 id="what-next">What Next?</h3>

<p>Consider looking into <a href="https://github.com/boldpoker/edeliver">edeliver</a> for deployment.
“edeliver is based on deliver and provides a bash script to build and deploy
Elixir and Erlang applications and perform hot-code upgrades.”</p>

<h2 id="references">References:</h2>
<ul>
  <li><a href="http://elixir-lang.org/getting-started/mix-otp/task-and-gen-tcp.html">Elixir, Task and gen_tcp</a></li>
  <li><a href="http://elixir-lang.org/docs/v1.0/elixir/System.html#get_env/1">Elixir, System</a></li>
  <li><a href="https://github.com/bitwalker/exrm">Elixir Release Manager</a></li>
  <li><a href="https://exrm.readme.io/docs/release-configuration">Elixir exrm Release Configuration</a></li>
  <li><a href="https://hexdocs.pm/exrm/extra-deployment.html">Elixir exrm Deployment</a></li>
  <li><a href="https://github.com/boldpoker/edeliver">Elixir, edeliver</a></li>
  <li><a href="https://pragprog.com/book/elixir12/programming-elixir-1-2">Elixir, Programming Elixir 1.2</a></li>
  <li><a href="https://forums.freebsd.org/threads/opt-directory-replacement.12614/">FreeBSD Forums, /opt directory replacement</a></li>
  <li><a href="https://forums.freebsd.org/threads/creating-a-system-user-like-www.2152/">FreeBSD Forums, Creating a System User (like www)</a></li>
  <li><a href="https://forums.freebsd.org/threads/configurable-rc-d-script-template.53308/">FreeBSD Forums, Configurable rc.d Script Template?</a></li>
  <li><a href="https://www.freebsd.org/doc/en/articles/rc-scripting/index.html">FreeBSD, Practical rc.d scripting in BSD</a></li>
  <li><a href="https://www.freebsd.org/cgi/man.cgi?query=rc.subr&amp;sektion=8">FreeBSD Man Pages, rc.subr(8)</a></li>
  <li><a href="https://www.freebsd.org/doc/handbook/shells.html">FreeBSD Handbook, Shells</a></li>
  <li><a href="http://stackoverflow.com/questions/285658/run-as-different-user-under-freebsd">FreeBSD, Run as different user under FreeBSD (closed)</a></li>
  <li><a href="http://unix.stackexchange.com/questions/11544/what-is-the-difference-between-opt-and-usr-local">UNIX, What is the difference between /opt and /usr/local?</a></li>
  <li><a href="http://stackoverflow.com/questions/1537673/how-do-i-forward-parameters-to-other-command-in-bash-script">UNIX, How do I forward parameters to other command in bash script?</a></li>
  <li><a href="https://www.joedog.org/articles-cheat-sheet/">UNIX, sh Scripting Cheat Sheet</a></li>
  <li><a href="http://superuser.com/questions/260925/how-can-i-make-chown-work-recursively">UNIX, How can I make chown work recursively?</a></li>
  <li><a href="http://stackoverflow.com/questions/229551/string-contains-in-bash/229585#229585">UNIX, String contains in bash</a></li>
  <li><a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_03.html">UNIX, bash Using case statements</a></li>
  <li><a href="http://www.greenend.org.uk/rjk/tech/useradd.html">UNIX, Creating System Users And Groups</a></li>
  <li><a href="http://www.erlang.org/doc/reference_manual/distributed.html">Erlang, Distributed Erlang</a></li>
  <li><a href="http://www.erlang.org/doc/man/config.html">Erlang, Config</a></li>
  <li><a href="http://aerosol.github.io/anxibits/little-known-fact-about-erlang-sys-config/">Erlang, A little known fact about Erlang’s sys.config</a></li>
  <li><a href="https://www.rebar3.org/docs/releases">Erlang, rebar Releases</a></li>
  <li><a href="irc://irc.freenode.net/elixir-lang">IRC #elixir-lang on Freenode</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="elixir" /><category term="erlang" /></entry></feed>