<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://danivovich.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://danivovich.com/" rel="alternate" type="text/html"/><updated>2026-01-12T21:55:58+00:00</updated><id>https://danivovich.com/feed.xml</id><title type="html">Dan Ivovich</title><subtitle>The personal website and blog of Dan Ivovich</subtitle><author><name>Dan Ivovich</name></author><entry><title type="html">My DIY Peloton</title><link href="https://danivovich.com/blog/2020/05/18/diy-peloton/" rel="alternate" type="text/html" title="My DIY Peloton"/><published>2020-05-18T12:00:00+00:00</published><updated>2020-05-18T12:00:00+00:00</updated><id>https://danivovich.com/blog/2020/05/18/diy-peloton</id><content type="html" xml:base="https://danivovich.com/blog/2020/05/18/diy-peloton/"><![CDATA[<p>Longer ago than I’d like to admit, I put together a DIY Peloton bike to participate in Peloton Digital classes. I used it pretty consistently at first, but never formed a habit.</p> <p>While I’ve wanted to post about the project for a while, not using the setup didn’t motivate me to write about it. Here in spring 2020, SARS-CoV-2 (COVID-19) means canceled weekend plans, no trips, and no commute. All of that means plenty of time to build a new exercise habit!</p> <p>I’ve reinvigorated my love of the setup, the classes, and my motivation to share how I set it all up. So now that I’ve been pretty much relegated to home for 9 weeks, I’ve built a new habit, and made some tweaks to the setup. So let’s dive in!</p> <h2 id="why">Why</h2> <p><a href="https://www.onepeloton.com/">Peloton</a> has become an at home exercise giant, but it also comes with a significant price tag. For a while now, they’ve offered their digital content, almost all of the cycling workouts, strength training, and to the best of my knowledge, every new program they’ve added to their All-Access Membership. Take a look at the internet, and you’ll see lots of people who use the Peloton Digital Membership via tablets, phones, or the web.</p> <p>From my perspective, this DIY setup was a great way to save some money, and see if this style of exercise would work well for me. I had a hunch that it would, given that I used to enjoy spin classes at a local gym (never got into that habit either, despite enjoying them). This investment felt way lower risk than the almost $3,000 first year for the Peloton bike, accessories, and 12 months of the subscription.</p> <h2 id="the-bike">The Bike</h2> <p>For the basic bike, I followed many internet recommendations and got the <a href="https://sunnyhealthfitness.com/products/sunny-health-and-fitness-sf-b1002-belt-drive-indoor-cycling-bike">Sunny Health &amp; Fitness 49 Lb Chromed Flywheel, Silent Belt Drive Indoor Cycle Bike with Leather Resistance Pad</a>. Despite that insanely long name, the bike is a solid, and affordable (or at least it was much more affordable when I purchased it)</p> <p>Overall, it is a solid bike, relatively quiet operation for friction resistance, easy to maintain, easy to enhance, and it feels like it will last a long time.</p> <h2 id="the-accessories">The Accessories</h2> <p>Just having the bike is the bare minimum to a great workout. There are a few items you’ll want to add right away to make the most of your bike and the Peloton Digital Membership.</p> <p>Unless you can place your bike in front of a TV with compatible streaming support, you’ll need a tablet or phone in front of you. I opted to place both an old iPad I had already, and my phone, on the handlebars of the bike.</p> <ul> <li><a href="https://www.amazon.com/gp/product/B01LXLO3VJ">Phone Holder</a></li> <li><a href="https://www.amazon.com/gp/product/B06WVNXGBY">Tablet Holder</a></li> </ul> <p><img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2020-05-diy-peloton/mounts.jpg" alt="The Phone and Tablet Holders"/></p> <p>This allows me to stream the Peloton classes as well as have my phone for another task. I use the phone and a couple Bluetooth accessories to help with measuring my effort and keeping in sync with the class.</p> <ul> <li><a href="https://www.wahoofitness.com/devices/bike-sensors/wahoo-rpm-cadence-sensor">Wahoo Fitness Cadence Sensor</a></li> <li><a href="https://www.wahoofitness.com/devices/heart-rate-monitors/tickr/buy">Wahoo Fitness Tickr Heart Monitor</a></li> </ul> <p>The cadence sensor is critical to measuring my effort during the class, and the instructors call out an expected cadence while the class is ongoing. Using the Wahoo app on my phone allows me to keep the display of cadence in front of my while the class content streams to the iPad. The heart monitor is useful to measure how hard I’m working, and helps the Wahoo app give me an estimate on the calories burned. Since neither the iPad app nor the Wahoo app can measure the resistance setting of the bike, heart rate is the best measure of output I’ve got to know how hard I’m working.</p> <p>Finally, I want to make it easy to keep the iPad and my phone charged, so a simple USB charger and some long 6ft cables zip-tied to the bike frame make it easy.</p> <ul> <li><a href="https://www.amazon.com/gp/product/B00P936188">USB Charger for Phone and Tablet</a></li> </ul> <p><img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2020-05-diy-peloton/charging.jpg" alt="The Charger"/></p> <p>The end result looks pretty good!</p> <p><img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2020-05-diy-peloton/bike_1.jpg" alt="The Setup"/></p> <h2 id="the-app">The App</h2> <p>The Peloton Digital apps are pretty good. On my iPad it provides a decent view of all the classes, good options to filter, and the ability to preload the video so you don’t need to stream a previously recorded class on demand. With the Peloton Digital Membership, I’ve event been able to participate in live classes. My iPad is pretty old and no longer receives iOS updates from Apple, so I believe I’m no longer getting updates of the Peloton app itself, but so far it is still working.</p> <p>On Android, the app is very similar, and would meet your basic needs. It doesn’t appear to allow for preload of classes, and the profile stats are more limited. The Android app also supports Chromecast, which is a handy way to get the video content onto a TV. I’ve used this approach to leverage some of the other content (stretching, strength, etc).</p> <p>One other thing to know, the Peloton Digital Membership is only for a single member, where the Peloton All-Access Membership supports multiple users.</p> <h2 id="issues">Issues</h2> <h3 id="bike">Bike</h3> <p>If you are going to have an issue with the bike off the bat, the fit is likely to be your issue. After several rides I have gotten the settings how I’d like, but you don’t have a ton of flexibility with the various adjustments. The other item to be aware of is that as a friction resistance bike, it can be a bit loud when you are moving quick under a higher resistance.</p> <p>The largest annoyance by far is that you have no real measurement of resistance. The Peloton bike provides a numeric value for resistance and the instructors will call out ranges for you to set and work within. Without a numeric measurement and also that range not matching that of the Peloton bike, you are working blind in this area. The good news is that you’ll get a feel for how hard you should be working, just as relative effort compared to other moments, and the instructors will describe the effort as a flat road, moderate hill, etc.</p> <h3 id="technology">Technology</h3> <p>Chromecast from Android, seriously, what? I haven’t tried this in a while, but the Android initiated Chromecast support is very problematic, and in my experience stutters and buffers pretty often, and then will rewind about a minute to resume playing. This is frustrating and really makes it useless.</p> <p>What is more infuriating is that you can also initiate the Chromecast playback from the web version of the Peloton Digital app, and that plays fine. My understanding of Chromecast is that it sets up a direct stream from the content provider servers to the Chromecast, so the initiation method should be irrelevant to the quality of the playback. It seems that all Peloton Chromecasting should perform equally. It doesn’t truly matter however, as I really only use the iPad on the bike, and when I want to stream other classes to a Chromecast, I can initiate them from the Chrome browser on my phone.</p> <h2 id="enhancements">Enhancements</h2> <p>As I started to get more active with the bike in March, I decided it would be nice to make a small enhancement. I opted to replace the pedals with SPD clip pedals, and bought some SPD shoes. I’ve never used clip-in pedals on any bike before. The pedals I bought are clip-in on one side, so I don’t need to put on the shoes every time, or someone else can use the bike SPD shoes. There is something nice about the ritual of clipping-in for a workout, as well as it does feel more solid and effective to cycle with the clip-in shoes.</p> <p><img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2020-05-diy-peloton/bike_2.jpg" alt="Fully Loaded"/></p> <h2 id="cost-comparison">Cost Comparison</h2> <p>The main goal of the DIY Peloton is to save money, so let’s take a look.</p> <p>At the time of purchase:</p> <table> <thead> <tr> <th>Item</th> <th style="text-align: center">Cost September 2018</th> <th style="text-align: center">Cost May 2020</th> </tr> </thead> <tbody> <tr> <td><a href="https://www.amazon.com/dp/B00467H5YW">Bike</a></td> <td style="text-align: center">~$300</td> <td style="text-align: center">~$600</td> </tr> <tr> <td><a href="https://www.amazon.com/gp/product/B06WVNXGBY">Tablet Holder</a></td> <td style="text-align: center">~$23</td> <td style="text-align: center"><a href="https://www.amazon.com/dp/B07LCJ83S7">~$14</a> (another option)</td> </tr> <tr> <td><a href="https://www.amazon.com/dp/B071Z5T9N5">Phone Holder</a></td> <td style="text-align: center">~$13</td> <td style="text-align: center">~$28</td> </tr> <tr> <td><a href="https://www.amazon.com/gp/product/B001THTUAO">Bike Mat</a></td> <td style="text-align: center">~$30</td> <td style="text-align: center">~$33</td> </tr> <tr> <td><a href="https://www.amazon.com/dp/B00L9XNFPY">Cadence Sensor</a></td> <td style="text-align: center">~$40</td> <td style="text-align: center">~$40</td> </tr> <tr> <td><a href="https://www.amazon.com/gp/product/B00INQVYZ8">Heart Monitor</a></td> <td style="text-align: center">~$50</td> <td style="text-align: center"><a href="https://www.amazon.com/dp/B00O5Y4FXA">~$80</a> (another option)</td> </tr> <tr> <td><a href="https://www.amazon.com/gp/product/B00P936188">USB Charging Block</a> and Cables</td> <td style="text-align: center">~$46</td> <td style="text-align: center">~$22 + Cables</td> </tr> <tr> <td>iPad</td> <td style="text-align: center">Already Owned</td> <td style="text-align: center"><a href="https://www.apple.com/ipad-10.2/">~$330</a></td> </tr> <tr> <td>Weights</td> <td style="text-align: center">Already Owned</td> <td style="text-align: center"><a href="https://www.target.com/p/hand-weight-3lb-lilac-all-in-motion-8482/-/A-77459834">~$11</a></td> </tr> <tr> <td><strong>Total</strong></td> <td style="text-align: center"><strong>~$502</strong></td> <td style="text-align: center"><strong>~$1158</strong></td> </tr> </tbody> </table> <p>Enhancement Items Added Spring 2020</p> <table> <thead> <tr> <th>Item</th> <th style="text-align: center">Cost</th> </tr> </thead> <tbody> <tr> <td>SPD Shoes</td> <td style="text-align: center"><a href="https://www.rei.com/product/141419/shimano-me3-mountain-bike-shoes-mens">~$100</a></td> </tr> <tr> <td>SPD Pedals</td> <td style="text-align: center"><a href="https://www.amazon.com/gp/product/B000NORMU4">~$38</a></td> </tr> </tbody> </table> <p>Recurring Cost:</p> <table> <thead> <tr> <th>Item</th> <th style="text-align: center">Peloton Digital</th> <th style="text-align: center">Peloton All-Access</th> </tr> </thead> <tbody> <tr> <td>Monthly</td> <td style="text-align: center">~$14</td> <td style="text-align: center">~$39</td> </tr> <tr> <td>Annually</td> <td style="text-align: center">~$168</td> <td style="text-align: center">~$468</td> </tr> </tbody> </table> <p>In May 2020, looking at the “<a href="https://www.onepeloton.com/shop/bike/the-works-package">Works Package</a>” from Peloton, which has a similar setup of gear (shoes, mat, weights, heart monitor, etc), we are looking at a retail price of $2,494. Looking then at the 1 year costs, the ownership of the Peloton is $2,962. My first year cost of ownership (given my already purchased iPad and weights) was $670. One note, the Peloton Digital Membership used to be a bit more, but I don’t recall when it exactly went down. Even if I had needed to purchase the weights and iPad, I’d be looking at a one year cost of ownership of $1,011. For a new purchase today, with slightly higher costs on several items, the first year would cost $1,326. The DIY approach saves at least half the cost of the full retail Peloton.</p> <h2 id="conclusion">Conclusion</h2> <p>This is a great option! You can get started for far less than the ‘real’ bike. There are downsides, but they don’t stop you from getting a great workout or building positive habits. I’m certain the Peloton Bike is very nice, and the software that goes with it appears to be much stronger and more engaging software. Ultimately I recommend the exercise program that Peloton has developed, and would recommend it to anyone interested. How you decide to get involved, and what level of investment you make is of course up to you!</p> <p>It always helps to have a gym buddy!</p> <p><img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2020-05-diy-peloton/gym_buddy.jpg" alt="Always good to have a buddy"/></p>]]></content><author><name>Dan Ivovich</name></author><category term="tech-adjacent"/><category term="exercise"/><category term="peloton"/><summary type="html"><![CDATA[Longer ago than I’d like to admit, I put together a DIY Peloton bike to participate in Peloton Digital classes. I used it pretty consistently at first, but never formed a habit.]]></summary></entry><entry><title type="html">pre-commit for Elixir Projects</title><link href="https://danivovich.com/blog/2018/11/01/elixir-pre-commit/" rel="alternate" type="text/html" title="pre-commit for Elixir Projects"/><published>2018-11-01T00:00:00+00:00</published><updated>2018-11-01T00:00:00+00:00</updated><id>https://danivovich.com/blog/2018/11/01/elixir-pre-commit</id><content type="html" xml:base="https://danivovich.com/blog/2018/11/01/elixir-pre-commit/"><![CDATA[<p>In the new project I mentioned in the <a href="https://danivovich.com/blog/2018/10/29/testing-elixir-with-gitlab/">previous post</a>, I’m focusing on practicing good habits, which my CI setup is a large part of. The other big part is keeping things clean and well written before they are even committed into the repository.</p> <p>To accomplish this task, I set up <a href="https://pre-commit.com/">pre-commit</a>. Pre-commit is a really easy way to set up some useful git pre-commit hooks, as well as any custom command you want to run.</p> <h2 id="install">Install</h2> <p>Pre-commit can be installed into your python setup, or on OSX, use homebrew and run <code class="language-plaintext highlighter-rouge">brew install pre-commit</code></p> <p>You’ll create a <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> in the root of the git working directory that describes the hooks you want to fire on pre-commit.</p> <p>Then you can setup your actual hook to fire everything described in <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> by running <code class="language-plaintext highlighter-rouge">pre-commit install</code>. You can then modify the <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code> file and adjust your hooks without another install. If you ever need to skip this stuff for a commit, just add <code class="language-plaintext highlighter-rouge">--no-verify</code> to the commit command (but don’t! fix the problem instead!)</p> <h2 id="basics">Basics</h2> <p>I’ll start with the common hooks, defined in <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code>:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">git://github.com/pre-commit/pre-commit-hooks</span>
  <span class="na">sha</span><span class="pi">:</span> <span class="s">v1.4.0</span>
  <span class="na">hooks</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">trailing-whitespace</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-merge-conflict</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-yaml</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">end-of-file-fixer</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">no-commit-to-branch</span>
    <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">-b</span><span class="pi">,</span> <span class="nv">master</span><span class="pi">,</span> <span class="nv">-b</span><span class="pi">,</span> <span class="nv">production</span><span class="pi">,</span> <span class="nv">-b</span><span class="pi">,</span> <span class="nv">staging</span><span class="pi">]</span>
