<?xml version="1.0" encoding="utf-8"?>

<!--
  ATOM template originally sourced from
  https://github.com/jekyll/jekyll-feed/blob/99f344c7005221d45270574015f86033322d9983/lib/jekyll-feed/feed.xml
  MIT License: https://github.com/jekyll/jekyll-feed/blob/b907a1b2ac9e270f59dde88fab9a37542342dfb3/LICENSE.txt
-->

<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="https://robacarp.io/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://robacarp.io/" rel="alternate" type="text/html" />
  <updated>2025-04-19T12:02:52+00:00</updated>
  <id>03315f11-b8f3-414a-b5a1-b0a46b413598</id><title type="html">Robacarp</title><author>
        <name>Robert Carpenter</name></author><entry>
      <title type="html">Days of a month iterator in Crystal</title>
      <link href="https://robacarp.io/2024/07/16/calendar-month-iterator-in-crystal.html" rel="alternate" type="text/html" title="Days of a month iterator in Crystal" />
      <published>2024-07-16T20:25:44+00:00</published>
      <updated>2024-07-16T20:25:44+00:00</updated>
      <id>e54c2b03-1adc-46e2-8bd8-9ad71dbbab5f</id>
      <content type="html" xml:base="https://robacarp.io/2024/07/16/calendar-month-iterator-in-crystal.html">&lt;p&gt;On &lt;a href=&quot;https://archive.fm&quot;&gt;Archive.fm&lt;/a&gt; I wanted to display a calendar which shows the percentage complete of any day in the history of podcasting. I want to know if my hardware is keeping up, making progress on the immense backlog, or falling ever further behind.&lt;/p&gt;

&lt;p&gt;Rendering a calendar-style view of a month is a remarkably fiddly operation. At a minimum, the display logic needs to track:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Which day of the week the display starts/ends on&lt;/li&gt;
  &lt;li&gt;Which days have additional content — in this case the percentage of content transcribed for the day&lt;/li&gt;
  &lt;li&gt;Rendering the headers and day numbers&lt;/li&gt;
  &lt;li&gt;Placing the “cells” representing each day on the display underneath the correct day of the week&lt;/li&gt;
  &lt;li&gt;Rendering the cells which aren’t actually part of the current month, but need to be on screen to make the month look correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like any datetime algorithm, when all of this is stacked on top of each other, it gets hairy quickly. I decided to tackle the last point with a &lt;a href=&quot;https://crystal-lang.org/api/1.13.1/Iterator.html&quot;&gt;custom iterator&lt;/a&gt; in Crystal. I didn’t want to have to deal with rendering the parts of a week which fall before or after the current month. Leaving that constraint out I could easily handle the wrapping and other display logic inline and the implementation is readable:&lt;/p&gt;

&lt;div class=&quot;language-erb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;presenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;month_header&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;presenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days_of_the_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;monday?&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;presenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in_month?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%-d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;percent_transcribed_for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;color: #ccc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%-d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sunday?&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The implementation of the presenter method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;days_of_the_month&lt;/code&gt; and the iterator it builds allow the above code to forget about the messy situation of a month which starts on Wednesday and what to to about the days in that week &lt;em&gt;before&lt;/em&gt; Wednesday. Here’s what I came up with:&lt;/p&gt;

&lt;div class=&quot;language-crystal highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Presenter&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calendar&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@first_of_month&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;days_of_the_month&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;RenderableMonthIterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@first_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Iterates the days of the month, and including the &quot;padding&quot; days both&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# before the start of the month and after the end which are needed to&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# visually render a calendar.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RenderableMonthIterator&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Iterator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@first_of_month&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@first_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at_beginning_of_week&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@last_day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@first_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at_end_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at_end_of_week&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;next&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@first&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@last_day&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@current_day&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Crystal stdlib provides some handy convenience mutators on Time objects which make the setup trivial: &lt;a href=&quot;https://crystal-lang.org/api/1.13.1/Time.html#at_beginning_of_week%28start_day%3ATime%3A%3ADayOfWeek%3D%3Amonday%29%3ATime-instance-method&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at_beginning_of_week&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://crystal-lang.org/api/1.13.1/Time.html#at_end_of_month%3ATime-instance-method&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at_end_of_month&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://crystal-lang.org/api/1.13.1/Time.html#at_end_of_week%3ATime-instance-method&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;at_end_of_week&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The best part about this is that there’s nothing complicated in front of me at the end of the day – it’s all readable, single-purpose code.&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="crystal-lang" /><category term="algorithms" /><category term="readability" /><summary type="html">On Archive.fm I wanted to display a calendar which shows the percentage complete of any day in the history of podcasting. I want to know if my hardware is keeping up, making progress on the immense backlog, or falling ever further behind.</summary></entry><entry>
      <title type="html">A Price is Right search in JavaScript</title>
      <link href="https://robacarp.io/2024/04/11/price-is-right-search-in-javascript.html" rel="alternate" type="text/html" title="A Price is Right search in JavaScript" />
      <published>2024-04-11T11:52:44+00:00</published>
      <updated>2024-04-11T11:52:44+00:00</updated>
      <id>897783F9-B297-4B1E-B015-DEEC27125899</id>
      <content type="html" xml:base="https://robacarp.io/2024/04/11/price-is-right-search-in-javascript.html">&lt;p&gt;On &lt;a href=&quot;https://archive.fm&quot;&gt;Archive.fm&lt;/a&gt; I needed an array search feature which would return the item which matches the closest without going over – price is right rules for an array search.&lt;/p&gt;

