<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Philip House</title>
    <description>A personal collection of thoughts on programming, learning, and other things.</description>
    <link>http://phizzle.space/</link>
    <atom:link href="http://phizzle.space/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 30 Dec 2023 16:01:23 -0600</pubDate>
    <lastBuildDate>Sat, 30 Dec 2023 16:01:23 -0600</lastBuildDate>
    <generator>Jekyll v3.7.4</generator>
    
      <item>
        <title>Encrypting Existing RDS Instances</title>
        <description>&lt;p&gt;It’s been a while since I last wrote, and life has gotten a bit busier since
I last posted. My family welcomed a baby girl into the world, and we have
since found out we have another on the way. There have certainly been things
worth writing about, but none stood out so much as this topic.&lt;/p&gt;

&lt;p&gt;Our company recently had to modify all of our RDS instances to be
encrypted-at-rest for compliance reasons. While this is now the default in
AWS, at the time when we started building our infrastructure (late 2017),
this was not the case. Moving our large production instance with
minimal-downtime was not as simple as we hoped, and required
experimentation and cobbling together various sources of information and
tooling.&lt;/p&gt;

&lt;p&gt;The AWS documentation has a &lt;a href=&quot;https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/encrypt-an-existing-amazon-rds-for-postgresql-db-instance.html&quot;&gt;dedicated article&lt;/a&gt; for this topic, which
does a great job of giving a high-level overview of what needs to be done.
Unfortunately, there are some critical details in my opinion that are missing.
I’d like to share our experience of what gaps we had to fill to get
everything to work smoothly. I recommend doing this migration on a
less-critical database to get some confidence and real-time experience
before doing it in production. To give you some sense of time,
the total migration time was about 5 hours, and application downtime lasted
15 minutes.&lt;/p&gt;

&lt;p&gt;If you are a db admin, you will find this elementary. This is
written for those of us that are used to RDS magic, but find ourselves having to
make a more manual operational change than we’re used to.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#migration-overview&quot;&gt;Migration Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#detailed-walkthrough&quot;&gt;Detailed Walkthrough&lt;/a&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#step-1-encrypting-the-source-snapshot&quot;&gt;Step 1: Encrypting the Source Snapshot&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-2-creating-the-encrypted-target-instance&quot;&gt;Step 2: Creating the Encrypted Target Instance&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-3-preparing-target-instance-for-replication&quot;&gt;Step 3: Preparing Target Instance for Replication&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-4-configuring-and-running-the-dms-task&quot;&gt;Step 4: Configuring and Running the DMS Task&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-5-stopping-writes-on-the-source-instance&quot;&gt;Step 5: Stopping Writes on the Source Instance&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-6-restoring-foreign-keys-triggers-and-sequences&quot;&gt;Step 6: Restoring Foreign Keys, Triggers and Sequences&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-7-ending-downtime-cleaning-up&quot;&gt;Step 7: Ending Downtime, Cleaning Up&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#random-notes&quot;&gt;Random Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; 
 &lt;/p&gt;

&lt;h3 id=&quot;migration-overview&quot;&gt;Migration Overview&lt;/h3&gt;
&lt;p&gt;As I mentioned, I do highly recommend reading through the AWS tutorial above
as it does give a good lay of the land. Just to briefly recap, here are the
big items that need to happen:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a new snapshot of the source database. Copy the snapshot, and
 encrypt it.&lt;/li&gt;
  &lt;li&gt;Start a new target database with the encrypted snapshot.&lt;/li&gt;
  &lt;li&gt;Disable foreign keys and triggers on target database.&lt;/li&gt;
  &lt;li&gt;Setup a DMS replication task that replicates source -&amp;gt; target, continuously.&lt;/li&gt;
  &lt;li&gt;Once DMS task is caught up, shut off writes to the source database.&lt;/li&gt;
  &lt;li&gt;Re-enable foreign keys, triggers and sequences on target database.&lt;/li&gt;
  &lt;li&gt;Switch over DNS entry to new target database, resume as normal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are not already familiar with DMS (AWS’s Database Migration Service),
you’ll need to setup a replication instance ahead of time. You also need to
make sure there are replication endpoints for both the source and target
databases. I recommend testing the source endpoint before you start just to
get it out of the way.&lt;/p&gt;

&lt;p&gt;I’m also assuming you are using CNAME dns records in your application settings
instead of the RDS endpoint directly. If you are not doing this, go ahead and
set that up first, as it will allow you to reduce downtime and make it easy to
cutover when the time comes.&lt;/p&gt;

&lt;p&gt; 
 &lt;/p&gt;

&lt;h3 id=&quot;detailed-walkthrough&quot;&gt;Detailed Walkthrough&lt;/h3&gt;

&lt;p&gt;Some of these steps are straightforward, but some of them have a ton of
configuration or confusing options to sort through. I’ll try and walk through
each of them that we had to consider ourselves.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress1.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-1-encrypting-the-source-snapshot&quot;&gt;Step 1: Encrypting the Source Snapshot&lt;/h4&gt;

&lt;p&gt;Copying a snapshot and encrypting it is basic. The only thing to think about
here is whether you want to use the default RDS KMS key for encryption, or a
customer or self-managed one. The encryption checkbox is all the way at the
bottom of the page.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress2.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-2-creating-the-encrypted-target-instance&quot;&gt;Step 2: Creating the Encrypted Target Instance&lt;/h4&gt;

&lt;p&gt;Creating a new target database is straightforward as well, you just need to
make sure you copy over &lt;em&gt;all&lt;/em&gt; the settings over from the existing database.
Make sure you use the same engine settings, network settings, master password
(or IAM auth), parameter group and option group. Double-check this, failing to
match this can cause time-consuming issues further in the process.&lt;/p&gt;

&lt;p&gt;Once this is done, make sure to create a DMS endpoint for the target database,
and test the connection. This database won’t populate in the DMS dropdown until
it is fully deployed and available.&lt;/p&gt;

&lt;p&gt; 
 &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress3.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-3-preparing-target-instance-for-replication&quot;&gt;Step 3: Preparing Target Instance for Replication&lt;/h4&gt;

&lt;p&gt;In order for the DMS replication task to work, foreign keys and triggers on
the target database need to be disabled. Even on a small database, doing this
manually is almost impossible to do reliably. We created a script that
generates SQL statements for dropping the foreign keys, and used the following
SQL query to generate all foreign keys to drop.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-- list all foreign keys on a table in the public schema
SELECT conrelid::regclass AS table_name, 
       conname AS foreign_key
FROM   pg_constraint 
WHERE  contype = 'f' 
AND    connamespace = 'public'::regnamespace   
ORDER  BY conrelid::regclass::text, contype DESC;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We then templated in the &lt;code class=&quot;highlighter-rouge&quot;&gt;table_name&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;foreign_key&lt;/code&gt; columns from above into
the following template in a custom Python script. Please note this is
potentially dangerous with unsanitized input and should only be done from known
input that you verify yourself. Our script is basic and not ready for
open-source, so you can template it however you’d like. I do recommend
scripting this so that you can generate this on-demand for different
databases. You can also just template out the statements directly in your SQL
query if you prefer as well.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ALTER TABLE {table} DROP CONSTRAINT {fk};\n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Disabling triggers is not so bad, as you can disable triggers without
completely deleting them. If you have more than a few triggers, I recommend
having this ready to go ahead of time. The less you have to generate on the fly
during the migration, the better - prework as much as you can.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-- select all triggers
SELECT event_object_table AS tab_name ,trigger_name
 FROM information_schema.triggers
 GROUP BY tab_name,   trigger_name
 ORDER BY tab_name,trigger_name ;

-- update trigger manually
ALTER TABLE &amp;lt;table_name&amp;gt; DISABLE TRIGGER &amp;lt;trigger_name&amp;gt;;
ALTER TABLE &amp;lt;table_name&amp;gt; ENABLE TRIGGER &amp;lt;trigger_name&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt; 
 &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress4.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-4-configuring-and-running-the-dms-task&quot;&gt;Step 4: Configuring and Running the DMS Task&lt;/h4&gt;

&lt;p&gt;Once the target database is ready, it’s time to start replication using DMS.
There are a lot of levers in the AWS console here, and it’s important to get
them right.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Migration type&lt;/strong&gt;: Migrate existing data and replicate ongoing changes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Task Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Target table preparation mode&lt;/strong&gt;: Truncate&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Task Settings&lt;/strong&gt;: Enable validation&lt;/li&gt;
  &lt;li&gt;Check &lt;code class=&quot;highlighter-rouge&quot;&gt;Turn on Cloudwatch logs&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Check box at bottom, to keep task from starting upon setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For some reason, the AWS migration guide specifies using the &lt;code class=&quot;highlighter-rouge&quot;&gt;Truncate&lt;/code&gt; mode,