</code></pre></div></div> <p>Here I’m grabbing the hooks that are public and part of the pre-commit core. I’m using a couple specific ones for things I care about:</p> <ul> <li><strong>trailing-whitespace</strong>: Making sure that I don’t leave any extra spaces at the end of lines</li> <li><strong>check-merge-conflict</strong>: Making sure we never commit any unresolved merge conflicts</li> <li><strong>check-yaml</strong>: Verify yaml syntax and make sure all files are valid before committing them</li> <li><strong>end-of-file-fixer</strong>: Make sure my files end in a newline, and only a newline</li> <li><strong>no-commit-to-brach</strong>: Force myself to follow good flow practices and develop on a branch, and merge in GitLab</li> </ul> <p>So those are the basics, now onto my Elixir specific fun.</p> <h2 id="elixir-hooks">Elixir Hooks</h2> <p>Starting a new section in <code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code>, we’ll define an array of custom hooks:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">local</span>
  <span class="na">hooks</span><span class="pi">:</span>
</code></pre></div></div> <h3 id="tests">Tests</h3> <p>First, we’ll make sure the tests run, if any source or test file changes:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-test</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">test'</span>
  <span class="na">entry</span><span class="pi">:</span> <span class="s">mix test</span>
  <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
  <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>
</code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">files</code> pattern will be what changes this would trigger on, and we set <code class="language-plaintext highlighter-rouge">pass_filenames</code> as false, so that we run the full suite.</p> <h3 id="format">Format</h3> <p>Then, we’ll make sure all changed source and elixir script files are formatted correctly. Leaving the <code class="language-plaintext highlighter-rouge">pass_filenames</code> at its default of true, will just run the formatter on changed files.</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-format</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">format'</span>
  <span class="na">entry</span><span class="pi">:</span> <span class="s">mix format --check-formatted</span>
  <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
  <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>
</code></pre></div></div> <h3 id="compile">Compile</h3> <p>Then, we’ll make sure everything compiles without warnings.</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-compile</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">compile'</span>
  <span class="na">entry</span><span class="pi">:</span> <span class="s">mix compile --force --warnings-as-errors</span>
  <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
  <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">files</span><span class="pi">:</span> <span class="s">\.ex$</span>