&lt;p&gt;Here’s the JavaScript implementation I came up with:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;priceIsRightSearch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;moved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comparisonResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comparisonResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;moved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comparisonResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;moved&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mid&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here is how you use it:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;currentTimeOffset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;85.26&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;offsetBrackets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;39.94&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;59.16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;83.26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;104.25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;124.24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;146.3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;164.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;176.18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;187.22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;206.42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;237.62&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;249.62&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;270.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;297.14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;323.26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;359.54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;374.54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;405.76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;429.26&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;455.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;487.46&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;530.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;564.82&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;593.38&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;606.34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;646.24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;668.76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;702.42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;712.28&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;739.4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;772.12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;788&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;811.96&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;838.08&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;857.76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;885.4&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;closestBracketWithoutGoingOver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spanTimeOffsets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;priceIsRightSearch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;currentValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;currentValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;currentTimeOffset&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`The closest time without going over is &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;closestBracketWithoutGoingOver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="javascript" /><category term="algorithms" /><summary type="html">On Archive.fm I needed an array search feature which would return the item which matches the closest without going over – price is right rules for an array search.</summary></entry><entry>
      <title type="html">Iris MOT801 “V1” motion sensors</title>
      <link href="https://robacarp.io/2022/09/19/iris-v1-motion-sensor.html" rel="alternate" type="text/html" title="Iris MOT801 &quot;V1&quot; motion sensors" />
      <published>2022-09-19T10:31:24+00:00</published>
      <updated>2022-09-19T10:31:24+00:00</updated>
      <id>73DF4789-E91E-4165-94D9-424A8FBC4DD5</id>
      <content type="html" xml:base="https://robacarp.io/2022/09/19/iris-v1-motion-sensor.html">&lt;p&gt;This is a slow but reliable PIR Motion Sensor. The Lowes Iris line used Zigbee but has some proprietary preamble to the pairing process. Hubitat pairs them reliably enough, but has a special button for V1 Iris devices.&lt;/p&gt;

&lt;p&gt;When they’re available these motion sensors are &lt;em&gt;very&lt;/em&gt; cheap. I found a bag of 20 for $40.&lt;/p&gt;

&lt;p&gt;Model number MOT801 or MOT901, from eBay. The run for about a year on a CR-123A 3 volt battery.&lt;/p&gt;

&lt;h3 id=&quot;reset-process&quot;&gt;Reset process:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;pull battery for 10s.&lt;/li&gt;
  &lt;li&gt;insert battery.&lt;/li&gt;
  &lt;li&gt;press tamper switch 8x with 1s interval to reset.&lt;/li&gt;
  &lt;li&gt;LED will “pulse” or “heartbeat”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After reset, simply inserting the battery puts it into pairing mode.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=yK3fQ1R27cQ&quot;&gt;video&lt;/a&gt;&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="smart-home" /><category term="home-automation" /><category term="zigbee" /><category term="ebay" /><category term="iris" /><summary type="html">This is a slow but reliable PIR Motion Sensor. The Lowes Iris line used Zigbee but has some proprietary preamble to the pairing process. Hubitat pairs them reliably enough, but has a special button for V1 Iris devices.</summary></entry><entry>
      <title type="html">IKEA Trådfri Zigbee Repeater</title>
      <link href="https://robacarp.io/2022/09/14/ikea-zigbee-repeater.html" rel="alternate" type="text/html" title="IKEA Trådfri Zigbee Repeater" />
      <published>2022-09-14T11:34:34+00:00</published>
      <updated>2022-09-14T11:34:34+00:00</updated>
      <id>C8740677-DBFA-407D-82EC-8105991903B1</id>
      <content type="html" xml:base="https://robacarp.io/2022/09/14/ikea-zigbee-repeater.html">&lt;div style=&quot;display: flex; justify-content: space-between;&quot;&gt;
  &lt;div&gt;
    
&lt;p&gt;This works as a general purpose Zigbee Repeater.&lt;/p&gt;

&lt;p&gt;Factory Reset:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Paper clip to press button next to female USB port, hold for 5 seconds.&lt;/li&gt;
  &lt;li&gt;LED will blink quickly; then pulse quickly; then go back to pulsing slowly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pairing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Factory reset, then a single button press to pair.&lt;/li&gt;
&lt;/ul&gt;

  &lt;/div&gt;

  &lt;img style=&quot;width: 23em&quot; src=&quot;/images/posts/2022/Trafdri-signal-repeater-us-2022-09-14.png&quot; /&gt;
&lt;/div&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="smart-home" /><category term="home-automation" /><category term="zigbee" /><category term="ikea" /><category term="mesh-network" /><summary type="html"></summary></entry><entry>
      <title type="html">Hughes 206612 Zigbee Doorbell Sensor</title>
      <link href="https://robacarp.io/2022/09/13/hughes-206612-zigbee-doorbell-sensor.html" rel="alternate" type="text/html" title="Hughes 206612 Zigbee Doorbell Sensor" />
      <published>2022-09-13T16:17:00+00:00</published>
      <updated>2022-09-13T16:17:00+00:00</updated>
      <id>89FC2E57-EC65-46E3-8548-ACEF9FE8B6F7</id>
      <content type="html" xml:base="https://robacarp.io/2022/09/13/hughes-206612-zigbee-doorbell-sensor.html">&lt;p&gt;I’m convinced that one of the best smart devices ever made is the Hughes 206612 Zigbee Doorbell Sensor. They’re long out of production, but redily available on eBay for about $12 shipped.&lt;/p&gt;

&lt;p&gt;This device easily wires into an existing doorbell chime and is simply represented as a zigbee switch which is momentarily flipped “on” when someone rings your doorbell.&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Has no camera.&lt;/li&gt;
  &lt;li&gt;Will run for &lt;em&gt;years&lt;/em&gt; on a single CR123A.&lt;/li&gt;
  &lt;li&gt;Is small enough to fit inside most doorbell enclosures.&lt;/li&gt;
  &lt;li&gt;Is Zigbee connected, not WiFi.&lt;/li&gt;
  &lt;li&gt;Will send you a ping on your smart-tether when someone rings your doorbell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an age where literal entire subdivisions are being wired with Ring doorbells and Amazon has been known to sell Ring footage in bulk without a warrant, a slightly “dumber” doorbell is welcome.&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="smart-home" /><category term="home-automation" /><category term="zigbee" /><category term="ebay" /><summary type="html">I’m convinced that one of the best smart devices ever made is the Hughes 206612 Zigbee Doorbell Sensor. They’re long out of production, but redily available on eBay for about $12 shipped.</summary></entry><entry>
      <title type="html">Rendering build details.json with Crystal</title>
      <link href="https://robacarp.io/2022/01/28/details-file-for-crystal-docker-apps.html" rel="alternate" type="text/html" title="Rendering build details.json with Crystal" />
      <published>2022-01-28T13:18:17+00:00</published>
      <updated>2022-01-28T13:18:17+00:00</updated>
      <id>d7fe8968-75b5-4a75-a156-8f336683729f</id>
      <content type="html" xml:base="https://robacarp.io/2022/01/28/details-file-for-crystal-docker-apps.html">&lt;p&gt;This is a follow-up of my post on serving a &lt;a href=&quot;/2019/09/02/metadata_endpoint.html&quot;&gt;general metadata json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Solving this problem in &lt;a href=&quot;https://crystal-lang.org/&quot;&gt;Crystal&lt;/a&gt; provides a few
subtle problems that Ruby doesn’t experience:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Crystal is compiled, so the full source code isn’t available at runtime.&lt;/li&gt;
  &lt;li&gt;Crystal is type-safe, so nils must be handled gracefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this application the build details are rendered via docker build wrapper
script identically to the script in my previous entry. In contrast, I don’t
funnel the build details through a config object intermediary here.&lt;/p&gt;

&lt;p&gt;Here’s what I came up with to render a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/ping.json&lt;/code&gt; endpoint in a
&lt;a href=&quot;https://luckyframework.org&quot;&gt;Lucky&lt;/a&gt; action.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-crystal&quot; data-lang=&quot;crystal&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# src/lib/build.cr&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Build&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;DETAILS_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;build_details.json&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;details&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vc&quot;&gt;@@instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;details&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_timestamp&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;git_revision&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;getter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DETAILS_FILE&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;raw_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DETAILS_FILE&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;raw_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{}&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;parsed_details&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;raw_file&lt;/span&gt;

    &lt;span class=&quot;vi&quot;&gt;@build_timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;build_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;as_s?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;utc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@git_revision&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git_revision&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;as_s?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;000000000&quot;&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;as_s?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;development&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;details&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;build_timestamp&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@build_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;git_revision&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@git_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;crystal_version&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Crystal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VERSION&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-crystal&quot; data-lang=&quot;crystal&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Ping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BrowserAction&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/ping&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge!&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;details&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="crystal" /><category term="bash" /><category term="lucky" /><category term="docker" /><category term="build_details" /><category term="devops" /><summary type="html">This is a follow-up of my post on serving a general metadata json.</summary></entry><entry>
      <title type="html">Organizing files in a terraform codebase</title>
      <link href="https://robacarp.io/2021/11/11/organizing-terraform-files.html" rel="alternate" type="text/html" title="Organizing files in a terraform codebase" />
      <published>2021-11-11T20:28:01+00:00</published>
      <updated>2021-11-11T20:28:01+00:00</updated>
      <id>02a09b9e-fc4b-4193-b60d-adb9a6dbccae</id>
      <content type="html" xml:base="https://robacarp.io/2021/11/11/organizing-terraform-files.html">&lt;h3 id=&quot;organizing-files-in-a-terraform-codebase&quot;&gt;Organizing files in a terraform codebase&lt;/h3&gt;

&lt;p&gt;I started using terraform in about v0.8.3. At the time I thought “they’ll figure out how to organize my terraform codebase soon,” but they never have. This year v1.0 landed and you still cannot have folders in a terraform project, which is just bonkers to me.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;devops_repo/terraform&amp;gt; find . -iname &apos;*.tf&apos; | wc -l
     349
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The terraform projects I manage often have &lt;em&gt;hundreds&lt;/em&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tf&lt;/code&gt; files across dozens of “project domains” and at least a few AWS accounts. To reduce chaos and provide a safe infrastructure development environment, smaller discrete units of resources are broken apart from each other. Here is one file listing from a single environment, of a single application, for a single region:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.terraform/
.plan.out
.terraform-version
.terraform.lock.hcl
_data.tf
_main.tf
_vars.tf
acm_certificate.tf
asg.tf
asg_scheduled_actions.tf
cloudfront.tf
ec2_bastion.tf
elastic_domain.tf
iam_async_image_uploads.tf
iam_instance_profile.tf
iam_jenkins_permissions.tf
iam_policies.tf
iam_policies_parameters.tf
kms_key.tf
lb.tf
lb_downtime_rules.tf
lb_redirect_rules.tf
lb_static_rules.tf
log_group.tf
networking.tf
route53_dns.tf
s3_async_image_uploads.tf
s3_elb_logs.tf
s3_env_resources.tf
s3_public_assets.tf
s3_public_data.tf
s3_user_data.tf
waf_acl.tf
webapp.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;tenants-of-organization&quot;&gt;Tenants of organization&lt;/h3&gt;

&lt;p&gt;Assuming a project otherwise has:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Separate “states” for separate environments (Production, Staging, etc)&lt;/li&gt;
  &lt;li&gt;Modules which represent groups of terraform resources which are versioned together (Webapp, VPC, etc)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An individual terraform project domain can easily become difficult to manage. Questions arise about where resources live on the disk, “Where is the configuration for the s3 bucket that holds user data? user_data.tf? s3_policies.tf?”&lt;/p&gt;

&lt;p&gt;Here is my strategy:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Terraform “meta” should be underscore prefixed: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_main.tf&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data.tf&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;File names should be prefixed with the technology at play: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ec2&lt;/code&gt;, etc.&lt;/li&gt;
  &lt;li&gt;File names should represent the meaningful user-facing features the resources enable: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uploads&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;terraform-meta-files&quot;&gt;Terraform “meta” files&lt;/h4&gt;

&lt;p&gt;Terraform “meta” files should be underscore prefixed. Upon upgrading from terraform &amp;lt; 0.13 with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform 0.13upgrade&lt;/code&gt;, a “versions.tf” file will be created for you. This practice leaves important terraform meta-configuration files scattered throughout an alphabetical file listing. Prefixing all meta-configuration files with an underscore hoists them to the top of an alphabetical file listing.&lt;/p&gt;

&lt;p&gt;I tend to further break down the meta into: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_main.tf&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data.tf&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_locals.tf&lt;/code&gt;. Each of these tends to grow large enough that it’s easy to lose resources or configuration in the mess if they’re not separated out.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;_main.tf&lt;/em&gt; - holds a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform {}&lt;/code&gt; block, and all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provider &quot;&quot; {}&lt;/code&gt; declarations.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;_data.tf&lt;/em&gt; - holds all external resources needed for this project domain to apply. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data &quot;aws_vpc&quot; &quot;vpc&quot; {}&lt;/code&gt; etc.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;_locals.tf&lt;/em&gt; - holds all the “configuration variables” a project domain. Usually just a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locals {}&lt;/code&gt; block with a dozen or so variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;technology-prefixed-filenames-and-meaningful-user-facing-features&quot;&gt;Technology prefixed filenames, and meaningful user-facing features&lt;/h4&gt;

&lt;p&gt;Simply naming a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3.tf&lt;/code&gt; will get you part of the way there in a small project. When you have two s3 buckets in an infrastructure, you’ll get lost quickly.&lt;/p&gt;

&lt;p&gt;When a request comes in to modify the infrastructure, it’s usually stated in terms of the user-facing features it enables, e.g. “Task: allow users to upload files directly to S3.” As a result, a naming convention like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3_user_uploads.tf&lt;/code&gt; should allow you to quickly locate the resources which need to be modified for an infrastructure update.&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="terraform" /><category term="organizing" /><category term="aws" /><summary type="html">Organizing files in a terraform codebase</summary></entry><entry>
      <title type="html">Managing services on macos with launchctl</title>
      <link href="https://robacarp.io/2021/11/11/macos-launchctl.html" rel="alternate" type="text/html" title="Managing services on macos with launchctl" />
      <published>2021-11-11T09:28:01+00:00</published>
      <updated>2021-11-11T09:28:01+00:00</updated>
      <id>742ad5df-cde1-4077-8031-55cfdd0e23a6</id>
      <content type="html" xml:base="https://robacarp.io/2021/11/11/macos-launchctl.html">&lt;h3 id=&quot;managing-services-on-macos-with-launchctl&quot;&gt;Managing services on MacOS with launchctl&lt;/h3&gt;

&lt;p&gt;A lot of modern tools work hard to mask the service management system which has been built into MacOS. After researching it, you might want to mask and shim it too. Brew dedicates &lt;a href=&quot;https://github.com/Homebrew/homebrew-services/blob/0bae398d93f530230857aed8d23e5a99d90b14a8/lib/service/services_cli.rb&quot;&gt;a few hundred lines&lt;/a&gt; of ruby to interacting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchd&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man launchd&lt;/code&gt; - high level documentation on launchd&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man launchctl&lt;/code&gt; - documentation for interacting with launchd via launchctl&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man launchd.plist&lt;/code&gt; - specification for a launch plist&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;patterns&quot;&gt;Patterns&lt;/h3&gt;

&lt;p&gt;A service definition is written in MacOS plist format and saved as a .plist. They can be saved anywhere but by convention they’re located in one of two places:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;~/Library/LaunchAgents/ - for user-launched services.&lt;/li&gt;
  &lt;li&gt;/Libray/LaunchDaemons/ - for system-launched services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;System launched services are started at boot time, user-launched services are started when that user logs in.&lt;/p&gt;

&lt;p&gt;The distinction between Agent and Daemon is given in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man 8 launchd&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In the launchd lexicon, a daemon is, by definition, a system-wide service of which there is one instance for all clients. An agent is a service that runs on a per-user basis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;examples&quot;&gt;Examples&lt;/h3&gt;

&lt;p&gt;Installed in my ~/Library/LaunchAgents right now:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;homebrew postgres (homebrew.mxcl.postgresql.plist)&lt;/li&gt;
  &lt;li&gt;homebrew redis (homebrew.mxcl.redis.plist)&lt;/li&gt;
  &lt;li&gt;google keystone (com.google.keystone.agent.plist and com.google.keystone.xpcservice.plist)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And installed in my /Library/LaunchDaemons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;docker (com.docker.vmnetd.plist)&lt;/li&gt;
  &lt;li&gt;microsoft teams (com.microsoft.teams.TeamsUpdaterDaemon.plist)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;starting-stopping-and-inspecting&quot;&gt;Starting, stopping, and inspecting&lt;/h3&gt;

&lt;h4 id=&quot;deprecated-subcommands&quot;&gt;Deprecated subcommands&lt;/h4&gt;

&lt;p&gt;The manual cites these patterns as “legacy” though they continue to work.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ launchctl load ~/Library/LaunchAgents/com.whatever.whatever.plist
~/Library/LaunchAgents/com.whatever.whatever.plist operation in progress

$ launchctl unload ~/Library/LaunchAgents/com.whatever.whatever.plist
~/Library/LaunchAgents/com.whatever.whatever.plist operation in progress

$ launchctl list
PID	Status	Label
-	0	com.apple.whatever
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;list-services&quot;&gt;List services&lt;/h4&gt;

&lt;p&gt;There isn’t a great way to do this. The deprecated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt; subcommand is barely functional. brew service status &lt;a href=&quot;https://github.com/Homebrew/homebrew-services/blob/0bae398d93f530230857aed8d23e5a99d90b14a8/lib/service/services_cli.rb#L18&quot;&gt;uses launchctl list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The columnar data isn’t transparent, but it is helpful. From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man launchctl&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The first column displays the PID of the job if it is running.  The second column displays the last exit status of the job. If the number in this column is negative, it represents the negative of the signal which stopped the job.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;start-a-service&quot;&gt;Start a service&lt;/h4&gt;

&lt;p&gt;Given a service isn’t running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; launchctl list | grep redis
-	-9	homebrew.mxcl.redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It can be started with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kickstart&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; launchctl kickstart -p gui/501/homebrew.mxcl.redis
service spawned with pid: 91223
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;-p  Upon success, print the PID of the new process […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;stop-a-running-service&quot;&gt;Stop a running service&lt;/h4&gt;

&lt;p&gt;Given a running service:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; launchctl list | grep redis
91178	-3	homebrew.mxcl.redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Send it a signal:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; launchctl kill SIGKILL gui/501/homebrew.mxcl.redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And check that it has been killed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; launchctl list | grep redis
-	-9	homebrew.mxcl.redis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="macos" /><category term="launchd" /><category term="launchctl" /><category term="homebrew" /><summary type="html">Managing services on MacOS with launchctl</summary></entry><entry>
      <title type="html">Installing Crystal+Lucky on M1 Mac</title>
      <link href="https://robacarp.io/2021/10/05/crystal-lucky-m1.html" rel="alternate" type="text/html" title="Installing Crystal+Lucky on M1 Mac" />
      <published>2021-10-05T20:28:01+00:00</published>
      <updated>2021-10-05T20:28:01+00:00</updated>
      <id>ff020a07-8738-4c98-9b09-6ed726b5afe0</id>
      <content type="html" xml:base="https://robacarp.io/2021/10/05/crystal-lucky-m1.html">&lt;h3 id=&quot;macm1crystallucky&quot;&gt;Mac+M1+Crystal+Lucky&lt;/h3&gt;

&lt;p&gt;This is the series of steps I followed to get Crystal and Lucky running on a Mac M1 from scratch, as of Oct. 2021.&lt;/p&gt;

&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt; both exist.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt; will have to be created with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; but should be owned by your user.&lt;/li&gt;
  &lt;li&gt;Make sure your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$PATH&lt;/code&gt; includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt;. Doing so &lt;a href=&quot;https://askubuntu.com/questions/402353/how-to-add-home-username-bin-to-path&quot;&gt;is up to you&lt;/a&gt; and will vary a bit depending on your shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;installing-crystal&quot;&gt;Installing crystal&lt;/h3&gt;

&lt;p&gt;From &lt;a href=&quot;https://github.com/crystal-lang/distribution-scripts/pull/104#issuecomment-920887960&quot;&gt;this issue comment&lt;/a&gt; the “second gen” build comes as a tar.gz. &lt;a href=&quot;https://63866-6887813-gh.circle-artifacts.com/0/dist_packages/crystal-ci-univ2nd-dev-1-darwin-universal.tar.gz&quot;&gt;This is a direct link to the archive&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt&lt;/code&gt;, extract crystal-ci-univ2nd-dev-1.tar.gz.&lt;/li&gt;
  &lt;li&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch crystal ; chmod u+x crystal&lt;/code&gt; and then edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin/crystal&lt;/code&gt; to have these contents:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CRYSTAL_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;lib:/opt/crystal-ci-univ2nd-dev-1/src&apos;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CRYSTAL_LIBRARY_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;brew &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/lib&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LDFLAGS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/opt/homebrew/opt/openssl@1.1/lib&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CPPFLAGS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/opt/homebrew/opt/openssl@1.1/include&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PKG_CONFIG_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/opt/homebrew/opt/openssl@1.1/lib/pkgconfig&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/opt/homebrew/opt/openssl@1.1/bin:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/opt/crystal-ci-univ2nd-dev-1/bin/crystal&apos;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;which crystal&lt;/code&gt; should now show &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Users/user/bin/crystal&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crystal -v&lt;/code&gt; should now show:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Crystal 1.2.0-dev [d7f25e0c6] (2021-09-15)

LLVM: 10.0.0
Default target: aarch64-apple-darwin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;setting-up-shards&quot;&gt;Setting up shards&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;git@github.com:crystal-lang/shards.git&quot;&gt;Clone the shards repo&lt;/a&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/shards&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/shards&lt;/code&gt; run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt; run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s /opt/shards/bin/shards&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;which shards&lt;/code&gt; should now point to /opt/shards/bin/shards`&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shards --version&lt;/code&gt; should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shards 0.15.0 [6471b2b] (2021-10-04)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;setting-up-lucky-cli&quot;&gt;Setting up lucky cli&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;git@github.com:luckyframework/lucky_cli.git&quot;&gt;Clone the lucky_cli repo&lt;/a&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/lucky_cli&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/lucky_cli&lt;/code&gt; run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shards build&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/bin&lt;/code&gt; run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ln -s /opt/lucky_cli/bin/lucky&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="crystal" /><category term="lucky" /><category term="mac" /><category term="m1" /><summary type="html">Mac+M1+Crystal+Lucky</summary></entry><entry>
      <title type="html">Automatic build time metadata generator for ELB ping endpoints and other sanity.</title>
      <link href="https://robacarp.io/2019/09/02/metadata_endpoint.html" rel="alternate" type="text/html" title="Automatic build time metadata generator for ELB ping endpoints and other sanity." />
      <published>2019-09-02T07:55:09+00:00</published>
      <updated>2019-09-02T07:55:09+00:00</updated>
      <id>5d4812c7-db2a-49d0-b258-b135a7bf342c</id>
      <content type="html" xml:base="https://robacarp.io/2019/09/02/metadata_endpoint.html">&lt;p&gt;One of the few problems that comes with a fully automated deployment pipeline