which clears all row data, and migrates rows from scratch (not the schema).
Because of this, we cannot use &lt;code class=&quot;highlighter-rouge&quot;&gt;set_replication_role&lt;/code&gt; in &lt;code class=&quot;highlighter-rouge&quot;&gt;replica&lt;/code&gt; mode (see
&lt;a href=&quot;#random-notes&quot;&gt;note&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We enabled validation also according to the AWS documentation. This extends the
actual migration task process by quite some time, but it does give peace of
mind. I recommend turning on the logs in Cloudwatch as this gives you
good visibility into what is breaking and why. If you forget to remove a
foreign key, for example, the logs will show why certain tables are not able to
be replicated properly.&lt;/p&gt;

&lt;p&gt;Configuring the task but &lt;em&gt;not&lt;/em&gt; starting it allows for one more chance
to review things and make sure things are all set before you go. If you have
already removed foreign keys and triggers, you can also just go for it.&lt;/p&gt;

&lt;p&gt;Once the target database is ready and you’ve configured the DMS task, you are
ready to start the task. Depending on your database size, this could take anywhere
from 1-3 hours. In our case, the replication took 30 minutes but the validation
took almost 1 hour after the replication was finished. The AWS console gives a
good overview of what tables are in progress, and don’t forget to scroll all
the way to the right to see the full table metrics.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress5.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-5-stopping-writes-on-the-source-instance&quot;&gt;Step 5: Stopping Writes on the Source Instance&lt;/h4&gt;

&lt;p&gt;Once the DMS task is at 100% and validation is complete, you are now ready to
begin the cutting over process, and start downtime. Before we can switch over
to using the target database, we need to stop any new writes to the source
database, so that nothing gets lost. How you do this in practice will depend
on your architecture. In our case, it made sense to add a restrictive
security group that only allowed access from dev machines. You still need access
to the source database, so make sure you have a way to connect, however that
is.&lt;/p&gt;

&lt;p&gt;In addition to stopping application access, you can also stop the DMS
replication task at this time. It will take a minute or two, but you should see
connection activity and CPU activity drop significantly on the source.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress6.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-6-restoring-foreign-keys-triggers-and-sequences&quot;&gt;Step 6: Restoring Foreign Keys, Triggers and Sequences&lt;/h4&gt;

&lt;p&gt;This step is the most complex, and time critical as you are on the clock with
application downtime at this point. I &lt;strong&gt;really recommend going through this step
against a test database at least once&lt;/strong&gt; before you take down your production
environment.&lt;/p&gt;

&lt;p&gt;When searching for details about this process, I came across
&lt;a href=&quot;https://github.com/sinwoobang/dms-psql-post-data&quot;&gt;this project&lt;/a&gt; by &lt;a href=&quot;https://sinwoobang.notion.site/sinwoobang/Sin-Woo-Bang-796475b665ec48c39d721a9343f3dabf&quot;&gt;Sin-Woo Bang&lt;/a&gt;. He built some tooling for
automating the cleanup tasks to get your target database ready for the
switchover. It’s pretty well-documented and I recommend using it to restore
your foreign keys and sequences. An aside here, it does attempt to restore
indexes as well as foreign key constraints, but those error out quietly as they
already exist. He saved us a bunch of work, and helped answer some questions we
had about the process as well. Thanks Sin-Woo!&lt;/p&gt;

&lt;p&gt;Once you have restored your sequences and foreign keys, you can turn the
triggers back on, using the SQL from Step 3. At this point, planned downtime
can come to an end. I hope you can understand why running through this step
in practice is important, the faster you do this, the quicker your users are
back online.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/progress7.png&quot; alt=&quot;&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-7-ending-downtime-cleaning-up&quot;&gt;Step 7: Ending Downtime, Cleaning Up&lt;/h4&gt;

&lt;p&gt;At this point, you can point your CNAME dns record at the new target database,
and restart your services if necessary. Traffic should pick up on the new
encrypted database as normal, and you should be all set. Once things are
stable, you can go back and start cleaning up the mess left in your wake. A
couple things to remember:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Take a final snapshot of the source database, and shut it down, once you
feel confident that the target database has taken over without issue.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cleanup the DMS task, and replication instance once it is no longer needed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create any read-replicas as necessary for the new target database.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Other than that, you are done! Let me if you have any questions or experience
doing the same - I’m sure there are some other notes and tips I could add here.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3 id=&quot;random-notes&quot;&gt;Random Notes&lt;/h3&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4 id=&quot;why-not-use-session_replication_role-to-disable-foreign-keys&quot;&gt;Why not use &lt;code class=&quot;highlighter-rouge&quot;&gt;session_replication_role&lt;/code&gt; to disable foreign keys?&lt;/h4&gt;

&lt;p&gt;When doing the preparation, I came across &lt;a href=&quot;https://stackoverflow.com/questions/38112379/disable-postgresql-foreign-key-checks-for-migrations/49584660#49584660&quot;&gt;several&lt;/a&gt;
&lt;a href=&quot;https://www.pythian.com/blog/migrate-postgres-database-from-ec2-instance-to-rds-using-aws-dms-data-migration-services&quot;&gt;resources&lt;/a&gt; that mentioned you could set the replication
role to &lt;code class=&quot;highlighter-rouge&quot;&gt;replica&lt;/code&gt; for the DMS migration, without requiring disabling
foreign keys and triggers. I tried this route, but unfortunately found that
this wouldn’t work when using the &lt;code class=&quot;highlighter-rouge&quot;&gt;TRUNCATE&lt;/code&gt; option in DMS, as mentioned in
the &lt;a href=&quot;https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.PostgreSQL.html&quot;&gt;DMS documentation here&lt;/a&gt;. I believe you could use DMS to
replicate without using &lt;code class=&quot;highlighter-rouge&quot;&gt;TRUNCATE&lt;/code&gt;, but given I am not an expert, I opted to
follow the process laid out specifically for the encryption migration. I would
love to hear how to make it work using this, as it is much simpler.&lt;/p&gt;

&lt;div class=&quot;language-sql 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;c1&quot;&gt;-- prepare for migration&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_replication_role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'replica'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- post migration re-enablement&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_replication_role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'origin'&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;See note about &lt;code class=&quot;highlighter-rouge&quot;&gt;session_replication_role&lt;/code&gt; being incompatible with &lt;code class=&quot;highlighter-rouge&quot;&gt;TRUNCATE&lt;/code&gt;
operations, when foreign key constraints exist.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;PostgreSQL has a failsafe mechanism to prevent a table from being truncated, even when
                session_replication_role is set. You can use this as an alternative to
            disabling triggers, to help the full load run to completion. To do this, set the target
            table preparation mode to DO_NOTHING. Otherwise, DROP and TRUNCATE
            operations fail when there are foreign key constraints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; 
 &lt;/p&gt;

&lt;h4 id=&quot;how-do-i-migrate-my-source-database-replicas&quot;&gt;How do I migrate my source database replicas?&lt;/h4&gt;

&lt;p&gt;The simplest thing to do is leave your source replicas untouched, and setup
the new target database with replicas once the target is generally available.
In most scenarios, I imagine you can allow applications to use the original
read replica, as data will be slightly stale but available until the switch.
If that kind of lag is unnacceptable, you will have to either settle for more
downtime on services relying on the replica, or point those services at the
target database temporarily, until the replicas have time to get up and running
from the new target database.&lt;/p&gt;

</description>
        <pubDate>Sat, 30 Dec 2023 12:00:00 -0600</pubDate>
        <link>http://phizzle.space/dbadmin/aws/postgres/2023/12/30/rds-encryption-migration.html</link>
        <guid isPermaLink="true">http://phizzle.space/dbadmin/aws/postgres/2023/12/30/rds-encryption-migration.html</guid>
        
        
        <category>dbadmin</category>
        
        <category>aws</category>
        
        <category>postgres</category>
        
      </item>
    
      <item>
        <title>Deploying Static Sites on AWS with Terraform</title>
        <description>&lt;p&gt;Recently I’ve had to deploy a couple of client-side web applications to the
web, and my cloud provider of choice is AWS. If you are familiar with the
various tools provided by AWS, setting up a web stack through the console is
straightforward. It may be tempting to depend on the UI, especially for
something that is usually pretty static, but I highly recommend adopting
Infrastructure-as-Code (IaC) principles and using a management tool. You’ll
find that the simplicity in deploying new sites and regions is worth the
upfront time in setting up your deployment, and it’ll be much
easier to manage.&lt;/p&gt;

&lt;p&gt;If you are a web developer or full-stack developer with
little or no devops experience, you’ll find that this is a great way to get
started. In this post, I’ll walkthrough managing your infrastructure with an
open-source IaC tool called &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; but these principles will
apply with any other cloud agnostic tool, or AWS’s IaC tool,
&lt;a href=&quot;https://aws.amazon.com/cloudformation/&quot;&gt;CloudFormation&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#terraform-introduction&quot;&gt;Terraform Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#aws-resources&quot;&gt;AWS Resources&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#prerequisites&quot;&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#writing-the-plan&quot;&gt;Writing the Plan&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#deployments&quot;&gt;Deployments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#final-notes&quot;&gt;Final Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;terraform-introduction&quot;&gt;Terraform Introduction&lt;/h3&gt;

&lt;p&gt;Before I jump into how we’re going to deploy a static site, a brief
introduction to Terraform is required to make sense of the code we’ll write.
Terraform allows for engineers to write declarative code to create, modify and
destroy cloud assets on various cloud platforms such as &lt;a href=&quot;https://cloud.google.com/&quot;&gt;GCP&lt;/a&gt;, AWS and others.
Instead of having to navigate a platform’s CLI or UI, we can write terraform
files that can be version controlled and added to the CI/CD platform of your
choice.&lt;/p&gt;

&lt;p&gt;This makes for more maintainable cloud infrastructure - doing it
without the IaC approach is the software developer’s equivalent of manually
copying files with FTP or rsync to the production server. We are aiming for
reliable and repeatable deployments, and continuously shipping infrastructure
is a part of the modern stack.&lt;/p&gt;

&lt;p&gt;Below is some sample code from their homepage. The syntax is straightforward
and describes a running AWS instance with some attributes defined outside in
another block. Different types of AWS (and other platform) resources and their
definitions and syntax can be found in their &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/aws/latest/docs&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;iac_in_action&quot; {
  ami               = var.ami_id
  instance_type     = var.instance_type
  availability_zone = var.availability_zone

  // dynamically retrieve SSH Key Name
  key_name = aws_key_pair.iac_in_action.key_name

  // dynamically set Security Group ID (firewall)
  vpc_security_group_ids = [aws_security_group.iac_in_action.id]

  tags = {
    Name = &quot;Terraform-managed EC2 Instance for IaC in Action&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you have your infrastructure defined, Terraform gives you two CLI tools to
get your plan deployed. The &lt;code class=&quot;highlighter-rouge&quot;&gt;plan&lt;/code&gt; operation compares your defined
infrastructure versus what’s currently there. In the same way that
&lt;a href=&quot;https://dzone.com/articles/configuration-drift&quot;&gt;configuration drift&lt;/a&gt; occurs in physical servers, it also happens in
your cloud infrastructure. Maybe an engineer makes a change without anyone
knowing, or a resource has new features launched. Either way, &lt;code class=&quot;highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt;
shares an execution plan where you can confirm the upcoming changes are
exactly what you want.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;apply&lt;/code&gt; does exactly what you would expect, it will roll out that listed
execution plan across the resources as defined. I won’t be diving into
integrating these into continuous deployment workflows in this post, but basic
knowledge of the above will let you version control your static site in
preparation for automated deployments in the future. Now, onto the AWS
resources required to host a static site.&lt;/p&gt;

&lt;h3 id=&quot;aws-resources&quot;&gt;AWS Resources&lt;/h3&gt;

&lt;p&gt;Hosting a static website is a common and standard need for any business or
developer, and AWS provides production-grade resources to standup a new site in
minutes, so that developers don’t need to worry about reliability. I’ll
highlight each of the components and explain how each is used in the toolchain.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/static_site_infra.png&quot; alt=&quot;Diagram
of AWS resources required for hosting a static site.&quot; /&gt;
  &lt;figcaption&gt;Diagram
of AWS resources required for hosting a static site.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;1-s3-buckets&quot;&gt;1. S3 Buckets&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html&quot;&gt;S3 buckets&lt;/a&gt; are the most critical resource, as they are responsible for storing
your collection of images, Javascript, and HTML. S3 is AWS’s object storage
offering, and it is essentially a giant key-value store that allows for users
to reliably store objects of any size with a key, namespaced with buckets.
Deploying a new release to your site will involve overwriting existing assets
in this bucket.&lt;/p&gt;

&lt;h4 id=&quot;2-cloudfront-distributions&quot;&gt;2. CloudFront Distributions&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/cloudfront/&quot;&gt;CloudFront distributions&lt;/a&gt; are globally available content delivery networks
(CDNs) that allow for the contents of a single S3 bucket to be distributed with
low latency all over the globe, depending on the configuration. Do you want
your content optimized for access in Asia? Managing that is a simple
configuration change with CloudFront.&lt;/p&gt;

&lt;h4 id=&quot;3-route53-routes&quot;&gt;3. Route53 routes&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/route53/&quot;&gt;Route53&lt;/a&gt; is a DNS web service, that allows for you to programatically
direct network traffic to internal and external assets with your domain name.
We’ll use Route53 to direct traffic to our CloudFront distribution so that our
static site uses our memorable domain name.&lt;/p&gt;

&lt;h4 id=&quot;4-iam-policies&quot;&gt;4. IAM Policies&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/iam/&quot;&gt;IAM&lt;/a&gt; stands for Identity Access Management, and is AWS’s tool for
managing secure platform access within their ecosystem. We will use this
to prevent unauthorized access to our S3 bucket, so that the only way users
access our content is through our CloudFront distribution. This prevents
unauthorized access, and enforces the client requirements we will set in our
CDN. We’ll write an IAM policy in our terraform code below.&lt;/p&gt;

&lt;h4 id=&quot;5-aws-certificate-manager&quot;&gt;5. AWS Certificate Manager&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/certificate-manager/&quot;&gt;ACM&lt;/a&gt; helps us manage our SSL/TLS certificates for secured HTTPS access to
our static site. While not always necessary for certain kinds of content, I’ll
assume your site requires HTTPS, although deploying a site with HTTP only
access is just as easy.&lt;/p&gt;

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

&lt;p&gt;There are two prerequisites assumed below, so you will have to modify the 
configuration plan accordingly, or manually configure these assets. The reason
these are not contained below is that they are really static, and are much more
“set-it and forget-it” than anything below. They can be automated as well, but
I deemed it out of scope for this example.&lt;/p&gt;

&lt;p&gt;First, this tutorial assumes you have an existing Hosted Zone created in
Route53. For each unique domain you have, you’ll need a hosted zone. You don’t
necessarily need to purchase a domain through AWS, but if you manage a domain
through another domain provider like Namecheap, you’ll have to configure their
portal to point to the AWS name servers provided after you have a hosted zone
created. You will also need the hosted zone id once you have it setup.&lt;/p&gt;

&lt;p&gt;Second, I’m assuming you have a valid SSL/TLS certificate created through ACM.
You can create one with a wildcard to your domain, such as
&lt;code class=&quot;highlighter-rouge&quot;&gt;*.customdomain.com&lt;/code&gt;, and this will allow you to use the same certificate in
all future subdomain static sites. Keep track of the &lt;a href=&quot;https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html&quot;&gt;ARN&lt;/a&gt; that comes with
that certificate.&lt;/p&gt;

&lt;h3 id=&quot;writing-the-plan&quot;&gt;Writing the Plan&lt;/h3&gt;

&lt;p&gt;With all of that out of the way, we can get into the details and look at what
such a plan will look like. I’ve pasted the entire plan below as well as
in this &lt;a href=&quot;https://gist.github.com/phouse512/1b9267263e0f8f233fd70d620ba165e0&quot;&gt;this gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The parts that should be overriden by your own config are in the &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;&amp;gt;&lt;/code&gt; brackets,
and the brackets should also be replaced by whatever text or variable is
specified. The region can also be changed, I just defaulted to &lt;code class=&quot;highlighter-rouge&quot;&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;locals&lt;/code&gt; block allows for you to parameterize variables that get referenced
in multiple places later on. These could also be converted to 
&lt;a href=&quot;https://www.terraform.io/docs/language/values/variables.html&quot;&gt;input variables&lt;/a&gt; so that they can be dynamically set as well.&lt;/p&gt;

&lt;p&gt;The amount of CloudFront distribution parameters would take a full blog post to 
cover all of the details. I selected some sane defaults for this
distribution: it requires HTTPS, uses &lt;code class=&quot;highlighter-rouge&quot;&gt;PriceClass_100&lt;/code&gt;, which caches your
content in NA and EU (cheapest option), and uses some standard caching values.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = &quot;hashicorp/aws&quot;
      version = &quot;3.19.0&quot;
    }
  }
}