</code></pre></div></div> <h3 id="convention">Convention</h3> <p>Finally, we’ll run credo on the entire project if any elixir or elixir script file changes.</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-credo</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">credo'</span>
  <span class="na">entry</span><span class="pi">:</span> <span class="s">mix credo</span>
  <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
  <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>
</code></pre></div></div> <h2 id="putting-it-all-together">Putting it all together</h2> <p>Here is the final file. It may get to be too slow as the project gets larger, and since CI does a lot of these same checks, I’ll likely shorten it up, or move a few things to taking passed files again, rather than running on the full project even if only one piece changes.</p> <p><code class="language-plaintext highlighter-rouge">.pre-commit-config.yaml</code></p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">local</span>
  <span class="na">hooks</span><span class="pi">:</span>

  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-test</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">test'</span>
    <span class="na">entry</span><span class="pi">:</span> <span class="s">mix test</span>
    <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
    <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
    <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>

  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-format</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">format'</span>
    <span class="na">entry</span><span class="pi">:</span> <span class="s">mix format --check-formatted</span>
    <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
    <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>

  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-compile</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">compile'</span>
    <span class="na">entry</span><span class="pi">:</span> <span class="s">mix compile --force --warnings-as-errors</span>
    <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
    <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
    <span class="na">files</span><span class="pi">:</span> <span class="s">\.ex$</span>

  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">mix-credo</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">elixir:</span><span class="nv"> </span><span class="s">mix</span><span class="nv"> </span><span class="s">credo'</span>
    <span class="na">entry</span><span class="pi">:</span> <span class="s">mix credo</span>
    <span class="na">language</span><span class="pi">:</span> <span class="s">system</span>
    <span class="na">pass_filenames</span><span class="pi">:</span> <span class="kc">false</span>
    <span class="na">files</span><span class="pi">:</span> <span class="s">\.exs*$</span>

<span class="pi">-</span> <span class="na">repo</span><span class="pi">:</span> <span class="s">git://github.com/pre-commit/pre-commit-hooks</span>
  <span class="na">sha</span><span class="pi">:</span> <span class="s">v1.4.0</span>
  <span class="na">hooks</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">trailing-whitespace</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-merge-conflict</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">check-yaml</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">end-of-file-fixer</span>
  <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">no-commit-to-branch</span>
    <span class="na">args</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">-b</span><span class="pi">,</span> <span class="nv">master</span><span class="pi">,</span> <span class="nv">-b</span><span class="pi">,</span> <span class="nv">production</span><span class="pi">,</span> <span class="nv">-b</span><span class="pi">,</span> <span class="nv">staging</span><span class="pi">]</span>
</code></pre></div></div> <p>I hope this helps you keep your Elixir projects nice!</p>]]></content><author><name>Dan Ivovich</name></author><category term="elixir"/><category term="testing"/><category term="git"/><summary type="html"><![CDATA[In the new project I mentioned in the previous post, I’m focusing on practicing good habits, which my CI setup is a large part of. The other big part is keeping things clean and well written before they are even committed into the repository.]]></summary></entry><entry><title type="html">Testing Elixir Apps on GitLab</title><link href="https://danivovich.com/blog/2018/10/29/testing-elixir-with-gitlab/" rel="alternate" type="text/html" title="Testing Elixir Apps on GitLab"/><published>2018-10-29T00:00:00+00:00</published><updated>2018-10-29T00:00:00+00:00</updated><id>https://danivovich.com/blog/2018/10/29/testing-elixir-with-gitlab</id><content type="html" xml:base="https://danivovich.com/blog/2018/10/29/testing-elixir-with-gitlab/"><![CDATA[<p>Recently, I started a new side project (maybe more on that soon), and I decided to give <a href="http://gitlab.com/">GitLab</a> a try. I’ve used it for some client work through my job, but had never really messed with it on my own.</p> <p>Some of my motivations for giving it a try:</p> <ul> <li>Free Private Repos</li> <li>Issue management with a Kanban board</li> <li>Milestones</li> <li>CI / CD built in</li> </ul> <p>The biggest reason here is the free hours of Continuous Integration (CI). There are several guides for setting up testing for an Elixir Project, but my setup ended up a little different, so I figured I’d throw up a post about it (and have it as a reference for myself, for the future, one of the best reasons to blog!)</p> <h2 id="gitlab-ci-config">GitLab CI Config</h2> <p>Everything starts with a <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> file:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">elixir:1.7.3</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">postgres:9.6</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s">app_name_test</span>
  <span class="na">POSTGRES_HOST</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">POSTGRES_USER</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">postgres"</span>
  <span class="na">MIX_ENV</span><span class="pi">:</span> <span class="s2">"</span><span class="s">test"</span>
</code></pre></div></div> <p>This part is pretty straight forward, just set the running to use the right version of Elixir, connect PostgreSQL (if needed) and set up a few global variables</p> <h2 id="testexs">test.exs</h2> <div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Config</span>

<span class="c1"># We don't run a server during test. If one is required,</span>
<span class="c1"># you can enable the server option below.</span>
<span class="n">config</span> <span class="ss">:app_name</span><span class="p">,</span> <span class="no">Web</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="mi">4001</span><span class="p">],</span>
  <span class="ss">server:</span> <span class="no">false</span>

<span class="c1"># Print only warnings and errors during test</span>
<span class="n">config</span> <span class="ss">:logger</span><span class="p">,</span> <span class="ss">level:</span> <span class="ss">:warn</span>

<span class="c1"># Configure your database</span>
<span class="n">config</span> <span class="ss">:app_name</span><span class="p">,</span> <span class="no">AppName</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">"POSTGRES_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">"POSTGRES_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">"POSTGRES_DB"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"app_name_test"</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">"POSTGRES_HOST"</span><span class="p">)</span> <span class="o">||</span> <span class="s2">"localhost"</span><span class="p">,</span>
  <span class="ss">pool:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span>
</code></pre></div></div> <p>This is set up to mostly just pull the env variables from the CI run, or default to something reasonable so that your tests will still run locally.</p> <h2 id="global-setup">Global Setup</h2> <p>Next up in the <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> file:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">before_script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">mix local.hex --force</span>
  <span class="pi">-</span> <span class="s">mix local.rebar --force</span>
</code></pre></div></div> <p>Here we are making sure that every job that runs (each stage of the pipeline) has hex and rebar ready to go. This is important when we add caching later, as these install outside the project directory.</p> <h2 id="stages">Stages</h2> <p>Now we will add stages to the <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> file:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">compile</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">apt-get update &amp;&amp; apt-get -y install postgresql-client</span>
    <span class="pi">-</span> <span class="s">mix deps.get --only test</span>
    <span class="pi">-</span> <span class="s">mix compile --warnings-as-errors</span>

<span class="na">test</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">mix ecto.create</span>
    <span class="pi">-</span> <span class="s">mix ecto.migrate</span>
    <span class="pi">-</span> <span class="s">mix test</span>

<span class="na">lint</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">mix format --check-formatted</span>
    <span class="pi">-</span> <span class="s">mix credo</span>
</code></pre></div></div> <p>The setup here runs the compile job in the build stage, so this will happen first. We’ll make sure we are ready with the postgres client, all the dependices for the app, and we’ll compile all our elixir code. I set <code class="language-plaintext highlighter-rouge">--warnings-as-errors</code> to make sure I’m not leaving anything deprecated or unused behind in my code.</p> <p>The test stage has two jobs, test and lint. These will be able to run in parallel on GitLab’s servers, or any other connected runner. The test job setup up ecto, and runs the test suite. The link job makes sure everything is formatted, and clears a credo check.</p> <p>Here is the display from GitLab for the pipeline stages: <img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2018-10-testing-elixir-with-gitlab/pipeline_stages.png" alt=""/></p> <p>And the pipeline jobs: <img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2018-10-testing-elixir-with-gitlab/pipeline_jobs.png" alt=""/></p> <h2 id="caching">Caching</h2> <p>Lastly, we want builds to go faster, so we add a block for caching configuration:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">cache</span><span class="pi">:</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">_build</span>
    <span class="pi">-</span> <span class="s">deps</span>
    <span class="pi">-</span> <span class="s">assets/node_modules</span>