is validating which version of code exists in production, and validating that
version is in production at all deployment sites. A good automated deployment
can be self documenting and provide hooks for api clients to understand what’s
deployed as well.&lt;/p&gt;

&lt;p&gt;Consider this meta-endpoint:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;curl &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; my.app.com/ping

HTTP1.1 200 OK
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;okay&quot;&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;rails_env&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;production&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;signature&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;AppName Backend (production) 0.0.16&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;git_revision&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;1234123451234512345123451234123451234512&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;0.0.16&quot;&lt;/span&gt;,
  &lt;span class=&quot;s2&quot;&gt;&quot;build_timestamp&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;2019-09-02 13:13:16&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For devops purposes, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git_revision&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_timestamp&lt;/code&gt; are helpful.&lt;/p&gt;

&lt;p&gt;For an api consumer, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signature&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; are helpful.&lt;/p&gt;

&lt;p&gt;For the application developer, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails_env&lt;/code&gt; (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mix_env&lt;/code&gt;, etc) is a good
sanity check. Some applications inspect the database connection, others check
to make sure an external API is stable, etc. For ELB purposes, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;okay: true&lt;/code&gt;
is also mirrored in&lt;/p&gt;

&lt;p&gt;And whatever self test sanity check desired can be wired up behind &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;okay: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simple set of tools is used to automatically generate this document at build
time, and some light application hooks serve up the file at runtime.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts/build&lt;/code&gt; generates a JSON file which contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git_revision&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt;,
and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_timestamp&lt;/code&gt;. This file is used by developers/CI to do a docker build,
so any build time environment declarations or other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build&lt;/code&gt; hijinks can
be committed to version control here as well. The usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trap ... EXIT&lt;/code&gt;
ensures that the BUILD_DETAILS file is available to the docker build but
doesn’t hang around after the build.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# scripts/build&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-euo&lt;/span&gt; pipefail