provider &quot;aws&quot; {
  # region can be overriden, parameterized if desired
  region = &quot;us-east-2&quot;
}

# PARAMETERS, certificate and hosted zone id required
locals {
  s3_origin_id = &quot;myS3Origin&quot;
  certficate_arn = &quot;&amp;lt;certificate_arn_here&amp;gt;&quot;
  dns_zone_id = &quot;&amp;lt;hosted_zone_id&amp;gt;&quot;
}

# s3 bucket configuration
resource &quot;aws_s3_bucket&quot; &quot;bucket&quot; {
  bucket = &quot;&amp;lt;your_bucket_name_here&amp;gt;&quot;
  acl    = &quot;private&quot;

  website {
    # change this if you have something like root.html or home.html configured instead
    index_document = &quot;index.html&quot;
  }

  # feel free to modify tags for your own use, used for cost analytics
  tags = {
    Service = &quot;&amp;lt;service_name&amp;gt;&quot;
    Operation = &quot;app-hosting&quot;
    Environment = &quot;prod&quot;
  }
}

# cloudfront principal identity for s3 access
resource &quot;aws_cloudfront_origin_access_identity&quot; &quot;s3_access_identity&quot; {
  comment = &quot;Cloudfront user for S3 bucket access.&quot;
}

# cloudfront distribution configuration
resource &quot;aws_cloudfront_distribution&quot; &quot;s3_distribution&quot; {
  origin {
    domain_name = aws_s3_bucket.bucket.bucket_regional_domain_name
    origin_id = local.s3_origin_id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.s3_access_identity.cloudfront_access_identity_path
    }
  }

  enabled = true
  is_ipv6_enabled = true
  comment = &quot;Host for Blog&quot;
  default_root_object = &quot;index.html&quot;

  # logging_config {
  #   include_cookies = false
  #   bucket          = &quot;mylogs.s3.amazonaws.com&quot;
  #   prefix          = &quot;myprefix&quot;
  # }

  aliases = [&quot;&amp;lt;domain desired here, ex: blog.customdomain.com&amp;gt;&quot;]

  default_cache_behavior {
    allowed_methods  = [&quot;DELETE&quot;, &quot;GET&quot;, &quot;HEAD&quot;, &quot;OPTIONS&quot;, &quot;PATCH&quot;, &quot;POST&quot;, &quot;PUT&quot;]
    cached_methods   = [&quot;GET&quot;, &quot;HEAD&quot;]
    target_origin_id = local.s3_origin_id

    forwarded_values {
      query_string = false

      cookies {
        forward = &quot;none&quot;
      }
    }

    viewer_protocol_policy = &quot;allow-all&quot;
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = &quot;none&quot;
    }
  }

  price_class = &quot;PriceClass_100&quot;

  viewer_certificate {
    acm_certificate_arn = local.certficate_arn
    ssl_support_method = &quot;sni-only&quot;
  }

  tags = {
    Service = &quot;&amp;lt;your_service_name&amp;gt;&quot;
    Operation = &quot;cdn&quot;
    Environment = &quot;prod&quot;
  }
}