</code></pre></div></div> <p>This way our dependecy download, build output and anything with our node modules in assets are preserved and not built from scratch each time. Mix and Yarn should handle if your dependencies need updating due to a change you’ve made.</p> <h2 id="conclusion">Conclusion</h2> <p>This approach seems to be working great! For my really simple starting Phoenix app with just a few tests, the original build took about 4.5 minutes, and each stage now runs in about 1.75 minutes. That is of course a lot more time than the tests take to run themselves, but there is a lot of overhead to get the tests ready to run. The formatting and credo runs (and tests and compile without warnings) I also do myself with git pre-commit hooks (I should do a post on that), so its unlikely something wrong would slip in, but I like the redundancy, and having this automated is a lot of fun!</p> <p>Here is the GitLab interface for a successful pipeline run: <img src="https://danivovich-com-content.s3.amazonaws.com/blog/post-content-images/2018-10-testing-elixir-with-gitlab/pipeline_run.png" alt=""/></p> <p>Happy CI-ing!</p> <p>Here is the whole file for reference:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">elixir:1.7.3</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">postgres:9.6</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">POSTGRES_DB</span><span class="pi">:</span> <span class="s">app_name_test</span>
  <span class="na">POSTGRES_HOST</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">POSTGRES_USER</span><span class="pi">:</span> <span class="s">postgres</span>
  <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">postgres"</span>
  <span class="na">MIX_ENV</span><span class="pi">:</span> <span class="s2">"</span><span class="s">test"</span>

<span class="na">cache</span><span class="pi">:</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">_build</span>
    <span class="pi">-</span> <span class="s">deps</span>
    <span class="pi">-</span> <span class="s">assets/node_modules</span>

<span class="na">before_script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">mix local.hex --force</span>
  <span class="pi">-</span> <span class="s">mix local.rebar --force</span>

<span class="na">compile</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">build</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">apt-get update &amp;&amp; apt-get -y install postgresql-client</span>
    <span class="pi">-</span> <span class="s">mix deps.get --only test</span>
    <span class="pi">-</span> <span class="s">mix compile --warnings-as-errors</span>

<span class="na">test</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">mix ecto.create</span>
    <span class="pi">-</span> <span class="s">mix ecto.migrate</span>
    <span class="pi">-</span> <span class="s">mix test</span>

<span class="na">lint</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">mix format --check-formatted</span>
    <span class="pi">-</span> <span class="s">mix credo</span>