build_stats &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{ &quot;build_timestamp&quot; : &quot;&apos;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;+%Y-%m-%d %H:%M:%S&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\n&apos;&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&quot;, &quot;git_revision&quot; : &quot;&apos;&lt;/span&gt;
  git rev-parse HEAD | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\n&apos;&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&quot;, &quot;version&quot; : &quot;&apos;&lt;/span&gt;
  git tag &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;release &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;-&apos;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{ print $2 }&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-V&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\n&apos;&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&quot;}&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;cleanup &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;rm &lt;/span&gt;BUILD_DETAILS
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;trap &lt;/span&gt;cleanup EXIT
build_stats &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; BUILD_DETAILS

docker build &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; backend_server
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For a rails server, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/application.rb&lt;/code&gt; runs once at application boot, and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rails.application.config&lt;/code&gt; provides a global scope to stash the metadata.
Phoenix and other servers have a similar boot script and global scope config.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Backend&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Application&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;git_revision&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_signature&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Backend (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build_timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exist?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;BUILD_DETAILS&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;build_details&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;BUILD_DETAILS&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;git_revision&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git_revision&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_signature&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build_timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_details&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;build_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lastly, a rails StatusController which displays the expanded payload.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StatusController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# GET /ping&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Targeted by load balancer to check for server health.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;status_hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;okay: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;rails_env: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;signature: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_signature&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;revision&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;git_revision&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;status_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:git_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;revision&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server_version&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;status_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build_timestamp&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;status_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:build_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;http_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ok&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;http_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_acceptable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stable&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;render_json&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http_code&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stable&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# check stuff&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="bash" /><category term="rails" /><category term="aws" /><category term="elb" /><category term="docker" /><category term="devops" /><category term="build_details" /><summary type="html">One of the few problems that comes with a fully automated deployment pipeline is validating which version of code exists in production, and validating that version is in production at all deployment sites. A good automated deployment can be self documenting and provide hooks for api clients to understand what’s deployed as well.</summary></entry><entry>
      <title type="html">keeping your secrets hidden</title>
      <link href="https://robacarp.io/2019/06/12/secrets_in_aws_for_dailydrip.html" rel="alternate" type="text/html" title="keeping your secrets hidden" />
      <published>2019-06-12T11:43:11+00:00</published>
      <updated>2019-06-12T11:43:11+00:00</updated>
      <id>bfa8fe73-a834-4dd7-a761-7f4cf487fb9f</id>
      <content type="html" xml:base="https://robacarp.io/2019/06/12/secrets_in_aws_for_dailydrip.html">&lt;p&gt;There aren’t many rules when deploying applications to the cloud, but here’s one I think is pretty important:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Don’t commit your API keys to your git repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Perhaps even more important:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Don’t commit your database credentials to your git repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But your app needs to connect to Services and it surely needs to connect to the database. Many applications follow this pattern, demonstrated by a Rails &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;development&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;app_development&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;app_test&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV[&apos;DATABASE_URL&apos;] %&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s right, in production, this app reads the database connection string from an environment variable. Great! Now the application doesn’t need any secret credentials in the source code. But where does this environment config come from?&lt;/p&gt;

&lt;h3 id=&quot;deferring-responsibility&quot;&gt;Deferring Responsibility&lt;/h3&gt;

&lt;p&gt;Frequently, it comes from an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/environment&lt;/code&gt; file maintained by an Ansible guru or maybe even handcrafted by a local server shepherd.&lt;/p&gt;

&lt;p&gt;But how far down the line does that push the responsibility of secrecy? Who is taking care of the sacred credentials? All the way to the docker container, or perhaps to the VM.&lt;/p&gt;

&lt;p&gt;If you’re deploying to a PaaS, maybe you’re done there. As a service, they provide the database and they inject the credentials for you, perhaps even configured to serve up API keys for others services your app is connecting to.&lt;/p&gt;

&lt;p&gt;But if you’re managing your own VMs in AWS, or even using Elastic Container Service to orchestrate a docker cloud, you’re likely to end up with your database credentials hard coded into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/environment&lt;/code&gt; or worse, baked into a docker image for your production application.&lt;/p&gt;

&lt;p&gt;If your production container is downloaded, does it connect to the production database at boot? Yikes!&lt;/p&gt;

&lt;p&gt;If your VM is cast into an AMI and then used to stamp out new instances, does each new instance come with the credentials to access your database? Yikes!&lt;/p&gt;

&lt;p&gt;The solution is a little more delicate, but allows credentials and api keys to never be stored in an /etc/environment file, a docker container, or with the source code.&lt;/p&gt;

&lt;h3 id=&quot;enter-state-manager&quot;&gt;Enter State Manager&lt;/h3&gt;

&lt;p&gt;Amazon EC2 has a small utility allll the way at the bottom of that never-ending left hand pane of resources called “State Manager” under “Systems Manager Services” which stores secrets using at-rest encryption. Secrets can be requested via the AWS API, and access granted using an EC2 or ECS IAM Role.&lt;/p&gt;

&lt;p&gt;Create a KMS key:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;aws kms create-key &lt;span class=&quot;nt&quot;&gt;--description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;production secrets&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Upload a few secrets to your SSM “keychain”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;aws ssm put-parameter &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt; SecureString &lt;span class=&quot;nt&quot;&gt;--key-id&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$key_id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/production/database_string&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--value&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;postgresql://user:pass@host:port/db&apos;&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;aws ssm put-parameter &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt; SecureString &lt;span class=&quot;nt&quot;&gt;--key-id&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$key_id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/production/sendgrid_api_secret&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--value&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;SG.000000000000000000000000000&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now secrets can be read by API:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;aws ssm get-parameters-by-path &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;us-east-1 &lt;span class=&quot;nt&quot;&gt;--path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/production/ &lt;span class=&quot;nt&quot;&gt;--with-decryption&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;
{
  &quot;Parameters&quot;: [
    {
      &quot;Version&quot;: 1, 
      &quot;Type&quot;: &quot;SecureString&quot;, 
      &quot;Name&quot;: &quot;/production/database_string&quot;, 
      &quot;Value&quot;: &quot;postgresql://user:pass@host:port/db&quot;
    }, 
    {
      &quot;Version&quot;: 1, 
      &quot;Type&quot;: &quot;SecureString&quot;, 
      &quot;Name&quot;: &quot;/production/sendgrid_api_secret&quot;, 
      &quot;Value&quot;: &quot;SG.000000000000000000000000000&quot;
    }
  ]
}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;application-secret-discovery&quot;&gt;Application Secret Discovery&lt;/h3&gt;

&lt;p&gt;It’s necessary to configure your EC2 instance or ECS task definition to launch with an IAM role which grants access to your KMS key for decryption, and to the parameter store to read parameters with the correct path prefix.&lt;/p&gt;

&lt;p&gt;After that, assuming your server normally launches with the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/bin/my_app&lt;/code&gt;, a script like this can be used to wrap the server binary and inject the application secrets into the environment.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-euo&lt;/span&gt; pipefail

&lt;span class=&quot;c&quot;&gt;# Fetching environment variables from AWS&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;
  aws ssm get-parameters-by-path &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;us-east-1&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/production/&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--with-decryption&lt;/span&gt;
&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Format variables into Bash exports&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$vars&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;--exit-status&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--raw-output&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;
      .Parameters[] |
      {
        name: .Name | capture(&quot;/(.*/)(?&amp;lt;parsed_name&amp;gt;.*)&quot;) | .parsed_name,
        value: .Value
      } |
      &quot;export \(.name)=\&quot;\(.value)\&quot;&quot;
    &apos;&lt;/span&gt;
&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# eval the exports, including the environment variables into the environment&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;eval&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$exports&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# hand off this process and the modified environment to the application server&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; /bin/my_app&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;more-benefits&quot;&gt;More Benefits&lt;/h3&gt;

&lt;p&gt;Storing credentials in a central repository not only helps prevent leaks, but it also aids in changing credentials.&lt;/p&gt;

&lt;p&gt;Instead of needing to touch each server and update credentials manually, update everything with an Ansible run, or rebuild containers to update secrets, it’s as simple as updating a secret in one place and restarting services. Each service will fetch its own credentials again at boot and connect with the new secrets.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This post was originally written for &lt;a href=&quot;https://www.dailydrip.com/&quot;&gt;DailyDrip.com&lt;/a&gt; and published on 2018-08-01. &lt;a href=&quot;https://www.smoothterminal.com/articles/keeping-your-secrets-hidden&quot;&gt;Link to the original&lt;/a&gt;.&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="bash" /><category term="aws" /><category term="secret" /><category term="password" /><summary type="html">There aren’t many rules when deploying applications to the cloud, but here’s one I think is pretty important:</summary></entry><entry>
      <title type="html">Notes on setting up a raspberry-pi from a bare image</title>
      <link href="https://robacarp.io/2019/06/09/raspberry_pi_setup_notes.html" rel="alternate" type="text/html" title="Notes on setting up a raspberry-pi from a bare image" />
      <published>2019-06-09T20:58:51+00:00</published>
      <updated>2019-06-09T20:58:51+00:00</updated>
      <id>3381d216-2d2f-43e5-b684-8c8348dc88da</id>
      <content type="html" xml:base="https://robacarp.io/2019/06/09/raspberry_pi_setup_notes.html">&lt;h2 id=&quot;acquire-an-image&quot;&gt;Acquire an image&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/downloads/raspbian/&quot;&gt;https://www.raspberrypi.org/downloads/raspbian/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;burn-the-img&quot;&gt;Burn the .img&lt;/h2&gt;

&lt;p&gt;On macOS.&lt;/p&gt;

&lt;p&gt;Find the dev node for the disk:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# find disk&lt;/span&gt;
diskutil list

&lt;span class=&quot;c&quot;&gt;# unmount disk (eg disk3)&lt;/span&gt;
diskutil unmountDisk /dev/rdisk3

&lt;span class=&quot;c&quot;&gt;# copy image on disk3&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo dd &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1m &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/Downloads/raspbian-stretch-lite-2019-04-08-hkcam-v0.0.8-armv6.img &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/rdisk3 &lt;span class=&quot;nv&quot;&gt;conv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;pre-first-boot-modifications&quot;&gt;Pre-first-boot modifications:&lt;/h2&gt;

&lt;p&gt;A bunch of stuff can be enabled or disabled by dropping magic files into the boot partition which save a &lt;em&gt;bunch&lt;/em&gt; of time.&lt;/p&gt;

&lt;h4 id=&quot;wifi-config&quot;&gt;WiFi Config&lt;/h4&gt;

&lt;p&gt;On the SD card, before booting it for the first time:&lt;/p&gt;

&lt;p&gt;Create a file on the boot partition called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wpa_supplicant.conf&lt;/code&gt; with these contents:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
        ssid=&quot;network&quot;
        psk=&quot;password&quot;
        id_str=&quot;new&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;enable-ssh&quot;&gt;Enable SSH&lt;/h4&gt;

&lt;p&gt;On the SD card, before booting it for the first time:&lt;/p&gt;

&lt;p&gt;Touch a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; in the boot folder, this will configure the system to allow ssh.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/documentation/remote-access/ssh/README.md&quot;&gt;https://www.raspberrypi.org/documentation/remote-access/ssh/README.md&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;enable-the-camera&quot;&gt;Enable the camera&lt;/h2&gt;

&lt;p&gt;sudo raspi-config nonint do_camera 0&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://raspberrypi.stackexchange.com/questions/10357/enable-camera-without-raspi-config/93187#93187&quot;&gt;https://raspberrypi.stackexchange.com/questions/10357/enable-camera-without-raspi-config/93187#93187&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.raspberrypi.org/documentation/configuration/camera.md&quot;&gt;https://www.raspberrypi.org/documentation/configuration/camera.md&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;disable-the-camera-led&quot;&gt;Disable the camera LED&lt;/h2&gt;

&lt;p&gt;First up, we need to edit the config file.&lt;/p&gt;

&lt;p&gt;sudo nano /boot/config.txt&lt;/p&gt;

&lt;p&gt;Add the following line to the end of the file, then save it.&lt;/p&gt;

&lt;p&gt;disable_camera_led=1&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://thepihut.com/blogs/raspberry-pi-tutorials/16043032-stealth-cam-how-to-disable-the-raspberry-pi-camera-led&quot;&gt;https://thepihut.com/blogs/raspberry-pi-tutorials/16043032-stealth-cam-how-to-disable-the-raspberry-pi-camera-led&lt;/a&gt;&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="bash" /><category term="raspberry-pi" /><category term="diskutil" /><category term="wpa_supplicant" /><category term="ssh" /><summary type="html">Acquire an image</summary></entry><entry>
      <title type="html">Notes on installing HKCam on a raspberry-pi with systemd</title>
      <link href="https://robacarp.io/2019/06/09/hkcam_manual_installation.html" rel="alternate" type="text/html" title="Notes on installing HKCam on a raspberry-pi with systemd" />
      <published>2019-06-09T20:44:29+00:00</published>
      <updated>2019-06-09T20:44:29+00:00</updated>
      <id>9d507254-6025-4eb3-955b-5c500167c09b</id>
      <content type="html" xml:base="https://robacarp.io/2019/06/09/hkcam_manual_installation.html">&lt;h1 id=&quot;installing-hkcam-on-a-raspberry-pi-with-systemd&quot;&gt;Installing HKCam on a raspberry pi with systemd&lt;/h1&gt;

&lt;p&gt;The documentation for manually installing HKCam on a Raspberry PI are frustratingly obscured in an Ansible playbook. This is what I did to get it up and running manually.&lt;/p&gt;

&lt;h2 id=&quot;my-system-info&quot;&gt;My System Info:&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/debian_version
&lt;span class=&quot;go&quot;&gt;9.8
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/issue
&lt;span class=&quot;go&quot;&gt;Raspbian GNU/Linux 9 \n \l&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;install-process&quot;&gt;Install Process:&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;Installing &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;raspberrypi-kernel-headers&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; prevents the
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kernel headers for this kernel does not seem to be installed.&quot;&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;message from appearing when installing v4l2loopback with apt-get:
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;Module build &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;kernel 4.14.98-v7+ was skipped since the
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;kernel headers &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;this kernel does not seem to be installed.
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;Also sometimes this error is shown when attempting to attach with &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;modprobe v4l2loopback&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;modprobe: FATAL: Module v4l2loopback not found &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;directory /lib/modules/4.14.98-v7+
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;raspberrypi-kernel-headers
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;v4l2loopback
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ffmpeg
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;options v4l2loopback video_nr=1&apos;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/modprobe.d/v4l2loopback.conf
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;modprobe v4l2loopback
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;v4l2loopback | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/modules
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;modprobe bcm2835-v4l2
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;bcm2835-v4l2 | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; /etc/modules
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;visit https://github.com/brutella/hkcam/releases and download a tar.gz
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;wget https://github.com/brutella/hkcam/releases/download/v0.0.8/hkcam-v0.0.8_linux_armhf.tar.gz
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;xzf hkcam-v0.0.8_linux_armhf.tar.gz
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;hkcam-v0.0.8_linux_armhf/
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo cp &lt;/span&gt;usr/bin/hkcam /usr/local/bin
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;adduser &lt;span class=&quot;nt&quot;&gt;--system&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--ingroup&lt;/span&gt; video hkcam&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;systemd-service-configuration&quot;&gt;Systemd Service Configuration:&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;c&quot;&gt;# /etc/systemd/system/hkcam.service
&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;[Unit]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;HKcam HomeKit Camera&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;syslog.target network-online.target&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[Service]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;simple&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hkcam&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;WorkingDirectory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/home/hkcam&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/usr/local/bin/hkcam&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Restart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;always&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;RestartSec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;KillMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;process&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[Install]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;multi-user.target&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;run-script&quot;&gt;Run script&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# /etc/hkcam&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# set the video4linux ctl parameters&lt;/span&gt;
v4l2-ctl &lt;span class=&quot;nt&quot;&gt;--set-fmt-video&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1280,height&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;720,pixelformat&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YU12
v4l2-ctl &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;video_bitrate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1000000

&lt;span class=&quot;c&quot;&gt;# exec over to hkcam&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; /usr/local/bin/hkcam &lt;span class=&quot;nt&quot;&gt;--min_video_bitrate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;500&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;activate-the-service&quot;&gt;Activate the service:&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-console&quot; data-lang=&quot;console&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl start hkcam
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;hkcam&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="bash" /><category term="systemd" /><category term="raspberry-pi" /><summary type="html">Installing HKCam on a raspberry pi with systemd</summary></entry><entry>
      <title type="html">bash hr function</title>
      <link href="https://robacarp.io/2018/07/19/bash-hr-function-to-draw-a-line-across-the-terminal.html" rel="alternate" type="text/html" title="bash hr function" />
      <published>2018-07-19T10:24:50+00:00</published>
      <updated>2018-07-19T10:24:50+00:00</updated>
      <id>5c88bbf1-e1e7-444f-943a-0a3b7e159ab6</id>
      <content type="html" xml:base="https://robacarp.io/2018/07/19/bash-hr-function-to-draw-a-line-across-the-terminal.html">&lt;p&gt;I write a lot of bash scripts to automate different facets of developer workflows, and the user interface of a script is a place I try to pay attention to.&lt;/p&gt;

&lt;p&gt;It’s easy to write scripts which do the right thing, but it’s harder to write scripts which will be sturdy and insulated from bitrot inflicted as the architecture changes around them.&lt;/p&gt;

&lt;p&gt;Adequate feedback is also really helpful. How do I know the script did the right thing?&lt;/p&gt;

&lt;p&gt;One of the functions I think adds a lot to the output of a script is this little gem which prints an &amp;lt;hr&amp;gt; in the console:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;hr&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Text written into the horizontal rule, left justified&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-}&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# set the color&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;33[30;47m&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# print the message&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# finish the line across the console&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;expr&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;tput cols&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; - &lt;span class=&quot;nv&quot;&gt;$length&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; %&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cols&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;s&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# clear the background color and start a new line&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;33[0m&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It looks like this in the console:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/2018/bash-hr-function-2018-07-19 10-24.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="bash" /><category term="script" /><category term="user-interface" /><summary type="html">I write a lot of bash scripts to automate different facets of developer workflows, and the user interface of a script is a place I try to pay attention to.</summary></entry><entry>
      <title type="html">Notes on FRS, GMRS, MURS radios</title>
      <link href="https://robacarp.io/2018/07/08/notes-on-frs-gmrs-murs.html" rel="alternate" type="text/html" title="Notes on FRS, GMRS, MURS radios" />
      <published>2018-07-08T21:07:07+00:00</published>
      <updated>2018-07-08T21:07:07+00:00</updated>
      <id>0962dfe0-dfec-466c-a2cb-024238526812</id>
      <content type="html" xml:base="https://robacarp.io/2018/07/08/notes-on-frs-gmrs-murs.html">&lt;h3 id=&quot;frs&quot;&gt;FRS&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Modulation: FM&lt;/li&gt;
  &lt;li&gt;2.5 kHz deviation (Narrow band)&lt;/li&gt;
  &lt;li&gt;12.5kHz channel spacing&lt;/li&gt;
  &lt;li&gt;Detachable antenna not allowed, limiting usable range&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;gmrs&quot;&gt;GMRS&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Modulation: FM&lt;/li&gt;
  &lt;li&gt;2.5 kHz or 5 kHz deviation&lt;/li&gt;
  &lt;li&gt;12.5 kHz channel spacing&lt;/li&gt;
  &lt;li&gt;GMRS license and Call sign required:
    &lt;ul&gt;
      &lt;li&gt;Identified at least every 15 min, and at end of transmission series&lt;/li&gt;
      &lt;li&gt;Morse or spoken&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Repeaters allowed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;murs&quot;&gt;MURS&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-Use_Radio_Service#Authorized_modes&quot;&gt;Modulation: AM, FM, Phase Modulation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;60kHz spacing on channels 1-3&lt;/li&gt;
  &lt;li&gt;30kHz between channels 4 and 5&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;frequencies-as-of-2018&quot;&gt;Frequencies as of 2018&lt;/h3&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Channel #&lt;/th&gt;&lt;th&gt;Frequency (MHz)&lt;/th&gt;&lt;th&gt;Deviation&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;FRS/GMRS shared channels, Max Power: 2 W FRS / 5 W GMRS&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 1 / GMRS 9&lt;/td&gt; &lt;td&gt;1&lt;/td&gt;&lt;td&gt;462.5625 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 2 / GMRS 10&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;462.5875 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 3 / GMRS 11&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;462.6125 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 4 / GMRS 12&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;462.6375 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 5 / GMRS 13&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;462.6625 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 6 / GMRS 14&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;462.6875 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 7 / GMRS 15&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;462.7125 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;

  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;FRS Channels, Max Power: 0.5 W&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 8&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;467.5625 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 9&lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;467.5875 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 10&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;467.6125 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 11&lt;/td&gt;&lt;td&gt;11&lt;/td&gt;&lt;td&gt;467.6375 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 12&lt;/td&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;467.6625 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 13&lt;/td&gt;&lt;td&gt;13&lt;/td&gt;&lt;td&gt;467.6875 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;FRS 14&lt;/td&gt;&lt;td&gt;14&lt;/td&gt;&lt;td&gt;467.7125 &lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;

  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;Dedicated GMRS Channels&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 1&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;462.5500 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 2&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;td&gt;462.5750 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 3&lt;/td&gt;&lt;td&gt;17&lt;/td&gt;&lt;td&gt;462.6000 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 4&lt;/td&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;462.6250 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 5&lt;/td&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;462.6500 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 6&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;462.6750 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 7&lt;/td&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;462.7000 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;GMRS 8&lt;/td&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;462.7250 &lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;

  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;MURS Channels&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;MURS 1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;151.820&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;MURS 2&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;151.880&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;MURS 3&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;151.940&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;MURS 4&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;154.570&lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;MURS 5&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;154.600&lt;/td&gt;&lt;td&gt;W/N&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;h3 id=&quot;power-considerations&quot;&gt;Power considerations&lt;/h3&gt;

&lt;p&gt;FRS radios:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;are limited to 0.5W on channels 8-14.&lt;/li&gt;
  &lt;li&gt;are limited to 2W on channels 15-22.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Family_Radio_Service#Technical_information&quot;&gt;pre-2017 Radios &lt;em&gt;may&lt;/em&gt; be limited to 0.5W on all channels&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GMRS radios (with FCC issued license)&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;are limited to 5W on channels 1-7.&lt;/li&gt;
  &lt;li&gt;are limited to 0.5W on channels 8-14.&lt;/li&gt;
  &lt;li&gt;are limited to 50W on channels 15-22&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;gmrs-repeaters&quot;&gt;GMRS Repeaters&lt;/h3&gt;

&lt;p&gt;GMRS Repeaters listen at +5MHz and broadcast on regular channels. GMRS Channel 1 = 462.550, GMRS-R input = 467.550.&lt;/p&gt;</content><author>
        <name>Robert Carpenter</name></author><category term="sdr" /><category term="radio" /><category term="gmrs" /><category term="frs" /><summary type="html">FRS</summary></entry></feed>

