<?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/phoenix.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/phoenix.xml</id><title type="html">Brendan A R Sechter’s Development Blog | Phoenix</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">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></feed>