</code></pre></div></div>]]></content><author><name>Dan Ivovich</name></author><category term="elixir"/><category term="testing"/><category term="gitlab"/><summary type="html"><![CDATA[Recently, I started a new side project (maybe more on that soon), and I decided to give GitLab a try. I’ve used it for some client work through my job, but had never really messed with it on my own.]]></summary></entry><entry><title type="html">Writing an Ansible Role for Galaxy</title><link href="https://danivovich.com/blog/2017/11/09/writing-an-ansible-role-for-galaxy/" rel="alternate" type="text/html" title="Writing an Ansible Role for Galaxy"/><published>2017-11-09T00:00:00+00:00</published><updated>2017-11-09T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/11/09/writing-an-ansible-role-for-galaxy</id><content type="html" xml:base="https://danivovich.com/blog/2017/11/09/writing-an-ansible-role-for-galaxy/"><![CDATA[<p>I’ve mentioned a <a href="https://blog.danivovich.com/2017/07/06/ansible-for-webapps/">few</a> <a href="https://blog.danivovich.com/2017/08/31/ansible-ssh-hardening-lockout/">times</a> how I’ve <a href="https://blog.danivovich.com/2017/04/26/giving-ansible-a-shot/">moved to use Ansible</a> more and more for server provisioning and change management.</p> <p>One thing that I recently embarked on was making an Ansible version of the Chef Cookbook that I use most often. The recipe is used to help me install the correct public keys into the authorized keys file for SSH access to servers I maintain. It uses GitHub Organizations and Users to fetch the public keys that GitHub users have added to their profiles. It also supports adding specific hardcoded keys, useful for deployment scripts or other types of programatic access.</p> <p>You can view the Ansible Role that I created <a href="https://galaxy.ansible.com/smartlogic/github_keys/">on Galaxy</a> and the <a href="https://github.com/smartlogic/ansible-role-github_keys">source</a>.</p> <h2 id="writing-the-role">Writing the Role</h2> <p><a href="https://www.vagrantup.com/">Vagrant</a> is your friend. I’ve used Vagrant for a number of different development tasks, but it really worked great for developing an Ansible role. It is always important to test your development, and Vagrant made it really easy to start fresh and test again and again.</p> <p>Get started by <a href="https://galaxy.ansible.com/intro">reading the documentation from Ansible</a>, it is a great guide. Pay particular attention to the good practices:</p> <ul> <li>Provide clear documentation in the README.md.</li> <li>Give accurate information in meta/main.yml.</li> <li>Include dependencies in meta/main.yml.</li> <li>Prefix variable names with the role name.</li> <li>Integrate your roles with Travis CI.</li> </ul> <p>Ansible gives some really great building blocks, so I didn’t need to write any custom code, just leverage the built in modules. All in all, I used these modules:</p> <ul> <li>user</li> <li>file</li> <li>lineinfile</li> <li>assemble</li> <li>uri</li> <li>get_url</li> <li>set_fact</li> </ul> <h2 id="compared-to-chef">Compared to Chef</h2> <p>Compared to Chef, it was a little harder to write since I couldn’t just mix in Ruby code to accomplish what I wanted, but stuck to the building blocks of Ansible. I think that in the end, this will make it more maintainable and more resistant to version changes of Ansible, which is one of my largest issues with Chef.</p> <p>The tight integration of GitHub and Ansible’s Galaxy is awesome, making it easy to publish and keep things up-to-date.</p> <p>I was able to pretty easily test Chef recipes with Vagrant or Docker. Testing with Ansible on Travis-CI is pretty easy, and I was able to also leverage Docker for testing using this great guide from <a href="https://www.jeffgeerling.com/blog/2016/how-i-test-ansible-configuration-on-7-different-oses-docker">Jeff Geerling</a>.</p> <p>Overall it was a great experience, and I’d look forward to doing it again if the need arises. There is also room for improvement in the current role. Right now, you can only use it to install keys into a single user account, unless you include the role more than once with different variables each time. It would be nice to give variables for configuration that allow the role to be included just once as setup all the user accounts necessary.</p>]]></content><author><name>Dan Ivovich</name></author><category term="ansible"/><category term="opensource"/><summary type="html"><![CDATA[I’ve mentioned a few times how I’ve moved to use Ansible more and more for server provisioning and change management.]]></summary></entry><entry><title type="html">Cyber Security Month Writing Roundup</title><link href="https://danivovich.com/blog/2017/11/06/cyber-security-month-roundup/" rel="alternate" type="text/html" title="Cyber Security Month Writing Roundup"/><published>2017-11-06T00:00:00+00:00</published><updated>2017-11-06T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/11/06/cyber-security-month-roundup</id><content type="html" xml:base="https://danivovich.com/blog/2017/11/06/cyber-security-month-roundup/"><![CDATA[<p>October was <a href="https://en.wikipedia.org/wiki/National_Cyber_Security_Awareness_Month">National Cyber Security Awareness Month</a>. On the <a href="https://blog.smartlogic.io/">company blog</a> I wrote up a 3 post series about building acceptable security when creating custom web and mobile applications.</p> <p>I start with the basics in the first post, <a href="https://blog.smartlogic.io/2017-intro-to-web-app-security/">Web App Security Part 1: An Introduction</a>.</p> <p>The series continues, digging deeper into securing applications with best practices, and a few items beyond the basics. Check out the post, <a href="https://blog.smartlogic.io/2017-web-app-security-part-2/">Web App Security Part 2: Digging Deeper</a>.</p> <p>The series rounds out with a walkthrough of how SmartLogic works to make security part of our every day work. Check out the concluding post, <a href="https://blog.smartlogic.io/2017-web-app-security-part-3/">Web App Security Part 3: The SmartLogic Process</a>.</p> <p>I hope you enjoy the series!</p>]]></content><author><name>Dan Ivovich</name></author><category term="other-writing"/><category term="company-blog"/><summary type="html"><![CDATA[October was National Cyber Security Awareness Month. On the company blog I wrote up a 3 post series about building acceptable security when creating custom web and mobile applications.]]></summary></entry><entry><title type="html">Intro To Elixir Presentation</title><link href="https://danivovich.com/blog/2017/10/27/intro-to-elixir-presentation/" rel="alternate" type="text/html" title="Intro To Elixir Presentation"/><published>2017-10-27T08:00:00+00:00</published><updated>2017-10-27T08:00:00+00:00</updated><id>https://danivovich.com/blog/2017/10/27/intro-to-elixir-presentation</id><content type="html" xml:base="https://danivovich.com/blog/2017/10/27/intro-to-elixir-presentation/"><![CDATA[<p>Recently posted on my main webpage in the <a href="https://www.danivovich.com/other.html">other section</a>, is a PDF of a presentation I gave at the <a href="https://www.meetup.com/Baltimore-Elixir-and-Erlang-Meetup/">Baltimore Elixir and Erlang Meetup</a>.</p> <p>In the presentation I cover:</p> <ul> <li>What is Elixir?</li> <li>What is the relationship to Erlang?</li> <li>The benefits of Elixir</li> <li>How to get started installing</li> <li>The basic programming types</li> <li>The basics of collection types in Elixir</li> <li>Functions, Structs, and Modules</li> <li>Pattern Matching</li> <li>Guards, Exceptions, Pipes</li> <li>Examples!</li> <li>A list of resources for learning more</li> </ul> <p>Here is the direct links for <a href="https://s3.amazonaws.com/danivovich-com-content/web/presentations/2017-10-25-Intro-To-Elixir-Baltimore-Meetup.pdf">Introduction to Elixir</a></p>]]></content><author><name>Dan Ivovich</name></author><category term="elixir"/><summary type="html"><![CDATA[Recently posted on my main webpage in the other section, is a PDF of a presentation I gave at the Baltimore Elixir and Erlang Meetup.]]></summary></entry><entry><title type="html">Writing Blog Posts with Atom</title><link href="https://danivovich.com/blog/2017/09/07/writing-blog-posts-with-atom/" rel="alternate" type="text/html" title="Writing Blog Posts with Atom"/><published>2017-09-07T00:00:00+00:00</published><updated>2017-09-07T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/09/07/writing-blog-posts-with-atom</id><content type="html" xml:base="https://danivovich.com/blog/2017/09/07/writing-blog-posts-with-atom/"><![CDATA[<p>Back when I migrated from <a href="https://blog.danivovich.com/2017/07/04/goodbye-ghost/">Ghost to Jekyll</a>, one thing I gave up was a really easy to use Markdown editor with a side-by-side preview.</p> <p>I’ve used a variety of Markdown editors, and some with Preview features, but none that really gave me what I wanted.</p> <p>When I looked around for alternatives, I found some that looked promising, but I settled on a free and open source solution.</p> <p>I’d heard of, and tried, the Atom editor before, but my entire development workflow is so focused around tmux and VIm that I’ve never really gotten any windowed editor to stick as part of my workflow.</p> <p>Atom is nice, very customizable, with a ton of great plugins.</p> <p>It even has some that understand Jekyll blogs and their folder structure, etc. The feature I use most however, is a ability to show a great Markdown preview, right next to the Markdown document I’m editing.</p> <p>If you are looking for a GUI editor for markdown documents, like writing blog posts, I suggest Atom.</p>]]></content><author><name>Dan Ivovich</name></author><category term="meta"/><category term="atom"/><summary type="html"><![CDATA[Back when I migrated from Ghost to Jekyll, one thing I gave up was a really easy to use Markdown editor with a side-by-side preview.]]></summary></entry><entry><title type="html">Preventing User Lockout with Ansible ssh-hardening</title><link href="https://danivovich.com/blog/2017/08/31/ansible-ssh-hardening-lockout/" rel="alternate" type="text/html" title="Preventing User Lockout with Ansible ssh-hardening"/><published>2017-08-31T00:00:00+00:00</published><updated>2017-08-31T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/08/31/ansible-ssh-hardening-lockout</id><content type="html" xml:base="https://danivovich.com/blog/2017/08/31/ansible-ssh-hardening-lockout/"><![CDATA[<p>I’m continuing to really enjoy working with <a href="https://www.ansible.com/">Ansible</a>. It meets the needs I have for server configuration, and has a lot of great community resources.</p> <p>One thing that is crucial to any server setup is ensuring that your SSH configuration is sound.</p> <p>A great role that I’ve used for my Ansible SSH configuration is <a href="https://galaxy.ansible.com/dev-sec/ssh-hardening/">ssh-hardening</a>.</p> <p>A pitfall that the author points out in the Readme, is that it is possible that your user account will be locked out after the role is applied. I’ve found this to be particularly true for the <code class="language-plaintext highlighter-rouge">ubuntu</code> account on EC2 servers.</p> <p>In order to make sure I can continue to get in to that user with my AWS key-pair, I’ve started adding this to a role that runs right after the <code class="language-plaintext highlighter-rouge">ssh-hardening</code> role in my playbooks.</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check if Ubuntu is locked</span>
  <span class="na">command</span><span class="pi">:</span> <span class="s">grep -q "ubuntu:!:" /etc/shadow</span>
  <span class="na">register</span><span class="pi">:</span> <span class="s">check_ubuntu_lock</span>
  <span class="na">ignore_errors</span><span class="pi">:</span> <span class="s">True</span>
  <span class="na">changed_when</span><span class="pi">:</span> <span class="s">False</span>
  <span class="na">become</span><span class="pi">:</span> <span class="kc">true</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Unlock Ubuntu</span>
  <span class="na">command</span><span class="pi">:</span> <span class="s">usermod -p "*" ubuntu</span>
  <span class="na">when</span><span class="pi">:</span> <span class="s">check_ubuntu_lock.rc == </span><span class="m">0</span>
  <span class="na">become</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div> <p>This checks the shadow file for the <code class="language-plaintext highlighter-rouge">!</code> indicator that would lock the account, and sets the password has to <code class="language-plaintext highlighter-rouge">*</code> which will unlock the account, but also ensure that the user can only log in via ssh keys.</p>]]></content><author><name>Dan Ivovich</name></author><category term="ansible"/><summary type="html"><![CDATA[I’m continuing to really enjoy working with Ansible. It meets the needs I have for server configuration, and has a lot of great community resources.]]></summary></entry><entry><title type="html">Using Webpack 3.x with Phoenix 1.3</title><link href="https://danivovich.com/blog/2017/08/30/webpack-phoenix/" rel="alternate" type="text/html" title="Using Webpack 3.x with Phoenix 1.3"/><published>2017-08-30T00:00:00+00:00</published><updated>2017-08-30T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/08/30/webpack-phoenix</id><content type="html" xml:base="https://danivovich.com/blog/2017/08/30/webpack-phoenix/"><![CDATA[<p>My design skills are wanting. I’m much more focused on backend development. I can cobble together pieces, but I’m far from expert.</p> <p>I’m really enjoying using more and more <a href="http://phoenixframework.org/">Phoenix</a>. For those that don’t know, Phoenix uses <a href="http://brunch.io/">Brunch</a> as it’s default way to manage javascript, image, and css assets.</p> <p>I’m not opposed to Brunch, but I’d need to learn it. I also am not opposed to <a href="https://webpack.js.org/">Webpack</a>, and I need to learn more about that as well.</p> <p>Given that both are new to me, and neither are completely similar to the Rails asset pipeline, I felt that it was a good time to step back and decide what I wanted to learn.</p> <p>Right around this time, Rails also <a href="https://github.com/rails/webpacker">embraced Webpack</a>. On top of that, many of my coworkers, who are way more excited about JavaScript than I am, seem to really prefer Webpack.</p> <h2 id="webpack-and-phoenix">Webpack and Phoenix</h2> <p>There are a number of tutorials out on the web to get this set up. I thank many of them for giving me the context to figure this out, but like all things, technology moves fast and our blog posts get out of date. Nothing I found worked out of the box with Phoenix 1.3 and Webpack 3.5.5.</p> <p>The good news, in Phoenix 1.3 the assets folder is at the top level, and is really distinct from how the rest of our application functions.</p> <h2 id="getting-started">Getting Started</h2> <p>You can start a new Phoenix app with the <code class="language-plaintext highlighter-rouge">--no-brunch</code> flag, or you can remove brunch. I’ll point on the one major difference, but in general, if you already have a brunch <code class="language-plaintext highlighter-rouge">assets</code> folder, you can remove it.</p> <p>The folder structure you’ll want to have at this point in the process should look like:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assets
  \_ js
  \_ css
  \_ static
    \_ images
    \_ robots.txt
</code></pre></div></div> <p>Now you can create your JavaScript package file.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="dl">"</span><span class="s2">dependencies</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">phoenix</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">file:../deps/phoenix</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">phoenix_html</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">file:../deps/phoenix_html</span><span class="dl">"</span>
  <span class="p">},</span>
  <span class="dl">"</span><span class="s2">devDependencies</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">babel-core</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^6.26.0</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">babel-loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^7.1.2</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">babel-plugin-transform-es2015-modules-strip</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^0.1.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">babel-plugin-transform-object-rest-spread</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^6.3.13</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">babel-preset-es2015</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^6.24.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">babel-preset-react</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^6.24.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">bootstrap</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^4.0.0-beta</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">copy-webpack-plugin</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^4.0.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">css-loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^0.28.0</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">extract-text-webpack-plugin</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^3.0.0</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">import-glob-loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^1.1.0</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^3.2.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">node-sass</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^4.5.2</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">popper.js</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^15.6.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">react-dom</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^15.6.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">sass-loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^6.0.3</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^10.0.2</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">style-loader</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^0.16.1</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">webpack</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^3.5.5</span><span class="dl">"</span>
  <span class="p">},</span>
  <span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">watch</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">webpack --watch --color</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">deploy</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">webpack -p</span><span class="dl">"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div> <p>There is a lot in there, but its the tooling I wanted to use on this project.</p> <p>I recommend you use <a href="https://yarnpkg.com/">Yarn</a> to install and manage this stuff.</p> <p>Now the Webpack config file:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">ExtractTextPlugin</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">extract-text-webpack-plugin</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">CopyWebpackPlugin</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">copy-webpack-plugin</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">webpack</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webpack</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">env</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MIX_ENV</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">dev</span><span class="dl">'</span>