# json policy for cloudfront -&amp;gt; s3 access
data &quot;aws_iam_policy_document&quot; &quot;s3_policy&quot; {
  statement {
    actions = [&quot;s3:GetObject&quot;]
    resources = [
      &quot;${aws_s3_bucket.bucket.arn}/*&quot;
    ]

    principals {
      type = &quot;AWS&quot;
      identifiers = [ aws_cloudfront_origin_access_identity.s3_access_identity.iam_arn ]
    }
  }
}

# iam policy
resource &quot;aws_s3_bucket_policy&quot; &quot;s3_read_access&quot; {
  bucket = aws_s3_bucket.bucket.id
  policy = data.aws_iam_policy_document.s3_policy.json
}

# dns route to cloudfront
resource &quot;aws_route53_record&quot; &quot;app_route&quot; {
  zone_id = local.dns_zone_id
  name = &quot;blog.customdomain.com&quot;
  type = &quot;A&quot;

  alias {
    name = aws_cloudfront_distribution.s3_distribution.domain_name
    zone_id = aws_cloudfront_distribution.s3_distribution.hosted_zone_id
    evaluate_target_health = false
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you have a plan that you are happy with, you can test it out using
&lt;code class=&quot;highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt; to get a full list of what will happen, and deploy it using
&lt;code class=&quot;highlighter-rouge&quot;&gt;terraform apply&lt;/code&gt; if nothing errors out.&lt;/p&gt;

&lt;h3 id=&quot;deployments&quot;&gt;Deployments&lt;/h3&gt;

&lt;p&gt;All that is required to deploy updates to your static site is to sync your
desired build directory to the S3 bucket, and then create an 
&lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html&quot;&gt;Invalidation&lt;/a&gt; in your CF distribution to let it know that the 
cached content needs to be refreshed from the S3 bucket.&lt;/p&gt;

&lt;p&gt;For a quick and easy example of how I do this with this blog, see the following
&lt;a href=&quot;https://github.com/phouse512/blog/blob/master/scripts/deploy.sh&quot;&gt;deployment script&lt;/a&gt; in this very blog that uses the AWS CLI to sync my
build folder, and create the invalidation.&lt;/p&gt;

&lt;p&gt;That script runs automatically with TravisCI so that each merge to my production
branch is deployed without any effort.&lt;/p&gt;

&lt;h3 id=&quot;final-notes&quot;&gt;Final Notes&lt;/h3&gt;

&lt;p&gt;On the topic of pricing: you might be intimidated by all the resources we’ve
created in this post and wondering what kind of AWS bill you will incur at the
end of the month. I can assure you that for the average site, AWS’s usage based
pricing of S3, CloudFront and Route53 will be competitive to almost any
alternative out there for hosting websites with a CDN. If you don’t believe me,
make sure your tagging schema is set correctly, and use the Cost Explorer next
month to see how little it costs. For reference, this site costs less than a 
cup of coffee a month to host.&lt;/p&gt;

&lt;p&gt;With the rise of cloud platforms in the past decade, running infrastructure in
the cloud has never been more accessible. I hope this gives you a peek
into the power of IaC for your side project or business. If you have any
questions, feel free to email or ask below, I’m always happy to help if I can.&lt;/p&gt;

</description>
        <pubDate>Sun, 02 May 2021 13:00:00 -0500</pubDate>
        <link>http://phizzle.space/terraform/devops/aws/2021/05/02/terraform-cloudfront-static-site.html</link>
        <guid isPermaLink="true">http://phizzle.space/terraform/devops/aws/2021/05/02/terraform-cloudfront-static-site.html</guid>
        
        
        <category>terraform</category>
        
        <category>devops</category>
        
        <category>aws</category>
        
      </item>
    
      <item>
        <title>Homelab Log: #002</title>
        <description>&lt;h3 id=&quot;home-security-camera-network&quot;&gt;Home Security Camera Network&lt;/h3&gt;

&lt;p&gt;I recently set up the first service for my homelab, a &lt;a href=&quot;https://www.zoneminder.com/&quot;&gt;ZoneMinder&lt;/a&gt; server
that is recording and saving data from a couple of POE cameras.&lt;/p&gt;

&lt;h4 id=&quot;physical-installation&quot;&gt;Physical Installation&lt;/h4&gt;
&lt;p&gt;For the physical installation perspective, I purchased 500ft of Cat6 outdoor
rated cable to run along the outside of our home inside plastic PVC conduit.
Our home is not new construction, and didn’t have any sort of structured
wiring installation. Given that I don’t want to tear out the drywall in our
home, I decided to run the conduit along an exterior wall, out of view.&lt;/p&gt;

&lt;p&gt;From my office, I drilled 1” holes to the exterior and fit PVC conduit through
those holes to junction boxes similar to &lt;a href=&quot;https://www.amazon.com/Thomas-Betts-E987R-JUNCTION-BOX/dp/B000HEIX6W/&quot;&gt;these&lt;/a&gt;. Given that our
length of exterior wall is quite long (at least 120ft), I put several junction
boxes at key points for future use, so I can expand the network with more
cameras and access points as necessary. I purchased PVC conduit in the standard
10ft lengths, and glued and cut to size pieces as necessary. Instead of going
with 3/4” conduit, I went with 1” for more cable space if required in the
future.&lt;/p&gt;

&lt;p&gt;I only had a maximum of 180 degrees of turns in each stretch, so pulling the
wire wasn’t difficult, and didn’t require any special tools. All cables were
terminated at &lt;a href=&quot;https://www.amazon.com/gp/product/B07FNQWGLH/&quot;&gt;specialty junction boxes&lt;/a&gt; that fit my specific
Armcrest cameras. From there, I used an Ethernet cable tester to verify cables
were all working, and moved onto configuring the network.&lt;/p&gt;

&lt;h4 id=&quot;network-setup&quot;&gt;Network Setup&lt;/h4&gt;

&lt;p&gt;Once the physical installation was done, I moved on to setting up my ZoneMinder
server and switch to handle the new traffic. I’ve included a basic diagram
below that outlines the current state of the network.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/homelab_network_v1.png&quot; alt=&quot;Network
diagram of security cameras.&quot; /&gt;
  &lt;figcaption&gt;Network
diagram of security cameras.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I had an old PC lying around that I reconfigured as an Ubuntu machine and
installed ZoneMinder. To power the switches and handle packet routing, I am
using a Netgear GS516TP Managed Switch. It powers the cameras, and connects my
main router and ZM server so that I can review footage from any device at home.&lt;/p&gt;

&lt;p&gt;I used the managed functionality of my switch to set up a VLAN specifically for
my POE cameras. I set up an ACL to only allow devices in that VLAN to
communicate with the ZM server by using the service’s static IP address.&lt;/p&gt;

&lt;p&gt;In the best case, security cameras often have firmware from the manufacturer that will
‘phone home’ and send basic usage data. In the worst case, a compromised device
on my network could be used to access camera data and expose it to the outside
world. By isolating the traffic this VLAN allows, it narrows my surface area
for attack to just the ZM server. As long as I perform regular security updates
and keep that server up-to-date, I can be confident my camera data won’t be
compromised.&lt;/p&gt;

&lt;h4 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h4&gt;

&lt;p&gt;Given my timeline to finish this project, there were a few things I had to leave
as-is in the interest of getting this V1 of the system up and running.&lt;/p&gt;

&lt;p&gt;I’d like to setup a proper server rack that gives me space to install my
switch and patch panels for more reliable and cleaner cable management. Right
now, the corner of my office is a mess and leaves a lot to be desired.&lt;/p&gt;

&lt;p&gt;Next up is purchasing a real server such as the Dell R710 so I can begin
virtualizing my internal services and not require inefficient hardware for my
experiments. My old PC running ZM is loud, power hungry and lacks the ability
to easily manage and monitor it, something that a hypervisor like ProxMox would
offer.&lt;/p&gt;

&lt;p&gt;Finally, I have Ethernet cables outside my home that could be prone to
lightning strikes or other interference. To prevent something from frying my
expensive internal hardware, I need to install &lt;a href=&quot;https://www.amazon.com/Ethernet-Surge-Protector-Gigabit-1000Mbs/dp/B07GBLFFNK/&quot;&gt;Ethernet surge
protectors&lt;/a&gt; on each incoming line to isolate my homelab.&lt;/p&gt;

</description>
        <pubDate>Sun, 06 Dec 2020 12:00:00 -0600</pubDate>
        <link>http://phizzle.space/homelog/networking/security/2020/12/06/homelab-logs-2.html</link>
        <guid isPermaLink="true">http://phizzle.space/homelog/networking/security/2020/12/06/homelab-logs-2.html</guid>
        
        
        <category>homelog</category>
        
        <category>networking</category>
        
        <category>security</category>
        
      </item>
    
      <item>
        <title>Homelab Log: #001</title>
        <description>&lt;p&gt;Being a homeowner has opened up a world of projects both digital and physical,
especially with a dedicated garage. This post will be the first in a long
series about the various projects I take on, both as a personal record and as a way
to share the interesting things learned along the way. I’m titling the series
the Homelab Logs, as homage to the &lt;a href=&quot;https://www.reddit.com/r/homelab/&quot;&gt;homelabbers&lt;/a&gt;. Although that
community is centered around sysadmins and DIY technologies, I hope to emulate 
that spirit of experimentation and learning in my approach to improving and
upgrading our home.&lt;/p&gt;

&lt;p&gt;These logs will not be as long or include as much writing as my usual posts,
but will be a combination of photos and descriptions of the process. You can
also tell from my numbering schema that I plan on writing many of these, and
checking-in my work &lt;a href=&quot;https://blog.codinghorror.com/check-in-early-check-in-often/&quot;&gt;early and often&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;office-cabinets&quot;&gt;Office Cabinets&lt;/h3&gt;

&lt;p&gt;With the intro out of the way, here is a brief overview of one of my biggest
woodworking projects to date: an office cabinet for storage and organization.
It was based on standard cabinet dimensions, and I built 3 drawers on the
cabinet on the left, and 2 full length drawer slides on the right for easy
access.&lt;/p&gt;

&lt;p&gt;I’ve never built cabinets from scratch, and it was a great learning experience
for my next time around. The cabinets were built with baltic birch plywood, and 
the drawer faces and doors were all cut from a single piece of maple
plywood, for a continuous edge between them.&lt;/p&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/cabinet_face_install.jpeg&quot; alt=&quot;Installation of front doors on the right cabinet.&quot; /&gt;
  &lt;figcaption&gt;Installation of front doors on the right cabinet.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/cabinet_top.jpeg&quot; alt=&quot;Finishing the cabinet top and one of the doors.&quot; /&gt;
  &lt;figcaption&gt;Finishing the cabinet top and one of the doors.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/cabinet_bottom_install.jpeg&quot; alt=&quot;Installation of cabinets before the top went on. I used leveling
feet to get bot of these perfectly even and level.&quot; /&gt;
  &lt;figcaption&gt;Installation of cabinets before the top went on. I used leveling
feet to get bot of these perfectly even and level.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;image&quot;&gt;
  &lt;img src=&quot;/assets/cabinet_finished.jpeg&quot; alt=&quot;The finished cabinet, you can see the reveal on the left swinging
door had some alignment and warping issues.&quot; /&gt;
  &lt;figcaption&gt;The finished cabinet, you can see the reveal on the left swinging
door had some alignment and warping issues.&lt;/figcaption&gt;
&lt;/figure&gt;

</description>
        <pubDate>Fri, 16 Oct 2020 13:00:00 -0500</pubDate>
        <link>http://phizzle.space/homelog/woodworking/2020/10/16/homelab-logs-1.html</link>
        <guid isPermaLink="true">http://phizzle.space/homelog/woodworking/2020/10/16/homelab-logs-1.html</guid>
        
        
        <category>homelog</category>
        
        <category>woodworking</category>
        
      </item>
    
      <item>
        <title>Recovering Data from a Failed NTFS Drive</title>
        <description>&lt;p&gt;A family member recently came to me with a portable hard drive that Windows could not read. 
After confirming I was seeing the same issue on my
own PC, I went down the rabbit hole of attempting to recover as much
data as possible from the failed drive. I documented the path I took below, as
well as some notes if you ever find yourself in the same position.&lt;/p&gt;

&lt;h3 id=&quot;cloning-the-damaged-drive&quot;&gt;cloning the damaged drive&lt;/h3&gt;

&lt;p&gt;If you have a drive that can’t be read anymore, chances are that only a portion
of it is dead, and many (if not most) sectors might still be readable. The more
time passes between initial failure and imaging the drive, the higher the
chance the drive might continue to fail as it powers on/off.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.gnu.org/software/ddrescue/ddrescue.html&quot;&gt;GNU ddrescue&lt;/a&gt; is a great recovery tool that copies from a block
device to another. I won’t go into details about the documentation, since you
can read that yourself, but it does its job very well. Below are some important
notes about how to install and use it.&lt;/p&gt;

&lt;p&gt;Be sure to install it by using &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo apt-get install gddrescue&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ddrescue&lt;/code&gt; is
an older, incomplete script.&lt;/p&gt;

&lt;p&gt;ddrescue requires that the input file be visible when you run &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo lsblk&lt;/code&gt;, so
if the device doesn’t even register there, unfortunately this won’t help you.&lt;/p&gt;

&lt;p&gt;You will need an output disk that can contain the entire failed disk, not just
the amount you used, so if you have a 1 TB hard drive you are attempting to
recover, I recommend using at least a 2 TB hard drive to store the output.
ddrescue copies block for block, and doesn’t know anything about the contents.&lt;/p&gt;

&lt;p&gt;ddrescue can be run with a mapfile that can allow the process to be picked up
at any time, so you can take a break or shutdown your computer if desired. I’ve
read that some people don’t run it too long in one session to prevent the
damaged disk from getting too hot, but that seems anecdotal.&lt;/p&gt;

&lt;p&gt;The actual command I ended up running was &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo ddrescue -r
2 /dev/damaged_drive /media/large_backup_drive/image
/media/large_backup_drive/logfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final output file is called &lt;code class=&quot;highlighter-rouge&quot;&gt;image&lt;/code&gt; and the mapfile is called &lt;code class=&quot;highlighter-rouge&quot;&gt;logfile&lt;/code&gt; on
the &lt;code class=&quot;highlighter-rouge&quot;&gt;large_backup_drive&lt;/code&gt; in the above example. It also will attempt to retry
bad sectors two additional times.&lt;/p&gt;

&lt;p&gt;Lastly, be aware that this is a very time consuming process, especially when
you start retrying sectors. For my 1 TB hard drive, in total ddrescue ran for
over 70 hours to fully process and retry the failed sectors. In my case,
ddrescue was able to recover all but 60 MB of the original drive.&lt;/p&gt;

&lt;h3 id=&quot;fixing-the-drive-structure&quot;&gt;fixing the drive structure&lt;/h3&gt;

&lt;p&gt;Once you have imaged the drive, you can attempt some of the various tools to
fix the structure of your NTFS partition, like &lt;code class=&quot;highlighter-rouge&quot;&gt;fsck&lt;/code&gt;. Some people have had
success with other tools as well, but none of these worked for me. It’s worth
a shot, and here is a link to a discussion about some of the options you have.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://askubuntu.com/questions/47700/fix-corrupt-ntfs-partition-without-windows&quot;&gt;SuperUser - Fix corrupt NTFS partition without Windows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, it was unable to find the superblock, and couldn’t fix itself. I still recommend it, 
as the upside is huge, and it’s a simple check once you have created a backup.&lt;/p&gt;

&lt;h3 id=&quot;recovering-data&quot;&gt;recovering data&lt;/h3&gt;

&lt;p&gt;Once I had a full copy of the original drive, I was free to test out the
various open source data recovery tools out there. Originally I used
&lt;a href=&quot;http://foremost.sourceforge.net/&quot;&gt;Foremost&lt;/a&gt;, but I found it to not be helpful in terms of
communicating how far along it was, and ease of use in pausing and restarting.&lt;/p&gt;

&lt;p&gt;Ultimately I ended up using &lt;a href=&quot;https://en.wikipedia.org/wiki/PhotoRec&quot;&gt;Photorec&lt;/a&gt;, and followed it’s super
simple usage directions. Despite its name, it is more than just a tool for
recovering image files, it looks for all sorts of documents, text files, etc.
Make sure you already have a new directory created ahead of time, as you
won’t have the option to create one inside photorec.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo photorec /media/hardrive/backup_image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Photorec has a detailed terminal UI that will guide you through selecting the
image file (if you ran it without specifying it), selecting the output
directory, and start the long process of searching. It will report the
progress percentage and the number of each file type found. After running for
many hours, I was able to recover over 10k files and copy them to a new hard
drive.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;conclusion&lt;/h3&gt;

&lt;p&gt;At this point, you are free to test any data recovery tools on your backup
images, just make sure that you aren’t running them on the damaged disk. The
only thing left desired after going through this is related to lost metadata
after recovering files. After using photorec to find hundreds of gigabytes of
data, many files were missing filenames and of course, any directory
information was gone as well.&lt;/p&gt;

&lt;p&gt;Given that some file types have more complicated metadata embedded inside,
a tool that parsed metadata when filenames were lost would be a great addition
to photorec. I could imagine a simple rules engine that would look for
filenames, authors, timestamps, locations and add them in priority order for
some context for the drive owner.&lt;/p&gt;

&lt;p&gt;All in all, losing only 60 MB out of a damaged 1 TB hard drive is more than I can
ask for, so I can’t complain. Lastly, I will always plug using some sort of backup
tool like &lt;a href=&quot;https://restic.net/&quot;&gt;Restic&lt;/a&gt; or &lt;a href=&quot;https://www.backblaze.com/&quot;&gt;Backblaze&lt;/a&gt;. An ounce of prevention is
worth a pound of cure, and backing up this drive regularly would’ve saved hours
of work and headache.&lt;/p&gt;

</description>
        <pubDate>Sun, 06 Sep 2020 13:00:00 -0500</pubDate>
        <link>http://phizzle.space/linux/ubuntu/data/2020/09/06/recover-data-failed-ntfs-drive.html</link>
        <guid isPermaLink="true">http://phizzle.space/linux/ubuntu/data/2020/09/06/recover-data-failed-ntfs-drive.html</guid>
        
        
        <category>linux</category>
        
        <category>ubuntu</category>
        
        <category>data</category>
        
      </item>
    
      <item>
        <title>Configuring the Raspberry Pi Zero W</title>
        <description>&lt;p&gt;I recently was working on getting the Raspberry Pi Zero W set up, a tiny
computer with a Wi-Fi chipset built-in. I run these headless, so I need to
manually configure these to be available via SSH right out of the box. This
will be a short overview of what I did, as it took way too long and I plan on
setting up many of these over the coming years.&lt;/p&gt;

&lt;h3 id=&quot;setup&quot;&gt;setup&lt;/h3&gt;

&lt;h4 id=&quot;flashing-raspbian-lite&quot;&gt;flashing Raspbian lite&lt;/h4&gt;

&lt;p&gt;The first step is to download the Raspbian OS Lite image from the &lt;a href=&quot;https://www.raspberrypi.org/downloads/raspberry-pi-os/&quot;&gt;Raspbian
website&lt;/a&gt;. Unzip that and keep the &lt;code class=&quot;highlighter-rouge&quot;&gt;.img&lt;/code&gt; file accessible. You can use
&lt;code class=&quot;highlighter-rouge&quot;&gt;dd&lt;/code&gt; or a tool like &lt;a href=&quot;https://www.balena.io/etcher/&quot;&gt;Balena Etcher&lt;/a&gt; for OSX systems to flash the image
to your formatted SD card.&lt;/p&gt;

&lt;h4 id=&quot;boot-up-the-pi&quot;&gt;boot up the pi&lt;/h4&gt;

&lt;p&gt;Boot up the Pi using the PWR micro USB port, and give it a few minutes to
initialize for the first time.&lt;/p&gt;

&lt;h4 id=&quot;configuring-the-os&quot;&gt;configuring the OS&lt;/h4&gt;

&lt;p&gt;After the pi boots up, power it off and stick the SD card into your computer
once more. Once the SD card is mounted, we’ll make a few modifications to add
WiFi credentials and enable SSH access.&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;https://github.com/phouse512/circlefiles&quot;&gt;circlefiles&lt;/a&gt; repository, I have a python script that uses the
python &lt;a href=&quot;http://www.pyinvoke.org/&quot;&gt;invoke&lt;/a&gt; library to make it easy to run tasks against your
machine.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# run rasp pi zero setup to configure Wi-Fi and enable ssh
$ inv rasp-pi-zero-setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once those steps are complete, the SD card can be ejected and added back to the
Pi. The Pi Zero is now ready to boot and should be able to connect to your Wi-Fi
without issue. Give it a few minutes to fully boot up, and then you can find
the IP on your router device list and connect.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# default user is pi and password is raspberry
$ ssh pi@&amp;lt;fake_ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;looking-ahead&quot;&gt;looking ahead&lt;/h4&gt;

&lt;p&gt;These are some simple instructions that outline configuring a Pi Zero with
little to no extras added. I plan on adding Ansible playbooks to setup docker
and various other utilities sometime in the near future.&lt;/p&gt;

</description>
        <pubDate>Tue, 07 Jul 2020 13:00:00 -0500</pubDate>
        <link>http://phizzle.space/devops/pi/2020/07/07/raspberry-pi-zero-w-configuration.html</link>
        <guid isPermaLink="true">http://phizzle.space/devops/pi/2020/07/07/raspberry-pi-zero-w-configuration.html</guid>
        
        
        <category>devops</category>
        
        <category>pi</category>
        
      </item>
    
      <item>
        <title>Logging iTerm2 Activity</title>
        <description>&lt;p&gt;I do most of my software development on OSX, and my terminal of choice is
&lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2&lt;/a&gt;. iTerm2 is a full-featured terminal emulator built for Mac that
allows you to do some incredible customization. The recent release of 3.3 added
a new level of customization, a Python API with which you can customize almost
any aspect of your terminal.&lt;/p&gt;

&lt;p&gt;I have wanted to do keylogging / tracking on my terminal to get a better idea
of what aliases would be the most impactful, see patterns in my usage and so
on. Up until now, I haven’t been able to find a useful open-source keylogger
for my environment. With the recent API release, iTerm2 has exposed all of its
internals, including the ability to hook into events on the terminal.&lt;/p&gt;

&lt;p&gt;With the recent change, I decided to bite the bullet and build a small daemon
to begin capturing my usage of iTerm on my development machine.&lt;/p&gt;

&lt;h2 id=&quot;api-introduction&quot;&gt;API Introduction&lt;/h2&gt;

&lt;p&gt;Before I get started, I highly recommend walking through the iTerm2
documentation and tutorials on getting started with the new API. &lt;a href=&quot;https://twitter.com/gnachman&quot;&gt;George
Nachman&lt;/a&gt; and the rest of the iTerm team did a fantastic job documenting
and helping new users get their first script running. The examples listed are
also helpful, and in particular the &lt;a href=&quot;https://iterm2.com/python-api/examples/autoalert.html&quot;&gt;Alert on Long-Running Jobs&lt;/a&gt;
script was helpful in demonstrating session monitoring capabilities.&lt;/p&gt;

&lt;p&gt;I recommend downloading one, slightly modifying it and place it into your
iTerm2 directory to begin testing. Once you place scripts inside your
&lt;code class=&quot;highlighter-rouge&quot;&gt;~/Library/Application Support/iTerm2/Scripts&lt;/code&gt; directory, you’ll see iTerm load
it up in the Scripts menu option. Again, there is clear
&lt;a href=&quot;https://iterm2.com/python-api/tutorial/running.html&quot;&gt;documentation&lt;/a&gt; for this portion, so I’ll point you there for
clear directions.&lt;/p&gt;

&lt;p&gt;Finally, you can take advantage of the Scripts console to monitor currently
running scripts, see exception logging and more. You can easily start new
scripts, restart existing ones and manage what is going on behind the scenes.
See &lt;a href=&quot;https://iterm2.com/python-api/tutorial/troubleshooting.html&quot;&gt;Troubleshooting&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2 id=&quot;logging-sessions&quot;&gt;Logging Sessions&lt;/h2&gt;

&lt;p&gt;Compared to what you can do, my logging script is relatively simple. I had
a few goals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;log sessions by name upon creation&lt;/li&gt;
  &lt;li&gt;log commands by session&lt;/li&gt;
  &lt;li&gt;log command exit status by session&lt;/li&gt;
  &lt;li&gt;log command duration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My script uses &lt;code class=&quot;highlighter-rouge&quot;&gt;PromptMonitor&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;EachSessionOnceMonitor&lt;/code&gt; to log sessions
opening up, and run a function that waits for any command input.&lt;/p&gt;

&lt;div class=&quot;language-python 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;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    This long running iTerm2 daemon logs commands, status's sessions, etc.
    :param connection: iTerm2 connection obj
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_get_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;monitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
        Monitor a session for commands, log them out.
        :param session_id: str
        &quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_session_by_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;new session: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_id&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;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;No session with id: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_id&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;n&quot;&gt;modes&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;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PROMPT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND_END&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;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;# blocks until a status changes, new prompt, command starts, command finishes
&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_get&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;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND_START&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;session-%s-command: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromptMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;COMMAND_END&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;session-%s-status: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterm2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EachSessionOnceMonitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_foreach_session_create_task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;monitor&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;With &lt;code class=&quot;highlighter-rouge&quot;&gt;PrompMonitor&lt;/code&gt;, you can listen to certain modes, so I connected to
commands start and ending, and logged those out along with the session id.
I use the standard python logging interface, and set up a rotating file handler
to turn over files once a day. If you’d like to see the full script, feel free
to check it out on &lt;a href=&quot;https://github.com/phouse512/piper_compute/blob/master/images/scripts/iterm2_logger.py&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running it 24/7 is as easy as putting it into the &lt;code class=&quot;highlighter-rouge&quot;&gt;Scripts/AutoLaunch&lt;/code&gt;
directory and letting iTerm2 take care of the rest.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;I plan on running this logger on my machine for the next several months before
beginning to look at the data. Some potential questions I have, for fun and for
utility:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;what are the most common tools I use? which ones do I use for work? personal?&lt;/li&gt;
  &lt;li&gt;which one of my current bash aliases are the most used? (or save the most
  keystrokes?) what are some commands that would benefit from being aliased?&lt;/li&gt;
  &lt;li&gt;what commands keep me waiting the longest? If I find myself waiting hours
  a month for test suites to finish, maybe I should make it easy to run
  smaller sets.&lt;/li&gt;
  &lt;li&gt;what series of commands should I combine into a single tool or uitility?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some last thoughts on how to improve this and further customize iTerm2:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;there are some transient errors around sessions closing or being interrupted,
  and I don’t know enough about the Python API to solve them yet&lt;/li&gt;
  &lt;li&gt;for each software project I work on, it’d be nice to issue a single command
  that opens all the sessions required for builds, checkout branches, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With such an open API, the options are limitless..&lt;/p&gt;

</description>
        <pubDate>Mon, 04 Nov 2019 12:00:00 -0600</pubDate>
        <link>http://phizzle.space/analytics/linux/2019/11/04/19-iterm2-session-logging.html</link>
        <guid isPermaLink="true">http://phizzle.space/analytics/linux/2019/11/04/19-iterm2-session-logging.html</guid>
        
        
        <category>analytics</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Building a Desktop Linux PC</title>
        <description>&lt;p&gt;For the past 7 years, I’ve almost exclusively lived off 3 different laptops
that I’ve owned. Between the three of them, I’ve used one Windows 7 machine,
and two OSX laptops. The OSX laptops have been the most reliable and common
ones that I’ve used for development the past 5 years. The Windows laptop died
a few years into usage and I haven’t touched it since. In the meantime, I’ve
wanted to work on getting a home network setup with NAS and various other
utilities. To set all that up, a Linux home base has been a prerequisite for
a while. Over the holidays, I found some good deals on some parts I’ve been
waiting for, so I decided to spring on it.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#goals&quot;&gt;goals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-build&quot;&gt;the build&lt;/a&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#parts-list&quot;&gt;parts list&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#order-of-operations&quot;&gt;order of operations&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#linux-install&quot;&gt;linux install&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#lessons-learned&quot;&gt;lessons learned&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#configuration-management&quot;&gt;configuration management&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#stability-and-burn-in&quot;&gt;stability and burn-in&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#backup-and-storage&quot;&gt;backup and storage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#last-thoughts&quot;&gt;last thoughts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;goals&quot;&gt;goals&lt;/h2&gt;

&lt;p&gt;This desktop build is meant to satisfy a relatively narrow set of use-cases. My
parts list and setup will optimize for the uses listed below.&lt;/p&gt;

&lt;p&gt;What it’s meant to do:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Run 24/7 with very high reliability&lt;/li&gt;
  &lt;li&gt;Handle multiple storage drives for dual-boot, potential NAS storage&lt;/li&gt;
  &lt;li&gt;Transcribe signals from analog video to digital formats&lt;/li&gt;
  &lt;li&gt;Be accessible from trusted devices in the local network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it’s not meant to do:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Play games that require lots of computing power&lt;/li&gt;
  &lt;li&gt;Mine cryptocurrencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-build&quot;&gt;the build&lt;/h2&gt;

&lt;p&gt;Building the PC in total took me about 2 hours for the physical setup and
testing, and another hour to get Linux set up as I wanted. At the time of
writing, I have used these components only for a short time, so I cannot give
a proper review. I plan on following up 6- 12 months from now with a review
based on what I’ve experienced.&lt;/p&gt;

&lt;h3 id=&quot;parts-list&quot;&gt;parts list&lt;/h3&gt;

&lt;p&gt;One of my unstated goals was to finish the build for under $500, and my parts
below at the time of writing cost in total about $485. You might be able to
find these cheaper depending on how prices change over time. The links below
are also not sponsored in any way, so please just use them as a reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/AMD-Ryzen-Processor-Radeon-Graphics/dp/B079D3DBNM/&quot;&gt;AMD Ryzen 3 2200G&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;includes on-board graphics&lt;/li&gt;
  &lt;li&gt;cheap workhorse CPU&lt;/li&gt;
  &lt;li&gt;if using an external PCI GPU, only uses PCI x8 mode even if your GPU supports
  x16. I’ve heard this is not a big deal, but just in case you care about
  maximizing your performance, I would do your research.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Motherboard&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/MSI-Crossfire-Motherboard-B450-Tomahawk/dp/B07F7W5KJS/&quot;&gt;MSI B450 Tomahawk ATX AM4&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I sprung for the B450 over the B350 because of the support for more SATA III
  connections. Other than a few minor USB configuration differences, they are
  mostly the same. If you have a really old CPU, there might be some
  compatibility issues that are worth upgrading to the B450. At the time,
  there was a $10 difference, so I decided to upgrade.&lt;/li&gt;
  &lt;li&gt;Includes a USB Type-C connector which was a non-negotiable for me.&lt;/li&gt;
  &lt;li&gt;From an aesthetics perspective, the board looks really good and the color
  scheme fits in well with my case. It also has a few RGB LED headers you can
  use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ram&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/gp/product/B01ARHBBPS/&quot;&gt;Corsair 1x8GB DDR4 2400MHz&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I plan on adding another 1x8 GB stick in the future, but this suffices for
  now.&lt;/li&gt;
  &lt;li&gt;Upon researching the different RAM speeds, it appears to make small
  performance improvements at 2800MHz + but I opted to stay away for now.&lt;/li&gt;
  &lt;li&gt;There are cheaper 1x8GB sticks out there from other brands, but I decided to
  go with Corsair’s tried and tested model for my own sanity’s sake and pay
  the extra $15.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;HDD&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/Samsung-250GB-Internal-MZ-76E250B-AM/dp/B07864WMK8/&quot;&gt;Samsung 860 Evo 250GB 2.5” SATA III&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I wanted a smaller drive to run my Linux system, and for the NAS storage
  options I talked about in the future, I plan on getting WD 2TB red drives.&lt;/li&gt;
  &lt;li&gt;This is the first SSD I’ve installed myself, and they are incredibly light,
  thin and cheap. SSD technology has come a long way since prices 10 years
  ago.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PSU&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/EVGA-Supernova-Modular-Warranty-220-G3-0750-X1/dp/B005BE058W/&quot;&gt;EVGA SuperNova G3 750W&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;This is a fully modular power supply, if you care about cable management,
  it’s worth the cost.&lt;/li&gt;
  &lt;li&gt;EVGA has great customer service and a 10 year warranty, make sure to register
  your product.&lt;/li&gt;
  &lt;li&gt;It includes an ECO mode that helps with silencing the PSU fans and only running
  as necessary, but not a big selling point for me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Case&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/Cooler-Master-Computer-Radiator-RC-902XB-KKN2/dp/B00FFJ0H3Q/&quot;&gt;Cooler Master HAF XB EVO ATX Desktop&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I didn’t want a tower build, but a more boxy build like the EVO.&lt;/li&gt;
  &lt;li&gt;It is incredibly spacious, and makes it easy to route cables with zip tie
  loops throughout the case.&lt;/li&gt;
  &lt;li&gt;Easy access to the motherboard makes it simple to service or modify if you
  keep it on your desk. The top and both side panels are removable to allow
  all encompassing access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;WiFi PCIe Adapter&lt;/strong&gt;: &lt;a href=&quot;https://www.amazon.com/gp/product/B00HF8K0O6&quot;&gt;Gigabyte GC-WB867D-I&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;order-of-operations&quot;&gt;order of operations&lt;/h3&gt;

&lt;p&gt;When looking up how to build your own PC, you’ll find pretty similar high level
instructions for component order and what to do. There are always smaller
details like test booting and other miscellaneous items that seem to fall
through the cracks, so I thought I’d record what I did for next time. I am by
no means a build expert, so I defer to professional opinions if you find
conflicting information. This is simply a record of my particular build.&lt;/p&gt;

&lt;p&gt;As most guides out there suggest, fully read through the list twice, before
removing a single item from packaging. If this is your first build, cross-check
it with other online resources if your parts are significantly different from mine.
Manuals are also your friend, I highly recommend reading each manual for your
motherboard, case, GPU and PSU with the priority being in that order. Every
build is different, and this is one case where it pays to read the manual
before starting to jam parts together.&lt;/p&gt;

&lt;p&gt;One final note, I did not test-boot my motherboard while it was outside of the
case before the power and reset switches from the case were connected. I am not
experienced enough to understand how to manually power off the motherboard so
I opted not to do this as many experts recommend. My method is more
time-consuming if you misconfigured your motherboard and have to debug it once
it’s in the case, but for me it was the safer option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt; - &lt;em&gt;these steps do not include GPU installation, so please read your
manual if you are installing a GPU.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Install CPU onto the motherboard. If this is your first time, please watch
some YouTube videos for your specific processor installation to make sure
you don’t damage your CPU. The CPU is delicate, and there is room for error
here if you aren’t careful. If you are using the same CPU as I am,
I recommend this particular &lt;a href=&quot;https://www.youtube.com/watch?v=9VtH0EJRyAc&quot;&gt;install video&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Mount CPU fan to the motherboard. My build uses the stock cooling fan for
the Ryzen 3, and already includes thermal paste. I again recommend watching
YouTube videos for your specific cooler for mounting instructions and using
thermal paste. Don’t forget to attach the CPU fan power cable to the
motherboard as specified in the manual.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install RAM on the motherboard. Make sure you carefully read your
motherboard manual to understand the mounting location. For example, I was
only installing 1 stick of RAM, and its spot was in the 2nd from the left.
Not the most intuitive, so always read the manual.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install PSU to the case. At this point before the motherboard is installed,
I recommend making sure that you attach all the power cables you need so
that you don’t have to dig under the motherboard later once it has already
been mounted and there is limited space. In my case, I needed the MB cable,
the CPU cable, one SATA power cable and one peripheral power cable for the
front fans.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install the standoffs to the motherboard mount. Mount the motherboard to the
standoffs for a standard ATX motherboard. Refer to the case manual on
specific instructions on how to do this. The &lt;a href=&quot;https://www.amazon.com/Cooler-Master-Computer-Radiator-RC-902XB-KKN2/dp/B00FFJ0H3Q/&quot;&gt;HAF XB EVO&lt;/a&gt; has
a removable mount that makes it easy to do this without having to fit your
motherboard in the case yet.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Place the IO shield for your motherboard inside the case in the standard
back slot. Make sure that it’s oriented correctly before pressing it in.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install the motherboard mount to the case, but only screw in a couple screws
so that it’s secure, but not too hard to remove later if necessary.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Read the motherboard and case manual to figure out how to attach the power
and reset switches to the mother board. Optionally attach the Power and HDD
LED’s if you are confident.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Plug in the power cable, flip on the PSU switch and try turning it on. Your
CPU fan should come on and some LEDs on the board will light up. The B450
board I used includes some easy debug LEDs that can tell you if you are
having CPU, RAM, VGA or boot issues. Once you see the BIOS come up, you made
it!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you successfully test boot, power off the system, turn off the PSU and
fully unplug the PSU before starting to work on it again.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I mounted the SSD in the back HD mount of the case. The case I was using
has 2 hot swappable drive mounts in the front, but I didn’t want the
primary boot drive to be easily removed. Carefully read your case manual
about how to mount a 2.5” drive, usually there are adapters that make HD
mountings compatible with both 2.5” and 3.5” drives.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Connect the SATA connectors to SATA1 on the motherboard, and connect the
PSU SATA cable to your drive.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Connect the front USB 3 cables and audio cables to the motherboard. Again,
this is always specific to each motherboard and case, so I highly recommend
reading both manuals to ensure proper connections.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Connect any external fan power cables to your motherboard or PSU. Manuals
come in handy as always!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Once I was able to boot and test that the system was stable, I then
installed my PCIe adapter for wireless networking.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, your computer is all set for you to begin booting up your
particular OS boot drive if you have that ready. I recommend waiting to zip tie
and organize cables until the very end of the process, but that’s up to you. If
you didn’t fully mount the motherboard or PSU with all 4 screws, I highly
recommend you do that now.&lt;/p&gt;

&lt;h3 id=&quot;linux-install&quot;&gt;Linux Install&lt;/h3&gt;

&lt;p&gt;I have the most experience with Ubuntu so I decided to go with the Ubuntu
18.04.1 LTS release. There are many guides that go over setting up bootable
drives so I won’t rehash those here. I was able to boot from my USB
drive easily and go through the standard Ubuntu install without much effort.&lt;/p&gt;

&lt;p&gt;Things only got tricky once I started rebooting my computer for the first time.
I would get a purple splash screen… and then nothing. Upon further
investigation, it looked to be an issue with support for the AMD Ryzen
3 processor with on-board graphics. AMD has not released any official driver
support for Linux, and I have not been able to find well-supported community
drivers either. The only thing I’ve found searching across different forums and
threads is the hint that the newer Linux kernels (4.17+) have better support
for AMD GPUs.&lt;/p&gt;

&lt;p&gt;I decided to use a utility called &lt;a href=&quot;https://github.com/teejee2008/ukuu&quot;&gt;UKUU&lt;/a&gt; to help manage the kernel
upgrade. Once installing kernel 4.20, I rebooted and have not had any issues
with the AMD on-board graphics GPU getting recognized. To get to the point where
I could even install UKUU and utilize the system, I had to manage the GRUB boot
settings to manually force it to not attempt to use a GPU.&lt;/p&gt;

&lt;p&gt;In the GRUB loader, selected Advanced options for Ubuntu, then click &lt;code class=&quot;highlighter-rouge&quot;&gt;e&lt;/code&gt; on the
default loading configuration to edit it. You should see a line that has
something looks like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ro quiet

# change it to:
ro nomodeset quiet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can use &lt;code class=&quot;highlighter-rouge&quot;&gt;CTRL + X&lt;/code&gt; to be able to boot with your modified entry. The system
should boot fine, but you’ll notice that the resolution will be poor, but it’s
enough to get what we need done. At this point, you can install UKUU and then
load the 4.20 Linux kernel, reboot and you should be able to properly use your
new system.&lt;/p&gt;

&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h3&gt;

&lt;p&gt;As this was my first build in quite a while, I made some mistakes and learned
some things that I either forgot or hadn’t experienced before.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Don’t forget to put your IO shield in early, I forgot to do this and had to
  move the motherboard late in the install after all the cables were plugged
  in.&lt;/li&gt;
  &lt;li&gt;Connect all the PSU cables you need if you have a fully modular PSU. You can
  route them out of the side of the case temporarily to preserve space, but
  it’s much easier to do it earlier than later once your motherboard is in.&lt;/li&gt;
  &lt;li&gt;Have a USB keyboard and mouse available. I have been using Bluetooth
  keyboards for so long that I had to find an old one in storage.&lt;/li&gt;
  &lt;li&gt;Research driver support early, before you buy components. In my case, it
  worked out that the later Linux kernel supported my integrated graphics
  card. In hindsight, I should’ve known there might’ve been potential
  compatibility issues before I bought the parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;configuration-management&quot;&gt;configuration management&lt;/h2&gt;

&lt;p&gt;In the spirit of making my builds repeatable and to prevent headaches when
building future machines, I wanted to put as much configuration as possible
into version control of some sort. I have the most experience with
&lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt;, so I decided to create an Ansible playbook for the machine.&lt;/p&gt;

&lt;p&gt;I’ll highlight the process in more detail in another post one day, but for now
the only dependency I had to install before running my playbook was ansible
itself. The initial steps are highlighted below:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once ansible was installed, I cloned my repo that contains my configs,
&lt;a href=&quot;https://github.com/phouse512/circlefiles&quot;&gt;circlefiles&lt;/a&gt;. I then run my ansible playbook to ensure that the
machine state is up-to-date.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ansible-playbook piper_home.yml --ask-sudo-pass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Using those ansible playbooks, you can then add whatever modules you need or
want installed. I also use my playbooks to copy my vim config dotfiles, bashrc
files and more. If you want to see the latest one that I use on this machine,
you’ll see it on the master branch on my circlefiles repo. The goal is to not
just run commands when configuring and setting up packages, but to put it all
in version control, so it’s easy to set up new systems if the need arrives. Two
years later when you’re trying to remember what you did, you’ll appreciate the
detail.&lt;/p&gt;

&lt;h2 id=&quot;stability-and-burn-in&quot;&gt;stability and burn-in&lt;/h2&gt;

&lt;p&gt;Now that my system was set up, I wanted to test for reliability and make sure
that there were no obvious stability issues. From my research, I came up with
two different tests for the CPU and memory. &lt;a href=&quot;https://www.mersenne.org/download/&quot;&gt;prime95&lt;/a&gt; is a common tool
that can be used to push a CPU to its limits. &lt;a href=&quot;https://www.memtest.org/&quot;&gt;memtest86&lt;/a&gt; can also
be used to thoroughly test your RAM to make sure that there are no damaged
areas.&lt;/p&gt;

&lt;h3 id=&quot;memtest86&quot;&gt;memtest86&lt;/h3&gt;

&lt;p&gt;Memtest86 is a utility that can be run from inside your existing OS or from
a bootable USB drive. I opted to do the latter, as it allows for your RAM to be
fully tested. From my understanding, when memtest is run from a running OS, it
can’t access all the RAM addresses possible. When run as a bootable drive, it
can fully test all of your RAM for any errors.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://phizzle.space/assets/memtest_benchmark.png&quot; alt=&quot;memtest benchmark&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see the results from my memtest run above. By default, it goes for four
full passes and runs each test four times. For my 8GB stick, it took a little
under 2.5 hours to complete.&lt;/p&gt;

&lt;h3 id=&quot;prime95&quot;&gt;prime95&lt;/h3&gt;

&lt;p&gt;prime95 is a software that calculates prime numbers, but has evolved into
a program used by system builders to test stability of their systems. prime95
is computationally intense, and can push your entire system’s limits. MPrime is
the specific Linux version that we will use. I based my test off of Jeff
Atwood’s &lt;a href=&quot;https://blog.codinghorror.com/is-your-computer-stable/&quot;&gt;blog post on system reliability&lt;/a&gt;, so if you are curious
about the details, I would read that first. The only difference is that I use
another &lt;a href=&quot;https://wiki.archlinux.org/index.php/lm_sensors&quot;&gt;tool&lt;/a&gt; to help monitor my CPU temps, as I’m running an AMD
processor and not an Intel series CPU. If your computer can run it overnight
without mprime crashing, you should be all set.&lt;/p&gt;

&lt;h2 id=&quot;backup-and-storage&quot;&gt;backup and storage&lt;/h2&gt;

&lt;p&gt;The final step that I wanted to setup at the beginning was automating periodic
backups of my user directories. There are dozens of backup tools out there, but
I am most comfortable with a tool called &lt;a href=&quot;https://github.com/restic/restic&quot;&gt;restic&lt;/a&gt;. To start, I just
want to backup my home directory once a week to a remote S3 bucket that I’ve
already configured.&lt;/p&gt;

&lt;p&gt;I’ve setup a cronjob that runs weekly that uses restic to create snapshots of
my &lt;code class=&quot;highlighter-rouge&quot;&gt;/home&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;/var&lt;/code&gt; directories and stores the encrypted snapshots on S3.&lt;/p&gt;

&lt;p&gt;It’s not very complex and wouldn’t handle a full system restore if I needed it,
but at the very least I know I will have my user data backed up if anything
goes wrong. If backups aren’t easy, you won’t ever backup your data, and using
a tool like restic removes a lot of the friction.&lt;/p&gt;

&lt;h2 id=&quot;last-thoughts&quot;&gt;last thoughts&lt;/h2&gt;

&lt;p&gt;If you’ve stuck around this far, you’ve seen the entire life-cycle of a new
desktop build. Setting up a Linux system definitely requires a few more tweaks
here and there, but if you’re looking for a free and reliable system you can
use for local development and browsing, I highly recommend Ubuntu 18.&lt;/p&gt;

&lt;p&gt;Lastly, this new desktop is only a couple weeks old and there will be lots of
things to iron over the coming months and years. There are many more things
I would like to experiment with this computer, and I plan on documenting those
projects as the need and time arises.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installing another SSD that exclusively boots Windows.&lt;/li&gt;
  &lt;li&gt;Installing at least two 1TB HDDs that I can use for media backups.&lt;/li&gt;
  &lt;li&gt;Running &lt;a href=&quot;https://www.minio.io/&quot;&gt;Minio&lt;/a&gt;, an open-source s3 tool on those HDDs.&lt;/li&gt;
  &lt;li&gt;Digitizing old VHS tapes using &lt;a href=&quot;https://www.ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt; or &lt;a href=&quot;https://www.videolan.org/vlc/index.html&quot;&gt;vlc&lt;/a&gt; and a TV tuner
  card.&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 12 Jan 2019 12:00:00 -0600</pubDate>
        <link>http://phizzle.space/linux/diy/build/2019/01/12/linux-desktop-build.html</link>
        <guid isPermaLink="true">http://phizzle.space/linux/diy/build/2019/01/12/linux-desktop-build.html</guid>
        
        
        <category>linux</category>
        
        <category>diy</category>
        
        <category>build</category>
        
      </item>
    
      <item>
        <title>Migrating Hosting to S3 and Cloudfront</title>
        <description>&lt;p&gt;When I began this blog, I decided to host it on a small Digital Ocean
droplet. At the time, it made sense - I was learning about managing Ubuntu
servers, firewalls and dns routing. I’ve learned a bunch since then, and lately
my focus has centered around building reliable data systems. I haven’t had time
to properly manage my blog hosting and maintain the toolchain around it.&lt;/p&gt;

&lt;p&gt;It’s built with Jekyll, and over time as I’ve switched computers, tried
installing new gems and more, my local Ruby installation is completely out of
sync. I was also previously using a Jenkins server for continuous integration
and deployment, but since then I’ve stopped it to cut costs.&lt;/p&gt;

&lt;p&gt;As a result, writing new posts has become a chore that requires me to wrestle
with Jekyll, test and then remember how to deploy manually. One of my 2018
goals is to simplify the projects that I work on and make sure that they are
easily maintainable moving forward. With that in mind, today I’ll be walking
through the process of modernizing all the operations around my blog.&lt;/p&gt;

&lt;h1 id=&quot;development-and-writing&quot;&gt;development and writing&lt;/h1&gt;

&lt;p&gt;My first goal was to make writing new posts and testing local with Jekyll as
easy as possible. In the past, It was hard to manage my local
Ruby installation and keep everything up-to-date. To deal with this, I
decided to use a Docker image with Jekyll and ruby already installed.&lt;/p&gt;

&lt;p&gt;Thankfully &lt;a href=&quot;https://github.com/envygeeks/&quot;&gt;envygeeks&lt;/a&gt; maintains a popular &lt;a href=&quot;https://github.com/envygeeks/jekyll-docker/&quot;&gt;Docker
image&lt;/a&gt; that I was able to out of the box, without building my
Dockerfile from scratch. From there, it was just a matter of modifying my
Makefile to run a simple bash script inside of the image.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Makefile

development:
	docker run --rm -p 4000:4000 --volume=&quot;${PWD}:/srv/jekyll&quot; \
        -it jekyll/jekyll ./scripts/development.sh

build:
	docker run --rm --volume=&quot;${PWD}:/srv/jekyll&quot;  \
        -it jekyll/jekyll ./scripts/build.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are just a few important things to note here about how I use docker to
build:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I begin by connecting my current directory with &lt;code class=&quot;highlighter-rouge&quot;&gt;/srv/jekyll&lt;/code&gt; in the
  container. The Dockerfile in the image uses the following line: &lt;code class=&quot;highlighter-rouge&quot;&gt;WORKDIR
  /srv/jekyll&lt;/code&gt; so this is where our commands will get run from.&lt;/li&gt;
  &lt;li&gt;I use &lt;code class=&quot;highlighter-rouge&quot;&gt;--rm&lt;/code&gt; to delete the container after it shuts down, I don’t need
  a bunch of old containers filling up my hard drive.&lt;/li&gt;
  &lt;li&gt;port 4000 from the container is connected to 4000 on the host (my computer)
  so I can easily test &lt;code class=&quot;highlighter-rouge&quot;&gt;http://localhost:4000/&lt;/code&gt; in the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scripts used are even simpler, as shown below. Since jekyll is already
installed, these are just a simple wrapper around in-case I want to add more
tasks in the future. One last note, since the volumes are shared between my
current dir and the container, &lt;code class=&quot;highlighter-rouge&quot;&gt;jekyll serve&lt;/code&gt; detects changes as I write and
immediately regenerates so I can proof-read and test quickly.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# scripts/development.sh

#!/usr/bin/env bash
jekyll build
jekyll serve


# scripts/build.sh

#!/usr/bin/env bash
jekyll build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;serverless-hosting-with-s3-and-cloudfront&quot;&gt;serverless hosting with s3 and cloudfront&lt;/h1&gt;

&lt;p&gt;Next, I wanted to remove all operational overhead
of hosting my own static website. Amazon’s S3 service is a perfect fit and
allows for static websites to be hosted without any personal maintenance.
On top of that, Cloudfront can be used to offer a CDN service in front of S3 to
improve latency. For many static sites, these tools are a great fit and allow
for you to pay only for what you use and nothing more.&lt;/p&gt;

&lt;p&gt;Deploying on this setup is as simple as syncing jekyll’s static output to your
S3 bucket. After that you only need to invalidate the Cloudfront cache in front
of your bucket to ensure that your changes propagate.&lt;/p&gt;

&lt;h1 id=&quot;continuous-integration-and-deployment&quot;&gt;continuous integration and deployment&lt;/h1&gt;

&lt;p&gt;The final step was to set up &lt;a href=&quot;https://travis-ci.org&quot;&gt;Travis-CI&lt;/a&gt; to automate testing and
deployment. Travis-CI supports a new &lt;a href=&quot;https://docs.travis-ci.com/user/build-stages/#What-are-Build-Stages%3F&quot;&gt;jobs&lt;/a&gt; feature that allows
for serial job pipelines that are great for setting up build and deploy
pipelines. Here is the barebones configuration I use to only deploy on
merges to master.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .travis.yml configuration
sudo: required
services:
  - docker

stages:
  - build
  - name: deploy
    if: branch = master

jobs:
  include:
    - stage: build
      script: make build
    - stage: deploy
      script: make deploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since I already have a Makefile setup for things like building and testing,
I added another &lt;code class=&quot;highlighter-rouge&quot;&gt;make deploy&lt;/code&gt; command so that it’s easy for me to deploy from
my machine and from a CI server. I just have to pass in my AWS creds to my
Docker image as environment variables and let it go.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;deploy: build
	docker run --rm --volume=&quot;${PWD}:/build&quot; -it \
	-e AWS_ACCESS_KEY_ID=&amp;lt;access_key&amp;gt; \
	-e AWS_SECRET_ACCESS_KEY=&amp;lt;secret_key&amp;gt; \
	-e AWS_DEFAULT_REGION=&amp;lt;region_name&amp;gt; \
	library/python:3.6 ./build/scripts/deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My deploy script is very simple, it is based on a Python image, so Python and
pip are already installed. From there, installing &lt;code class=&quot;highlighter-rouge&quot;&gt;awscli&lt;/code&gt; is a breeze, and we
only need to sync our local directory with S3 and invalidate the Cloudfront
cache.&lt;/p&gt;

&lt;div class=&quot;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;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Running deploy&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Install aws-cli&quot;&lt;/span&gt;
pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;awscli &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--user&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Beginning deploy&quot;&lt;/span&gt;
~/.local/bin/aws s3 &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt; ./build/_site s3://&amp;lt;bucket_name&amp;gt;
~/.local/bin/aws cloudfront create-invalidation &lt;span class=&quot;nt&quot;&gt;--distribution-id&lt;/span&gt; &amp;lt;distribution_id&amp;gt; &lt;span class=&quot;nt&quot;&gt;--paths&lt;/span&gt; /&lt;span class=&quot;se&quot;&gt;\*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;future-ideas&quot;&gt;future ideas&lt;/h1&gt;

&lt;p&gt;These changes go a long way in helping me post often and without much effort,
but there are a few more nice-to-haves that I’ll save for another weekend.&lt;/p&gt;

&lt;p&gt;I would like to have a command-line spelling/grammar check built into my
testing workflows. The only way I do it now is to copy and paste each blog post
into an online editor once before posting.&lt;/p&gt;

&lt;p&gt;Cloudfront logs all of its requests to file before compressing and storing them
in a S3 bucket. While I already use tools like Google Analytics and Gaug.es,
it would be great practice for my &lt;code class=&quot;highlighter-rouge&quot;&gt;sed&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;awk&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;grep&lt;/code&gt; skills to build some
basic analyzing scripts for my Makefile.&lt;/p&gt;

</description>
        <pubDate>Tue, 20 Mar 2018 13:00:00 -0500</pubDate>
        <link>http://phizzle.space/s3/cloudfront/blog/2018/03/20/hosting-with-s3-cloudfront.html</link>
        <guid isPermaLink="true">http://phizzle.space/s3/cloudfront/blog/2018/03/20/hosting-with-s3-cloudfront.html</guid>
        
        
        <category>s3</category>
        
        <category>cloudfront</category>
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Version Control with Flyway</title>
        <description>&lt;p&gt;It’s been a while since my last post with the main reason being that things
have gotten really busy at &lt;a href=&quot;https://www.amper.xyz/&quot;&gt;amper&lt;/a&gt;. In the past couple months, we’ve hired
another software engineer and a data scientist. As you can imagine, moving from
engineers working solo to teams requires more processes and tools to help
maintain order and keep people from stepping on toes.&lt;/p&gt;

&lt;p&gt;From the beginning, we’ve used things like continuous integration,
unit-tests and version control for our code. Something that we’ve put off is
applying similar principles to our database. We use a Postgres RDS instance
hosted by AWS, and connect many of our services to it. When we’ve needed to
make changes, it was a matter of jumping on the instance and manually writing
SQL for table/index modifications. This has been fine with only two people on
the team, but not with a team of six.&lt;/p&gt;

&lt;p&gt;Growing our team is the main reason why we’ve started version controlling our
database, but it’s not the only one. When I got started with this concept,
I found Jeff Atwood’s &lt;a href=&quot;https://blog.codinghorror.com/get-your-database-under-version-control/&quot;&gt;blog post&lt;/a&gt; on version controlling the
database to be very helpful. There are other resources as well that give some
solid reasons for moving in that direction, and I won’t rehash them here.&lt;/p&gt;

&lt;p&gt;We ended up going with &lt;a href=&quot;https://flywaydb.org/&quot;&gt;flyway&lt;/a&gt;, a Java-based tool that helps with this
process. It is built on using simple SQL scripts that are numbered and get
applied sequentially to bring database schemas up-to-date. The documentation
was decent at explaining the core functionality and usage of the tool, but
I found resources for using flyway in a production environment to be lacking.
The rest of this blog post will be about how amper uses flyway and integrates
it into our workflows. I don’t claim that our usage is the standard, but it has
been useful in getting ourselves up and running!&lt;/p&gt;

&lt;h3 id=&quot;flyway-in-practice&quot;&gt;flyway in practice&lt;/h3&gt;

&lt;p&gt;One of the first confusing things that tripped us up was figuring out how to
structure our repository. When you download flyway for the first time, it comes
with many directories: some for config files, jars, sql and more. This is what
the structure of our repository looks like. I’ll briefly go over what each
section is responsible for below.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.circleci/  # holds our circleci build configurations
  config.yml
conf/  # holds all of our configuration files for database locations
  factory_dev.conf
  factory_prod.conf
  factory_test_ci.conf
  flyway.conf
seed/  # for storing timestamped dump data when developing
  11_30_17_dump.sql
sql/  # where our sql migrations get stored
  V001__sample_sql.sql
  V002__another_sample.sql
  V003__more_sql.sql
users/  # sql that holds the user accounts
  admin.sql
.gitignore
README.md
initialize.sh  # used to set up the db locally
install_flyway.sh  # used to download, unzip and setup flyway
run_ci.sh  # used by circleci to run sql against test db
seed.sh  # adds the seed data to the local dev db
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;configuration-files&quot;&gt;configuration files&lt;/h4&gt;

&lt;p&gt;The first important directory is &lt;code class=&quot;highlighter-rouge&quot;&gt;conf/&lt;/code&gt;. Inside that directory, you’ll find
multiple configuration files that specify different database logins
depending on their name. For example, here is what my
&lt;code class=&quot;highlighter-rouge&quot;&gt;factory_dev.conf&lt;/code&gt; file looks like:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flyway.url=jdbc:postgresql://localhost:5432/dev_db
flyway.user=postgres
flyway.locations=filesystem:sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These configuration files are pretty simple, and if you want to read more about
configuration files, you can find documentation &lt;a href=&quot;https://flywaydb.org/documentation/commandline/&quot;&gt;here&lt;/a&gt;. We use
a separate configuration file for each development environment in order to make
it clear when we are running migrations.&lt;/p&gt;

&lt;h4 id=&quot;sql-migrations&quot;&gt;sql migrations&lt;/h4&gt;

&lt;p&gt;The next important directory is &lt;code class=&quot;highlighter-rouge&quot;&gt;sql/&lt;/code&gt; - this directory is more
self-explanatory. It holds the numbered sql files that flyway uses to migrate
your database. The flyway documentation is pretty clear about how this works,
so I’ll leave that to you to figure out.&lt;/p&gt;

&lt;h4 id=&quot;user-configuration&quot;&gt;user Configuration&lt;/h4&gt;

&lt;p&gt;Inside &lt;code class=&quot;highlighter-rouge&quot;&gt;users/&lt;/code&gt; we store sql scripts that hold the configuration for consistent
users for ourselves and our services. I create new credentials for each
service, along with fine-grained permissions based on what each service needs.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* user accounts */
CREATE USER admin_user WITH PASSWORD 'fake_pass';
CREATE USER user1 WITH PASSWORD 'fake_pass';
CREATE USER service1 WITH PASSWORD 'fake_pass';

/* give all priv's to admin_user */
GRANT ALL PRIVILEGES ON DATABASE &quot;test_db&quot; to admin_user;

/* give read-only privileges to user1 */
GRANT SELECT ON ALL TABLES IN SCHEMA test_schema TO user1;
ALTER DEFAULT PRIVILEGES IN SCHEMA test_schema GRANT SELECT ON TABLES TO user1;

/* give read/write privileges to service1 */
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA test_schema TO service1;
ALTER DEFAULT PRIVILEGES IN SCHEMA test_schema GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO service1;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We keep this outside our SQL migrations to allow for us to maintain consistent
permissions across all of our servers. The credentials generated here are what
the configuration files use to connect/run migrations, so it’s a bit of
a chicken-and-egg problem. The &lt;code class=&quot;highlighter-rouge&quot;&gt;initialize.sh&lt;/code&gt; script in the root of our
directory automatically runs these permissions commands to save some time and
&lt;code class=&quot;highlighter-rouge&quot;&gt;psql&lt;/code&gt; syntax lookups. This is definitely one of the more experimental
aspects of our flyway usage, so we’ll see how this evolves over time.&lt;/p&gt;

&lt;h4 id=&quot;scripting&quot;&gt;scripting&lt;/h4&gt;

&lt;p&gt;The rest of the contents in this directory are helper scripts that make common
actions a bit easier. &lt;code class=&quot;highlighter-rouge&quot;&gt;initialize.sh&lt;/code&gt; is used to ease getting a local
database up and running locally. &lt;code class=&quot;highlighter-rouge&quot;&gt;seed.sh&lt;/code&gt; makes it easy to load in seed data
from the &lt;code class=&quot;highlighter-rouge&quot;&gt;seeds/&lt;/code&gt; directory. We also include a script that our continous
integration tools use to automatically validate new migrations.&lt;/p&gt;

&lt;h3 id=&quot;practical-workflows&quot;&gt;practical workflows&lt;/h3&gt;

&lt;p&gt;With the repository structure set up, let’s go over how we actually use flyway.
I’ll go over two basic workflows, 1) initializing a local development database
and 2) making changes to the production database schema.&lt;/p&gt;

&lt;h4 id=&quot;local-setup&quot;&gt;local setup&lt;/h4&gt;

&lt;p&gt;When a new developer joins the team, or we’re setting up a new development
machine, we follow this workflow. It’s not completely automated, but the most
critical parts have been to help reduce human error.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Set up a local instance of Postgres, this can be done with Docker, brew or
any other packages.&lt;/li&gt;
  &lt;li&gt;Manually create a database with the name that your dev configuration uses.&lt;/li&gt;
  &lt;li&gt;Run the &lt;code class=&quot;highlighter-rouge&quot;&gt;initialize.sh&lt;/code&gt; script which takes care of user and schema
initialization.&lt;/li&gt;
  &lt;li&gt;Migrate the clean database to the current production schema with &lt;code class=&quot;highlighter-rouge&quot;&gt;flyway
migrate&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Optional: Add some seed data to the database by running the script
&lt;code class=&quot;highlighter-rouge&quot;&gt;seed.sh&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, it’s pretty simple, and it handles most of the complicated
aspects for you. Once you have a database installed and running, our process
takes care of almost all of it for you! We also use a similar flow when we want
to blow our local DB and bring it back up to latest, especially after lots of
testing.&lt;/p&gt;

&lt;h4 id=&quot;making-changes&quot;&gt;making changes&lt;/h4&gt;

&lt;p&gt;The next most common workflow is actually making changes to the database. These
steps assume that you already have the latest production schema running in your
local database.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Checkout a new branch in the database repo.&lt;/li&gt;
  &lt;li&gt;Using &lt;code class=&quot;highlighter-rouge&quot;&gt;psql&lt;/code&gt; or your favorite DB admin tool, modify the database to fit your
requirements. Be sure to keep track of exactly what you did if you spent
a bunch of time experimenting.&lt;/li&gt;
  &lt;li&gt;Place all of your new changes in a sql file, and be sure to name it
following the flyway convention. By default, it requires numerically-ordered
files that look like this: &lt;code class=&quot;highlighter-rouge&quot;&gt;V001__some_change.sql&lt;/code&gt;. Add a new file and
increment the latest version so that flyway can pick it up.&lt;/li&gt;
  &lt;li&gt;Open a pull-request, and let the CI server pick up your changes and ensure
that your sql runs without error.&lt;/li&gt;
  &lt;li&gt;Merge the pull-request, and then run &lt;code class=&quot;highlighter-rouge&quot;&gt;flyway migrate&lt;/code&gt; against your
production database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This flow is also pretty simple, it makes it very clear for everyone to review
what exactly you’re doing to the database. And while we don’t have complex
migration validation with our CI (it uses an empty DB), we can at least
validate that it is valid sql on an outside machine. Next, I’ll share a little
more about how we’ve set up our testing flow.&lt;/p&gt;

&lt;h3 id=&quot;continuous-testing&quot;&gt;continuous testing&lt;/h3&gt;

&lt;p&gt;Now that we can programmatically migrate our databases, the next step is to
hook it up to some continuous tools to help validate your builds on a clean
server. Our CI testing flow is pretty naive so it doesn’t ensure that existing
data can be migrated, but it helps validate that the SQL is valid and can be
run against the existing schema.&lt;/p&gt;

&lt;p&gt;We keep a tiny RDS instance running at all times. When a new commit is pushed
to a remote branch, we run &lt;code class=&quot;highlighter-rouge&quot;&gt;flyway clean&lt;/code&gt; and then &lt;code class=&quot;highlighter-rouge&quot;&gt;flyway migrate&lt;/code&gt; against the
test db. By clearing any existing schemas, this lets us be sure that the SQL we
wrote will work against what exactly is in production. Like mentioned before,
this doesn’t migrate with seed data, but it gives us enough confidence that we
aren’t missing anything obvious.&lt;/p&gt;

&lt;p&gt;I elected not to automatically deploy to production on merges, as there might
be cases where we want to carry out additional spot testing. At our team-size,
this hasn’t proven to be an issue as we aren’t altering the database multiple
times a day. If you elect to move forward with automatic production deploys, it
might be worth investing in automating testing of the existing APIs against the
new schema to make sure there it is compatible.&lt;/p&gt;

&lt;h3 id=&quot;looking-forward&quot;&gt;looking forward&lt;/h3&gt;

&lt;p&gt;So far, I’ve described our simple processes for managing our database schemas.
For a small team, it works decently well, and helps keep everyone on the same
page as we add, refactor, and remove old datastores. It also helps us find
errors and easily see a working history of our databases’ evolution over time.
That being said, there are a few areas that we’d like to improve as time
permits:&lt;/p&gt;

&lt;h4 id=&quot;improving-the-setup-process&quot;&gt;improving the setup process&lt;/h4&gt;

&lt;p&gt;When onboarding new developers and setting up new machines, the setup process
above is a bit complex and requires many steps. It is also quite easy to
botch it if one isn’t careful. In the future we’d like to simplify the process,
and also highlight exactly how flyway commands help us manage our schemas.&lt;/p&gt;

&lt;h4 id=&quot;automating-seed-data&quot;&gt;automating seed data&lt;/h4&gt;

&lt;p&gt;The seed data we currently have in our repo is manually generated using
&lt;code class=&quot;highlighter-rouge&quot;&gt;pgdump&lt;/code&gt; and the &lt;code class=&quot;highlighter-rouge&quot;&gt;--schema-only&lt;/code&gt; flag. As you can imagine, this gets out of
date as our schema evolves and requires someone to occasionally bump this. In
an ideal world, we would have an automated weekly job that dumps production
data into an S3 bucket. Each snapshot would be tagged with the current schema
version, and when a seed command gets run, the tool would reconcile the schema
version and find the latest valid seed snapshot.&lt;/p&gt;

&lt;h4 id=&quot;ci-testing-with-real-data&quot;&gt;ci testing with real data&lt;/h4&gt;

&lt;p&gt;Similar to the above point, we would want our CI testing to also get seeded
with the most recent production data. Once we’ve validated that our latest
migration works against a prod mirror, we’d like to run integration tests from
our API to ensure that it has the correct code needed for the new schema.&lt;/p&gt;

&lt;p&gt;If you have ideas, feedback or more questions about how we do simple version
control, feel free to reach out! When we developed this process, there wasn’t
as much helpful documentation on the web as I thought there might be, so
hopefully this gives you a concrete example. Good luck!&lt;/p&gt;

</description>
        <pubDate>Sun, 07 Jan 2018 12:00:00 -0600</pubDate>
        <link>http://phizzle.space/db/postgres/flyway/2018/01/07/version-control-with-flyway.html</link>
        <guid isPermaLink="true">http://phizzle.space/db/postgres/flyway/2018/01/07/version-control-with-flyway.html</guid>
        
        
        <category>db</category>
        
        <category>postgres</category>
        
        <category>flyway</category>
        
      </item>
    
  </channel>
</rss>