<span class="kd">var</span> <span class="nx">isProduction</span> <span class="o">=</span> <span class="p">(</span><span class="nx">env</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">prod</span><span class="dl">'</span><span class="p">)</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">entry</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">app</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">./js/app.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">./css/app.scss</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">},</span>
  <span class="na">output</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">path</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">../priv/static/</span><span class="dl">'</span><span class="p">),</span>
    <span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">js/[name].js</span><span class="dl">'</span>
  <span class="p">},</span>
  <span class="na">devtool</span><span class="p">:</span> <span class="dl">'</span><span class="s1">source-map</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">resolve</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">extensions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.jsx</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">},</span>
  <span class="na">module</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">rules</span><span class="p">:</span> <span class="p">[{</span>
      <span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.(</span><span class="sr">sass|scss</span><span class="se">)</span><span class="sr">$/</span><span class="p">,</span>
      <span class="na">include</span><span class="p">:</span> <span class="sr">/css/</span><span class="p">,</span>
      <span class="na">use</span><span class="p">:</span> <span class="nx">ExtractTextPlugin</span><span class="p">.</span><span class="nf">extract</span><span class="p">({</span>
        <span class="na">fallback</span><span class="p">:</span> <span class="dl">'</span><span class="s1">style-loader</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">use</span><span class="p">:</span> <span class="p">[</span>
          <span class="p">{</span><span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">css-loader</span><span class="dl">'</span><span class="p">},</span>
          <span class="p">{</span>
            <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sass-loader</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">options</span><span class="p">:</span> <span class="p">{</span>
              <span class="na">includePaths</span><span class="p">:</span> <span class="p">[</span>
                <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="dl">'</span><span class="s1">node_modules/bootstrap/scss</span><span class="dl">'</span><span class="p">)</span>
              <span class="p">],</span>
              <span class="na">sourceComments</span><span class="p">:</span> <span class="o">!</span><span class="nx">isProduction</span>
            <span class="p">}</span>
          <span class="p">}</span>
        <span class="p">]</span>
      <span class="p">})</span>
    <span class="p">},</span> <span class="p">{</span>
      <span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.(</span><span class="sr">js|jsx</span><span class="se">)</span><span class="sr">$/</span><span class="p">,</span>
      <span class="na">include</span><span class="p">:</span> <span class="sr">/js/</span><span class="p">,</span>
      <span class="na">use</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span> <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">babel-loader</span><span class="dl">'</span> <span class="p">}</span>
      <span class="p">]</span>
    <span class="p">}]</span>
  <span class="p">},</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span>
    <span class="k">new</span> <span class="nc">CopyWebpackPlugin</span><span class="p">([{</span> <span class="na">from</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./static</span><span class="dl">'</span> <span class="p">}]),</span>
    <span class="k">new</span> <span class="nc">ExtractTextPlugin</span><span class="p">(</span><span class="dl">'</span><span class="s1">css/app.css</span><span class="dl">'</span><span class="p">),</span>
    <span class="k">new</span> <span class="nx">webpack</span><span class="p">.</span><span class="nc">ProvidePlugin</span><span class="p">({</span>
      <span class="na">$</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">jQuery</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">window.jQuery</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">Popper</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">popper.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">]</span>
    <span class="p">})</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div> <p>Finally we need to tell Phoenix how to invoke webpack to watch our assets while we develop. Since Phoenix will expect our asset tooling (normally Brunch) to build into the <code class="language-plaintext highlighter-rouge">priv/static/</code> folder, then everything Phoenix does to serve up those files, and hot reload when they change, will still work.</p> <p>In your <code class="language-plaintext highlighter-rouge">config/dev.exs</code> file:</p> <div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:appname</span><span class="p">,</span> <span class="no">AppName</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="mi">4000</span><span class="p">],</span>
  <span class="ss">debug_errors:</span> <span class="no">true</span><span class="p">,</span>
  <span class="ss">code_reloader:</span> <span class="no">true</span><span class="p">,</span>
  <span class="ss">check_origin:</span> <span class="no">false</span><span class="p">,</span>
  <span class="ss">watchers:</span> <span class="p">[</span><span class="ss">yarn:</span> <span class="p">[</span><span class="s2">"run"</span><span class="p">,</span> <span class="s2">"watch"</span><span class="p">,</span>
    <span class="ss">cd:</span> <span class="no">Path</span><span class="o">.</span><span class="n">expand</span><span class="p">(</span><span class="s2">"../assets"</span><span class="p">,</span> <span class="n">__DIR__</span><span class="p">)]]</span>
</code></pre></div></div> <p>If you didn’t include brunch, then the <code class="language-plaintext highlighter-rouge">watchers:</code> key will be an empty list, and if you did, then you can just change it to what I have above. If you aren’t using yarn (you should be), then you’ll need to tweak this a bit.</p> <h2 id="breakdown-of-the-webpack-file">Breakdown of the Webpack file</h2> <p>Since this is the first time I’ve ever really dug deep into Webpack, let’s walk through the config file.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">ExtractTextPlugin</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">extract-text-webpack-plugin</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">CopyWebpackPlugin</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">copy-webpack-plugin</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">webpack</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">webpack</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">env</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MIX_ENV</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">dev</span><span class="dl">'</span>
<span class="kd">var</span> <span class="nx">isProduction</span> <span class="o">=</span> <span class="p">(</span><span class="nx">env</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">prod</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div> <p>Here we are just importing some things we will need, and setting up the environment, looking for our Elixir mix env, but defaulting to dev. We can use this to selectively do production optimizations like compressing and uglifying.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">entry</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">app</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">./js/app.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">./css/app.scss</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">},</span>
  <span class="na">output</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">path</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">../priv/static/</span><span class="dl">'</span><span class="p">),</span>
    <span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">js/[name].js</span><span class="dl">'</span>
  <span class="p">},</span>
  <span class="na">devtool</span><span class="p">:</span> <span class="dl">'</span><span class="s1">source-map</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">resolve</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">extensions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.jsx</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">},</span>
</code></pre></div></div> <p>Here we set up the main entrypoint, the app, and it’s two major files. We will have a <code class="language-plaintext highlighter-rouge">js/app.js</code> that will import all other files we need for the app. We will also have a <code class="language-plaintext highlighter-rouge">css/app.scss</code> that will import the css and scss for our app.</p> <p>Then we define where the outputs go, specifying that they should go in the Phoenix <code class="language-plaintext highlighter-rouge">priv/static/</code> folder, and that the application javascript bundle should go into the js folder there, with the entry point name and js file extension.</p> <p>Then we enable source maps.</p> <p>Finally we specify the resolve extensions when doing JavaScript imports, so that we can include <code class="language-plaintext highlighter-rouge">.jsx</code> files.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">module</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">rules</span><span class="p">:</span> <span class="p">[{</span>
      <span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.(</span><span class="sr">sass|scss</span><span class="se">)</span><span class="sr">$/</span><span class="p">,</span>
      <span class="na">include</span><span class="p">:</span> <span class="sr">/css/</span><span class="p">,</span>
      <span class="na">use</span><span class="p">:</span> <span class="nx">ExtractTextPlugin</span><span class="p">.</span><span class="nf">extract</span><span class="p">({</span>
        <span class="na">fallback</span><span class="p">:</span> <span class="dl">'</span><span class="s1">style-loader</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">use</span><span class="p">:</span> <span class="p">[</span>
          <span class="p">{</span><span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">css-loader</span><span class="dl">'</span><span class="p">},</span>
          <span class="p">{</span>
            <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sass-loader</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">options</span><span class="p">:</span> <span class="p">{</span>
              <span class="na">includePaths</span><span class="p">:</span> <span class="p">[</span>
                <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="dl">'</span><span class="s1">node_modules/bootstrap/scss</span><span class="dl">'</span><span class="p">)</span>
              <span class="p">],</span>
              <span class="na">sourceComments</span><span class="p">:</span> <span class="o">!</span><span class="nx">isProduction</span>
            <span class="p">}</span>
          <span class="p">}</span>
        <span class="p">]</span>
      <span class="p">})</span>
    <span class="p">},</span> <span class="p">{</span>
      <span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.(</span><span class="sr">js|jsx</span><span class="se">)</span><span class="sr">$/</span><span class="p">,</span>
      <span class="na">include</span><span class="p">:</span> <span class="sr">/js/</span><span class="p">,</span>
      <span class="na">use</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span> <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">babel-loader</span><span class="dl">'</span> <span class="p">}</span>
      <span class="p">]</span>
    <span class="p">}]</span>
  <span class="p">},</span>
</code></pre></div></div> <p>Here is the real meat. The first rule is what to do with our scss and sass files in the css folder. We are going to run them through the Extract Text Plugin, so that they will be in their own resultant file, with a fallback to the standard style-loader. Then we are going to use specific loaders to read in css files, and sass files. For the sass-loader, we are going to include sourceComments if we aren’t building a production bundle, and we are going to load up the bootstrap 4 scss path so that we can import them into our <code class="language-plaintext highlighter-rouge">app.scss</code> file.</p> <p>In the second part, we are going to pass any JavaScript and jsx files through the babel-loader.</p> <p>For the record, here is the <code class="language-plaintext highlighter-rouge">.babelrc</code> file I’ve got so far:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="dl">"</span><span class="s2">presets</span><span class="dl">"</span><span class="p">:[</span>
    <span class="dl">"</span><span class="s2">es2015</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div> <p>Finally at the end of the Webpack config:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">plugins</span><span class="p">:</span> <span class="p">[</span>
    <span class="k">new</span> <span class="nc">CopyWebpackPlugin</span><span class="p">([{</span> <span class="na">from</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./static</span><span class="dl">'</span> <span class="p">}]),</span>
    <span class="k">new</span> <span class="nc">ExtractTextPlugin</span><span class="p">(</span><span class="dl">'</span><span class="s1">css/app.css</span><span class="dl">'</span><span class="p">),</span>
    <span class="k">new</span> <span class="nx">webpack</span><span class="p">.</span><span class="nc">ProvidePlugin</span><span class="p">({</span>
      <span class="na">$</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">jQuery</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">window.jQuery</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">Popper</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">popper.js</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">]</span>
    <span class="p">})</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div> <p>We set up the Copy Webpack Plugin to copy our static files, like the robots.txt or images into the priv folder. We also run the extract text plugin over a non scss app file (which I probably don’t actually need here). Then finally we set up a few global namespace items so that they can be bundled correctly by Webpack even if they aren’t imported specifically in the file that references them.</p> <h2 id="usage">Usage</h2> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">phoenix_html</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">bootstrap</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div> <div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@import</span> <span class="s2">"bootstrap"</span><span class="p">;</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">padding-top</span><span class="p">:</span> <span class="m">1</span><span class="mi">.5rem</span><span class="p">;</span>
  <span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">1</span><span class="mi">.5rem</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.container</span> <span class="p">{</span>
  <span class="nl">padding-top</span><span class="p">:</span> <span class="m">1</span><span class="mi">.5rem</span><span class="p">;</span>
  <span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">1</span><span class="mi">.5rem</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div> <p>Pretty simple, you can import the JavaScript and scss for Bootstrap, and add your own times easily. You’ll still have access to the Phoenix JavaScript from your mix deps folder.</p> <h2 id="running">Running</h2> <p>When you run your <code class="language-plaintext highlighter-rouge">mix phx.server</code> you should see the Webpack watcher boot and emit your bundle files.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generated appname app
<span class="o">[</span>info] Running AppName.Endpoint with Cowboy using http://0.0.0.0:4000
yarn run v0.23.4
<span class="nv">$ </span>webpack <span class="nt">--watch</span> <span class="nt">--color</span>

Webpack is watching the files…

Hash: 19c0661b1f8ddf6c7912
Version: webpack 3.5.5
Time: 5233ms
          Asset       Size  Chunks                    Chunk Names
      js/app.js     478 kB       0  <span class="o">[</span>emitted]  <span class="o">[</span>big]  app
    css/app.css     234 kB       0  <span class="o">[</span>emitted]         app
  js/app.js.map     900 kB       0  <span class="o">[</span>emitted]         app
css/app.css.map   88 bytes       0  <span class="o">[</span>emitted]         app
     robots.txt  205 bytes          <span class="o">[</span>emitted]
</code></pre></div></div> <p>If you change an asset file, you will see Webpack emit the update bundle.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hash: 9797a337be458d27d712
Version: webpack 3.5.5
Time: 882ms
          Asset      Size  Chunks                    Chunk Names
      js/app.js    478 kB       0  <span class="o">[</span>emitted]  <span class="o">[</span>big]  app
    css/app.css    234 kB       0  <span class="o">[</span>emitted]         app
  js/app.js.map    900 kB       0  <span class="o">[</span>emitted]         app
css/app.css.map  88 bytes       0  <span class="o">[</span>emitted]         app
   <span class="o">[</span>1] ./js/app.js 62 bytes <span class="o">{</span>0<span class="o">}</span> <span class="o">[</span>built]
</code></pre></div></div> <h2 id="deployment">Deployment</h2> <p>I haven’t really messed with this yet, so far just focusing on locally development. That said, it should be the same general approach as with Brunch, build the assets to the <code class="language-plaintext highlighter-rouge">priv/static</code> folder then run <code class="language-plaintext highlighter-rouge">phx.digest</code>. If I get any more info on this, it will be great content for another post.</p>]]></content><author><name>Dan Ivovich</name></author><category term="webpack"/><category term="phoenix"/><summary type="html"><![CDATA[My design skills are wanting. I’m much more focused on backend development. I can cobble together pieces, but I’m far from expert.]]></summary></entry><entry><title type="html">Ansible for Webapps</title><link href="https://danivovich.com/blog/2017/07/06/ansible-for-webapps/" rel="alternate" type="text/html" title="Ansible for Webapps"/><published>2017-07-06T00:00:00+00:00</published><updated>2017-07-06T00:00:00+00:00</updated><id>https://danivovich.com/blog/2017/07/06/ansible-for-webapps</id><content type="html" xml:base="https://danivovich.com/blog/2017/07/06/ansible-for-webapps/"><![CDATA[<p>I’ve been using Ansible <a href="https://blog.danivovich.com/2017/07/04/goodbye-ghost/">more</a> and <a href="https://blog.danivovich.com/2017/04/27/elixir-ansible-and-credo/">more</a>. It is a really great way to manage server configuration. As I’ve <a href="https://blog.danivovich.com/2017/04/26/giving-ansible-a-shot/">transitioned away from Chef</a>, I’ve been working on establishing some patterns that seem to work well when setting up servers for deploying web applications.</p> <h2 id="directory-structure">Directory Structure</h2> <p>When using Chef, I would always create a git repository just for managing servers, with a combination of recipes and node files to cover all the various aspects of a projects infrastructure.</p> <p>For Ansible, I’ve found it easiest to just store the configuration alongside the application code. The bulk of the configuration goes in a <code class="language-plaintext highlighter-rouge">deploy</code> directory, with an <code class="language-plaintext highlighter-rouge">ansible.cfg</code> at the root.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app_root
  |- ansible.cfg
  |- deploy
     |- galaxy-roles
        |- .gitkeep
     |- group_vars
        |- all.yml
     |- hosts
     |- requirements.yml
     |- roles
        |- app
           ...
     |- setup.yml
</code></pre></div></div> <h2 id="configuration">Configuration</h2> <p>To start, we’ll run through the various base configuration files for the setup. This setup works well to manage both custom roles created for the project, but also roles downloaded from Anisble Galaxy. The setup also explains how to deal with a mixture of servers with different deployment environments like staging and production.</p> <h3 id="ansiblecfg">ansible.cfg</h3> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[defaults]</span><span class="w">
</span><span class="py">inventory</span><span class="p">=</span><span class="s">deploy/hosts</span>
<span class="py">remote_user</span><span class="p">=</span><span class="s">ubuntu</span>
<span class="py">roles_path</span><span class="p">=</span><span class="s">deploy/galaxy-roles:deploy/roles</span>
<span class="w">
</span><span class="nn">[ssh_connection]</span><span class="w">
</span><span class="py">ssh_args</span><span class="p">=</span><span class="s">"-o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=5m"</span>
<span class="py">pipelining</span><span class="p">=</span><span class="s">True</span>
<span class="py">scp_if_ssh</span><span class="p">=</span><span class="s">True</span>
</code></pre></div></div> <p>This file sets up some basic configuration for my setup. The inventory of servers will default to the file <code class="language-plaintext highlighter-rouge">deploy/hosts</code>, and by default we will connect as the ubuntu user (common with AWS and Vagrant).</p> <p>Then I specify the directories for roles to be installed in. First I specify the <code class="language-plaintext highlighter-rouge">deploy/galaxy-roles</code> directory, so that when I install roles from the Ansible Galaxy, they install into this directory, and then we have <code class="language-plaintext highlighter-rouge">deploy/roles</code> which is where we will put roles we write just for this deployment.</p> <p>In the ssh_connection section, we set some defaults that we want. The one unusual thing here is the <code class="language-plaintext highlighter-rouge">scp_if_ssh=True</code> which is necessary because the SSH hardening role disables sftp, so we want to use scp for file transfers.</p> <h3 id="hosts-file-with-various-environments">hosts file with various environments</h3> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[local]</span><span class="w">
</span><span class="na">vagrant_local_vm</span><span class="w"> </span><span class="py">ansible_host</span><span class="p">=</span><span class="s">127.0.0.1 ansible_port=2222 ansible_ssh_private_key_file=.vagrant/machines/default/virtualbox/private_key</span>
<span class="w">
</span><span class="nn">[staging]</span><span class="w">
</span><span class="na">staging</span><span class="w"> </span><span class="py">ansible_ssh_private_key_file</span><span class="p">=</span><span class="s">~/.ssh/aws-key.pem</span>
<span class="w">
</span><span class="nn">[prod_web]</span><span class="w">
</span><span class="na">prod_web_1</span><span class="w"> </span><span class="py">ansible_ssh_private_key_file</span><span class="p">=</span><span class="s">~/.ssh/aws-key.pem</span>
<span class="w">
</span><span class="nn">[prod_db]</span><span class="w">
</span><span class="na">prod_db_1</span><span class="w"> </span><span class="py">ansible_ssh_private_key_file</span><span class="p">=</span><span class="s">~/.ssh/aws-key.pem</span>
</code></pre></div></div> <p>Here we set various environments and the servers within those groups. We do this so we can filter the inventory we are applying a playbook on when we invoke <code class="language-plaintext highlighter-rouge">ansible-playbook</code></p> <p>For each server, if there a specific connection parameters we need, we apply them for each node. I generally set these hostnames in my <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> so that I don’t need to set IP addresses or some other configuration parameters when I want to ssh to these machines later.</p> <h3 id="group_vars">group_vars</h3> <p>Here I set specific values for the roles I’m pulling from Ansible Galaxy. I’ve found that many roles provide nice tables of all the default variables and options you have, and I keep this file neat by alphabetizing the variables as they are usually prefixed by the role they are part of.</p> <h2 id="galaxy">Galaxy</h2> <p>As defined in <code class="language-plaintext highlighter-rouge">ansible.cfg</code> the roles path begins with <code class="language-plaintext highlighter-rouge">galaxy-roles</code>, so when we run <code class="language-plaintext highlighter-rouge">ansible-galaxy -r deploy/requirements.yml</code>, they install into this directory.</p> <h3 id="git">git</h3> <p>I add a gitignore <code class="language-plaintext highlighter-rouge">deploy/.gitignore</code> to keep some files out of git within the ansible deploy directory. I add a <code class="language-plaintext highlighter-rouge">.gitkeep</code> file to the galaxy-roles directory to ensure we’ve got the folder.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*.retry
galaxy-roles/**
!galaxy-roles/.gitkeep
</code></pre></div></div> <h3 id="requirementsyml">requirements.yml</h3> <p>Here is a sample of some roles I’ve been using:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">franklinkim.newrelic</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">1.6.0</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">geerlingguy.nginx</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">2.1.0</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">dev-sec.ssh-hardening</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">4.1.2</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">https://github.com/ANXS/postgresql</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">9446ab512ff2a7c7bae21bc7ebba515192809433</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">jnv.unattended-upgrades</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">v1.3.0</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">geerlingguy.ntp</span>
  <span class="na">version</span><span class="pi">:</span> <span class="s">1.4.2</span>
<span class="pi">-</span> <span class="na">src</span><span class="pi">:</span> <span class="s">smartlogic.github_keys</span>
  <span class="na">version</span><span class="pi">:</span> <span class="m">0.1</span>
</code></pre></div></div> <p>These can be installed when you checkout the repo using</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ansible-galaxy <span class="nt">-r</span> deploy/requirements.yml
</code></pre></div></div> <h2 id="playbooks">Playbooks</h2> <p>Depending on the situation, I can create a playbook file for various situations. The most common is a <code class="language-plaintext highlighter-rouge">setup.yml</code></p> <h3 id="main-setup">Main Setup</h3> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Basic Setup</span>
  <span class="na">gather_facts</span><span class="pi">:</span> <span class="s">yes</span>
  <span class="na">hosts</span><span class="pi">:</span> <span class="s">all</span>
  <span class="na">roles</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">dev-sec.ssh-hardening</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">deploy_user</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">smartlogic.github_keys</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">jnv.unattended-upgrades</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">postgresql</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">geerlingguy.nginx</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">geerlingguy.ntp</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span> <span class="pi">}</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">app</span> <span class="pi">}</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Production servers get newrelic</span>
  <span class="na">gather_facts</span><span class="pi">:</span> <span class="s">yes</span>
  <span class="na">hosts</span><span class="pi">:</span> <span class="s">prod_*</span>
  <span class="na">roles</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="pi">{</span> <span class="nv">role</span><span class="pi">:</span> <span class="nv">franklinkim.newrelic</span><span class="pi">,</span> <span class="nv">become</span><span class="pi">:</span> <span class="nv">yes</span><span class="pi">,</span> <span class="nv">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">newrelic'</span><span class="pi">]</span> <span class="pi">}</span>
</code></pre></div></div> <p>The basic setup is applied to all hosts. It is a mix of roles from Ansible Galaxy and ones local to this repository. The second play applies to all servers that match the pattern <code class="language-plaintext highlighter-rouge">prod_*</code>, so that servers that begin with <code class="language-plaintext highlighter-rouge">prod_</code> in the <code class="language-plaintext highlighter-rouge">deploy/hosts</code> file get those roles. In this case we do that to only install the NewRelic server monitoring into our production servers.</p> <h3 id="others">Others</h3> <p>Other files might be <code class="language-plaintext highlighter-rouge">deploy.yml</code> or <code class="language-plaintext highlighter-rouge">migrate.yml</code> with roles and variables set for those actions. More complex examples of these is probably another post of its own.</p> <h2 id="reuse-and-roles">Reuse and Roles</h2> <p>With this setup, any shared behavior between various deployments you may do should be handled through a shared role in git or Ansible Galaxy. That way you don’t do any copy and paste between setups you manage.</p> <p>The few roles that are specific to your web application would be in the <code class="language-plaintext highlighter-rouge">deploy/roles</code> folder, and should be very specific to the application you are deploying. Examples of this might be creating user accounts or the specific nginx configuration files for your service.</p> <h3 id="sample-custom-role">Sample custom role</h3> <p>A simple task to template our nginx configuration based on SSL certificate presence:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check for app certs</span>
  <span class="na">stat</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">/etc/ssl/private/app.crt</span>
  <span class="na">register</span><span class="pi">:</span> <span class="s">app_cert</span>
  <span class="na">become</span><span class="pi">:</span> <span class="kc">true</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Template app no-ssl version</span>
  <span class="na">copy</span><span class="pi">:</span>
    <span class="na">src</span><span class="pi">:</span> <span class="s">app.nossl.conf</span>
    <span class="na">dest</span><span class="pi">:</span> <span class="s">/etc/nginx/sites-available/app.conf</span>
    <span class="na">owner</span><span class="pi">:</span> <span class="s">www-data</span>
    <span class="na">group</span><span class="pi">:</span> <span class="s">www-data</span>
  <span class="na">when</span><span class="pi">:</span> <span class="s">app_cert.stat.islnk is not defined</span>
  <span class="na">notify</span><span class="pi">:</span> <span class="s">restart nginx</span>
  <span class="na">become</span><span class="pi">:</span> <span class="kc">true</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Template app ssl version</span>
  <span class="na">copy</span><span class="pi">:</span>
    <span class="na">src</span><span class="pi">:</span> <span class="s">app.ssl.conf</span>
    <span class="na">dest</span><span class="pi">:</span> <span class="s">/etc/nginx/sites-available/app.conf</span>
    <span class="na">owner</span><span class="pi">:</span> <span class="s">www-data</span>
    <span class="na">group</span><span class="pi">:</span> <span class="s">www-data</span>
  <span class="na">when</span><span class="pi">:</span> <span class="s">app_cert.stat.islnk is defined</span>
  <span class="na">notify</span><span class="pi">:</span> <span class="s">restart nginx</span>
  <span class="na">become</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div> <h2 id="overlapping-deployments">Overlapping deployments</h2> <p>One thing I’ve considered with this setup is that if you have a more service oriented architecture, you might want to use a common Ansible repository that is shared for all your various services. I think that might make sense at some scale.</p> <p>For most of what I do, simply creating reusable and shared roles keeps the amount of duplication between setups minimal. Yes, if you run two project’s Ansible playbooks, there might be a lot of duplication in what actual tasks are run, but they should be idempotent and guarded by the right checks to prevent duplicate work.</p> <h2 id="conclusion">Conclusion</h2> <p>So far this setup has worked well for me. I’m sure it will continue to evolve and I do more and more with Ansible, but the end result is that I have a simple configuration along side my application code that is making setting up and maintaining server easier than it has ever been.</p>]]></content><author><name>Dan Ivovich</name></author><category term="ansible"/><category term="deployment"/><summary type="html"><![CDATA[I’ve been using Ansible more and more. It is a really great way to manage server configuration. As I’ve transitioned away from Chef, I’ve been working on establishing some patterns that seem to work well when setting up servers for deploying web applications.]]></summary></entry></feed>