<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Russell Ballestrini</title><link href="https://russell.ballestrini.net/" rel="alternate"></link><link href="https://russell.ballestrini.net/feeds/all.atom.xml" rel="self"></link><id>https://russell.ballestrini.net/</id><updated>2024-02-17T14:11:00-05:00</updated><entry><title>Integrating OpenAI with Dry's Sample Chat Game</title><link href="https://russell.ballestrini.net/integrating-openai-with-dry-sample-chat-game/" rel="alternate"></link><published>2024-02-17T14:11:00-05:00</published><updated>2024-02-17T14:11:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2024-02-17:/integrating-openai-with-dry-sample-chat-game/</id><summary type="html">&lt;p&gt;This tutorial demonstrates enhancing &lt;a class="reference external" href="https://gitlab.com/luckeyproductions/dry/-/blob/master/Source/Samples/16_Chat/Chat.cpp"&gt;Dry's Sample Chat Game&lt;/a&gt; by integrating OpenAI's language models, enabling the game to provide intelligent, AI-driven responses to user queries. Dry, the successor to Urho3D, offers a comprehensive framework for developing 2D and 3D games. Leveraging LLMs within the Dry engine opens up new possibilities.&lt;/p&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background …&lt;/h2&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;This tutorial demonstrates enhancing &lt;a class="reference external" href="https://gitlab.com/luckeyproductions/dry/-/blob/master/Source/Samples/16_Chat/Chat.cpp"&gt;Dry's Sample Chat Game&lt;/a&gt; by integrating OpenAI's language models, enabling the game to provide intelligent, AI-driven responses to user queries. Dry, the successor to Urho3D, offers a comprehensive framework for developing 2D and 3D games. Leveraging LLMs within the Dry engine opens up new possibilities.&lt;/p&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The Sample Chat game in Dry provides basic messaging functionality where multiple copies of the game client can connect to a server to communicate via text messages. Our aim is to extend this functionality to include responses from OpenAI's language models when messages contain specific triggers, such as &amp;quot;gpt-3&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prerequisites"&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Ensure you have the following before starting:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;An OpenAI API key.&lt;/li&gt;
&lt;li&gt;A &lt;a class="reference external" href="https://russell.ballestrini.net/building-dry-and-park-from-source-on-fedora-linux/"&gt;configured Dry build environment&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Some familiarity with build tooling or at least the ability to copy and paste commands into the terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="step-by-step-integration"&gt;
&lt;h2&gt;Step-by-Step Integration&lt;/h2&gt;
&lt;p&gt;Follow these steps to integrate OpenAI with the Sample Chat game:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Obtain the OpenAI C++ Client&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before modifying the chat game, you need to download the necessary files from the OpenAI C++ client repository. Use the following commands to create a directory for these files and download them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/git/dry
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;Source/ThirdParty/openai
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;Source/ThirdParty/openai/nlohmann
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Source/ThirdParty/openai
wget&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/olrea/openai-cpp/main/include/openai/openai.hpp
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nlohmann
wget&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/olrea/openai-cpp/main/include/openai/nlohmann/json.hpp
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I also found the need to modify the client to code slightly for the JSON import, pardon me if this is ignorant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// #include &amp;lt;nlohmann/json.hpp&amp;gt;  // nlohmann/json&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;Dry/ThirdParty/openai/nlohmann/json.hpp&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The client requires cURL development files, on Fedora:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dnf&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;libcurl-devel
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Update the CMake Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Apply the following diff to &lt;cite&gt;CMakeLists.txt&lt;/cite&gt; to include the OpenAI C++ client as well as cURL in your dry project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/CMakeLists.txt b/CMakeLists.txt&lt;/span&gt;
&lt;span class="gh"&gt;index a1eff04..0f32bc7 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/CMakeLists.txt&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/CMakeLists.txt&lt;/span&gt;
&lt;span class="gu"&gt;@@ -188,6 +188,20 @@ else ()&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;endif ()
&lt;span class="w"&gt; &lt;/span&gt;file (MAKE_DIRECTORY ${THIRD_PARTY_INCLUDE_DIR})

&lt;span class="gi"&gt;+# Find the cURL library&lt;/span&gt;
&lt;span class="gi"&gt;+find_package(CURL REQUIRED)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+# Globally link cURL to all targets&lt;/span&gt;
&lt;span class="gi"&gt;+link_libraries(${CURL_LIBRARIES})&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+# Create a symbolic link for openai&lt;/span&gt;
&lt;span class="gi"&gt;+execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink&lt;/span&gt;
&lt;span class="gi"&gt;+                ${PROJECT_SOURCE_DIR}/Source/ThirdParty/openai&lt;/span&gt;
&lt;span class="gi"&gt;+                ${CMAKE_BINARY_DIR}/include/Dry/ThirdParty/openai)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Modify Sample/16_Chat/chat.cpp file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implement the changes outlined in the diffs below for &lt;tt class="docutils literal"&gt;chat.cpp&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/Source/Samples/16_Chat/Chat.cpp b/Source/Samples/16_Chat/Chat.cpp&lt;/span&gt;
&lt;span class="gh"&gt;index ee7c2b7..9d0e454 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/Source/Samples/16_Chat/Chat.cpp&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/Source/Samples/16_Chat/Chat.cpp&lt;/span&gt;
&lt;span class="gu"&gt;@@ -41,6 +41,7 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;#include &amp;lt;Dry/UI/Text.h&amp;gt;
&lt;span class="w"&gt; &lt;/span&gt;#include &amp;lt;Dry/UI/UI.h&amp;gt;
&lt;span class="w"&gt; &lt;/span&gt;#include &amp;lt;Dry/UI/UIEvents.h&amp;gt;
&lt;span class="gi"&gt;+#include &amp;lt;Dry/ThirdParty/openai/openai.hpp&amp;gt;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;#include &amp;quot;Chat.h&amp;quot;

&lt;span class="gu"&gt;@@ -201,16 +202,58 @@ void Chat::HandleSend(StringHash /*eventType*/, VariantMap&amp;amp; eventData)&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;    if (serverConnection)
&lt;span class="w"&gt; &lt;/span&gt;    {
&lt;span class="gd"&gt;-        // A VectorBuffer object is convenient for constructing a message to send&lt;/span&gt;
&lt;span class="gd"&gt;-        VectorBuffer msg;&lt;/span&gt;
&lt;span class="gd"&gt;-        msg.WriteString(text);&lt;/span&gt;
&lt;span class="gd"&gt;-        // Send the chat message as in-order and reliable&lt;/span&gt;
&lt;span class="gd"&gt;-        serverConnection-&amp;gt;SendMessage(MSG_CHAT, true, true, msg);&lt;/span&gt;
&lt;span class="gi"&gt;+        // Check if the message contains &amp;quot;gpt-3&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        if (text.Contains(&amp;quot;gpt-3&amp;quot;))&lt;/span&gt;
&lt;span class="gi"&gt;+        {&lt;/span&gt;
&lt;span class="gi"&gt;+            // Initialize OpenAI&lt;/span&gt;
&lt;span class="gi"&gt;+            openai::start();&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            // Correctly construct the JSON payload as a std::string&lt;/span&gt;
&lt;span class="gi"&gt;+            std::string payload = std::string(R&amp;quot;({&amp;quot;model&amp;quot;: &amp;quot;gpt-3.5-turbo&amp;quot;, &amp;quot;messages&amp;quot;:[{&amp;quot;role&amp;quot;:&amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;:&amp;quot;)&amp;quot;) + text.CString() + std::string(R&amp;quot;(&amp;quot;}], &amp;quot;max_tokens&amp;quot;: 600, &amp;quot;temperature&amp;quot;: 0.5})&amp;quot;);&lt;/span&gt;
&lt;span class="gi"&gt;+            nlohmann::json gptResponse;&lt;/span&gt;
&lt;span class="gi"&gt;+            try {&lt;/span&gt;
&lt;span class="gi"&gt;+                // Parse the payload to JSON and make the API call&lt;/span&gt;
&lt;span class="gi"&gt;+                gptResponse = openai::chat().create(nlohmann::json::parse(payload));&lt;/span&gt;
&lt;span class="gi"&gt;+            } catch (const std::exception&amp;amp; e) {&lt;/span&gt;
&lt;span class="gi"&gt;+                // Handle JSON parsing errors or API call failures&lt;/span&gt;
&lt;span class="gi"&gt;+                std::cerr &amp;lt;&amp;lt; &amp;quot;Error making API call or parsing response: &amp;quot; &amp;lt;&amp;lt; e.what() &amp;lt;&amp;lt; &amp;#39;\n&amp;#39;;&lt;/span&gt;
&lt;span class="gi"&gt;+                return;&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            std::string responseText;&lt;/span&gt;
&lt;span class="gi"&gt;+            try {&lt;/span&gt;
&lt;span class="gi"&gt;+                // Extract the response text from the JSON response&lt;/span&gt;
&lt;span class="gi"&gt;+                responseText = gptResponse[&amp;quot;choices&amp;quot;][0][&amp;quot;message&amp;quot;][&amp;quot;content&amp;quot;].get&amp;lt;std::string&amp;gt;();&lt;/span&gt;
&lt;span class="gi"&gt;+            } catch (const std::exception&amp;amp; e) {&lt;/span&gt;
&lt;span class="gi"&gt;+                // Handle errors in accessing the response content&lt;/span&gt;
&lt;span class="gi"&gt;+                std::cerr &amp;lt;&amp;lt; &amp;quot;Error extracting response text: &amp;quot; &amp;lt;&amp;lt; e.what() &amp;lt;&amp;lt; &amp;#39;\n&amp;#39;;&lt;/span&gt;
&lt;span class="gi"&gt;+                return;&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            // send the user&amp;#39;s message to gpt-3 to the server.&lt;/span&gt;
&lt;span class="gi"&gt;+            VectorBuffer msg1;&lt;/span&gt;
&lt;span class="gi"&gt;+            msg1.WriteString(text);&lt;/span&gt;
&lt;span class="gi"&gt;+            serverConnection-&amp;gt;SendMessage(MSG_CHAT, true, true, msg1);&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            // send the gpt-3 completion to the server.&lt;/span&gt;
&lt;span class="gi"&gt;+            VectorBuffer msg2;&lt;/span&gt;
&lt;span class="gi"&gt;+            msg2.WriteString(String(responseText.c_str()));&lt;/span&gt;
&lt;span class="gi"&gt;+            serverConnection-&amp;gt;SendMessage(MSG_CHAT, true, true, msg2);&lt;/span&gt;
&lt;span class="gi"&gt;+        }&lt;/span&gt;
&lt;span class="gi"&gt;+        else&lt;/span&gt;
&lt;span class="gi"&gt;+        {&lt;/span&gt;
&lt;span class="gi"&gt;+            // Normal chat message handling&lt;/span&gt;
&lt;span class="gi"&gt;+            VectorBuffer msg;&lt;/span&gt;
&lt;span class="gi"&gt;+            msg.WriteString(text);&lt;/span&gt;
&lt;span class="gi"&gt;+            serverConnection-&amp;gt;SendMessage(MSG_CHAT, true, true, msg);&lt;/span&gt;
&lt;span class="gi"&gt;+        }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        // Empty the text edit after sending
&lt;span class="w"&gt; &lt;/span&gt;        textEdit_-&amp;gt;SetText(String::EMPTY);
&lt;span class="w"&gt; &lt;/span&gt;    }
&lt;span class="w"&gt; &lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Prepare the Build Environment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before running CMake, ensure that any previous build configurations are cleared to avoid conflicts. This might involve deleting the CMake cache file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;CMakeCache.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# If exists&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, generate the build configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;..&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug&lt;span class="w"&gt; &lt;/span&gt;-DRY_64BIT&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;strong&gt;Build the Chat Game&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Compile the Sample Chat game with the newly integrated OpenAI C++ client:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;16_Chat/fast
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-the-integration"&gt;
&lt;h2&gt;Testing the Integration&lt;/h2&gt;
&lt;p&gt;After applying the changes and compiling the game, ensure your OpenAI API key is available to the game:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;your_openai_api_key_here&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run the Sample Chat game and try sending a message containing &amp;quot;gpt-3&amp;quot;. You should see an intelligent response generated by OpenAI's language model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./bin/16_Chat
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Remember you'll need at least one instance of the game running as server mode before a client can interact with the LLM.&lt;/p&gt;
&lt;p&gt;That means you need to run &lt;tt class="docutils literal"&gt;./bin/16_Chat&lt;/tt&gt; at least twice in two different windows to see the experience.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-next"&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;You've now integrated OpenAI into Dry's Sample Chat game, enhancing it with AI-driven conversational capabilities. Explore further by customizing triggers, integrating other models using the same OpenAI client, or expanding the game's features.&lt;/p&gt;
&lt;p&gt;I think personally I will try to get the client communicating with vllm likely running openchat.&lt;/p&gt;
&lt;p&gt;Happy coding, and enjoy bringing LLM capabilities to your Dry games!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="C++"></category><category term="OpenAI"></category><category term="Dry"></category><category term="Chatbot"></category></entry><entry><title>Optimizing Memory Management for Plausible Docker on Ubuntu</title><link href="https://russell.ballestrini.net/optimizing-memory-management-for-plausible-docker-on-ubuntu/" rel="alternate"></link><published>2024-02-05T17:40:00-05:00</published><updated>2024-02-05T17:40:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2024-02-05:/optimizing-memory-management-for-plausible-docker-on-ubuntu/</id><summary type="html">&lt;p&gt;Running Docker containers on a server with limited memory can lead to out-of-memory (OOM) issues, which can disrupt services and lead to downtime. This guide will show you how to increase swap space on an Ubuntu server to provide a buffer against OOM errors, using a real-world example from a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Running Docker containers on a server with limited memory can lead to out-of-memory (OOM) issues, which can disrupt services and lead to downtime. This guide will show you how to increase swap space on an Ubuntu server to provide a buffer against OOM errors, using a real-world example from a server running multiple Docker containers.&lt;/p&gt;
&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Consider a typical scenario where an Ubuntu server is running several Docker containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;fox@analytics:~$ sudo docker ps
CONTAINER ID        IMAGE                                           PORTS                          NAMES
7677a77e5606        plausible/analytics:v2.0                        0.0.0.0:8000-&amp;gt;8000/tcp         plausible-plausible-1
0ea79b7d0c03        clickhouse/clickhouse-server:23.3.7.5-alpine    8123/tcp, 9000/tcp, 9009/tcp   plausible-plausible_events_db-1
33f6e8a30da4        postgres:14-alpine                              5432/tcp                       plausible-plausible_db-1
40400c725d7c        bytemark/smtp                                   25/tcp                         plausible-mail-1

fox@analytics:~$ free -m
              total        used        free      shared  buff/cache   available
Mem:            981         693          62          70         225          66
Swap:             0           0           0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, the server has less than 1 GB of RAM and no swap space configured. This configuration is prone to OOM errors, especially when the containers require more memory than what is available. In my case the service stayed online but the configuration management service &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;salt-minion&lt;/span&gt;&lt;/tt&gt; was unable to be reached to deploy new TLS certificates, the cert expired and prevented clients from sending metrics.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-and-enabling-swap-space"&gt;
&lt;h2&gt;Creating and Enabling Swap Space&lt;/h2&gt;
&lt;p&gt;To prevent OOM errors, we'll create a swap file. This script automates the process, ensuring that your system has additional virtual memory to handle peak loads.&lt;/p&gt;
&lt;p&gt;Save the script as &lt;tt class="docutils literal"&gt;setup_swap.sh&lt;/tt&gt; and execute it on your server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c1"&gt;# Size of the swap file&lt;/span&gt;
&lt;span class="nv"&gt;SWAP_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1G
&lt;span class="nv"&gt;SWAP_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/swapfile
&lt;span class="nv"&gt;SWAPPINESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="nv"&gt;CACHE_PRESSURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;

&lt;span class="c1"&gt;# Create a swap file&lt;/span&gt;
fallocate&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SWAP_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1048576&lt;/span&gt;

&lt;span class="c1"&gt;# Secure swap file permissions&lt;/span&gt;
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;

&lt;span class="c1"&gt;# Set up a Linux swap area&lt;/span&gt;
mkswap&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;

&lt;span class="c1"&gt;# Enable the swap file&lt;/span&gt;
swapon&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;

&lt;span class="c1"&gt;# Make the swap file permanent&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$SWAP_FILE&lt;/span&gt;&lt;span class="s2"&gt; none swap sw 0 0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/fstab

&lt;span class="c1"&gt;# Set the swappiness value&lt;/span&gt;
sysctl&lt;span class="w"&gt; &lt;/span&gt;vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$SWAPPINESS&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vm.swappiness=&lt;/span&gt;&lt;span class="nv"&gt;$SWAPPINESS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/sysctl.conf

&lt;span class="c1"&gt;# Set the cache pressure value&lt;/span&gt;
sysctl&lt;span class="w"&gt; &lt;/span&gt;vm.vfs_cache_pressure&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$CACHE_PRESSURE&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vm.vfs_cache_pressure=&lt;/span&gt;&lt;span class="nv"&gt;$CACHE_PRESSURE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/sysctl.conf

&lt;span class="c1"&gt;# Output the results&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Swap file created and enabled:&amp;quot;&lt;/span&gt;
swapon&lt;span class="w"&gt; &lt;/span&gt;--show
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Swappiness set to &lt;/span&gt;&lt;span class="nv"&gt;$SWAPPINESS&lt;/span&gt;&lt;span class="s2"&gt; and cache pressure set to &lt;/span&gt;&lt;span class="nv"&gt;$CACHE_PRESSURE&lt;/span&gt;&lt;span class="s2"&gt;.&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This script will create a 1 GB swap file, configure the system to use it, and adjust kernel parameters to optimize memory usage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="understanding-swappiness-and-cache-pressure"&gt;
&lt;h2&gt;Understanding Swappiness and Cache Pressure&lt;/h2&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;swappiness&lt;/tt&gt; parameter influences how often the system uses swap space. A value of 10 encourages the system to keep processes in RAM, resorting to swap only when necessary.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;vfs_cache_pressure&lt;/tt&gt; setting determines how aggressively the kernel reclaims memory from the cache. A value of 50 provides a balance between reclaiming memory and maintaining cache for quick file access.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="monitoring-your-system"&gt;
&lt;h2&gt;Monitoring Your System&lt;/h2&gt;
&lt;p&gt;After increasing the swap space, monitor your system's memory usage with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;free&lt;span class="w"&gt; &lt;/span&gt;-m
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will help you understand if the swap space is sufficient or if further adjustments are needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-next"&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;By adding swap space and tuning kernel parameters, you've bolstered your server's ability to handle memory-intensive Docker containers. However, swap is not a replacement for physical RAM. If your server consistently uses a lot of swap, consider upgrading the RAM for better performance.&lt;/p&gt;
&lt;p&gt;Stay proactive in managing your server's resources to ensure uninterrupted service for your Dockerized applications. Happy hosting!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Docker"></category><category term="Ubuntu"></category></entry><entry><title>Understanding Pause Functionality in LucKey Park Built on Dry Engine</title><link href="https://russell.ballestrini.net/understanding-pause-functionality-in-luckey-park-game/" rel="alternate"></link><published>2024-02-05T08:57:00-05:00</published><updated>2024-02-05T08:57:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2024-02-05:/understanding-pause-functionality-in-luckey-park-game/</id><summary type="html">&lt;p class="first last"&gt;Dive deep into the pause functionality of LucKey Park built on the Dry engine, exploring how the game's status is managed and toggled end-to-end.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In today's post, we're examining the pause functionality of the &lt;a class="reference external" href="https://gitlab.com/luckeyproductions/games/park"&gt;LucKey Park game code&lt;/a&gt;, which is built on the 'Dry' engine. We'll uncover how the game's status is managed and toggled, especially in response to the cancel button (ESC key), and provide insights into the game's input handling and state management.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Check out &lt;a class="reference external" href="https://luckeyproductions.itch.io/park"&gt;screenshots and videos of LucKey Park on itch.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="understanding-the-game-s-status-management"&gt;
&lt;h2&gt;Understanding the Game's Status Management&lt;/h2&gt;
&lt;p&gt;The 'Park' game uses an enumeration &lt;tt class="docutils literal"&gt;GameStatus&lt;/tt&gt; with values like &lt;tt class="docutils literal"&gt;GS_MAIN&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;GS_PLAY&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;GS_PAUSED&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;GS_MODAL&lt;/tt&gt; to represent the game's current state. The &lt;tt class="docutils literal"&gt;Game&lt;/tt&gt; class in &lt;tt class="docutils literal"&gt;game.h&lt;/tt&gt; maintains a &lt;tt class="docutils literal"&gt;status_&lt;/tt&gt; variable to track this state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;GameStatus&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GS_MAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GS_PLAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GS_PAUSED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GS_MODAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Game&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;GameStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Game&lt;/tt&gt; class provides methods to get and set this status, as well as a &lt;tt class="docutils literal"&gt;TogglePause()&lt;/tt&gt; method that switches between &lt;tt class="docutils literal"&gt;GS_PLAY&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;GS_PAUSED&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="section" id="binding-the-cancel-action"&gt;
&lt;h3&gt;Binding the Cancel Action&lt;/h3&gt;
&lt;p&gt;In &lt;tt class="docutils literal"&gt;inputmaster.cpp&lt;/tt&gt;, the &lt;tt class="docutils literal"&gt;InputMaster&lt;/tt&gt; class binds the &lt;tt class="docutils literal"&gt;IA_CANCEL&lt;/tt&gt; action to the ESC key. This setup is crucial for handling the pause functionality when the player presses ESC.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IA_CANCEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;KEY_ESCAPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="handling-the-cancel-action"&gt;
&lt;h3&gt;Handling the Cancel Action&lt;/h3&gt;
&lt;p&gt;When the &lt;tt class="docutils literal"&gt;IA_CANCEL&lt;/tt&gt; action is triggered, the &lt;tt class="docutils literal"&gt;HandleAction&lt;/tt&gt; method in &lt;tt class="docutils literal"&gt;InputMaster&lt;/tt&gt; responds accordingly. If no tool is active, it calls &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gui-&amp;gt;ShowMainMenu()&lt;/span&gt;&lt;/tt&gt;, which indirectly pauses the game by changing the game's status.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;IA_CANCEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sceneCursor&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gui&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ShowMainMenu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="showing-the-main-menu-and-pausing-the-game"&gt;
&lt;h3&gt;Showing the Main Menu and Pausing the Game&lt;/h3&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;ShowMainMenu()&lt;/tt&gt; method in &lt;tt class="docutils literal"&gt;gui.cpp&lt;/tt&gt; is where the game's status is set to &lt;tt class="docutils literal"&gt;GS_MODAL&lt;/tt&gt;, effectively pausing the game when the main menu is displayed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GUI::ShowMainMenu&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;mainMenu_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GetSubsystem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetStatus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GS_PLAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GS_MODAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;HideDash&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-main-update-loop-controlled-by-dry-engine"&gt;
&lt;h3&gt;The Main Update Loop Controlled by Dry Engine&lt;/h3&gt;
&lt;p&gt;The Dry engine, like its predecessor Urho3D, manages the main game loop internally. Developers hook into this loop by subscribing to the &lt;tt class="docutils literal"&gt;E_UPDATE&lt;/tt&gt; event, which the engine dispatches every frame. This event allows developers to perform per-frame updates to their game logic.&lt;/p&gt;
&lt;p&gt;While the main update loop is not explicitly shown in the provided snippets, it's typically where the game checks the &lt;tt class="docutils literal"&gt;status_&lt;/tt&gt; variable each frame. If the status is &lt;tt class="docutils literal"&gt;GS_PAUSED&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;GS_MODAL&lt;/tt&gt;, the loop skips updating the game world, pausing the game's logic.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="end-to-end-pause-functionality"&gt;
&lt;h3&gt;End-to-End Pause Functionality&lt;/h3&gt;
&lt;p&gt;From the moment the player presses the ESC key to the game entering a paused state, the flow is as follows:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The ESC key is pressed.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;InputMaster&lt;/tt&gt; detects the &lt;tt class="docutils literal"&gt;IA_CANCEL&lt;/tt&gt; action.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;HandleAction&lt;/tt&gt; calls &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gui-&amp;gt;ShowMainMenu()&lt;/span&gt;&lt;/tt&gt; if no tool is active.&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ShowMainMenu()&lt;/tt&gt; sets the game's status to &lt;tt class="docutils literal"&gt;GS_MODAL&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;The main update loop, controlled by the Dry engine, respects this status and pauses the game.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This exploration has provided a clear understanding of how the pause functionality is implemented in Lucky Park using the Dry engine. It's a great example of how game state management and input handling work together to create responsive gameplay.&lt;/p&gt;
&lt;p&gt;Stay tuned for more deep dives into game development.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Programming"></category><category term="Game Development"></category><category term="Dry"></category><category term="Park"></category></entry><entry><title>Installing YaCy on Ubuntu</title><link href="https://russell.ballestrini.net/installing-yacy-on-ubuntu/" rel="alternate"></link><published>2024-02-04T11:02:00-05:00</published><updated>2024-02-04T11:02:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2024-02-04:/installing-yacy-on-ubuntu/</id><summary type="html">&lt;p&gt;This guide will walk you through installing YaCy on Ubuntu.&lt;/p&gt;
&lt;p&gt;By default YaCy is configured to bind to &lt;tt class="docutils literal"&gt;0.0.0.0&lt;/tt&gt; but it's admin interface is only accessible by default to a white list which includes &lt;tt class="docutils literal"&gt;localhost&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;127.0.0.1&lt;/tt&gt;, since my install is headless on a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This guide will walk you through installing YaCy on Ubuntu.&lt;/p&gt;
&lt;p&gt;By default YaCy is configured to bind to &lt;tt class="docutils literal"&gt;0.0.0.0&lt;/tt&gt; but it's admin interface is only accessible by default to a white list which includes &lt;tt class="docutils literal"&gt;localhost&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;127.0.0.1&lt;/tt&gt;, since my install is headless on a remote Ubuntu server, I access the admin interface remotely via an SSH tunnel.&lt;/p&gt;
&lt;div class="section" id="installing-yacy"&gt;
&lt;h2&gt;Installing YaCy&lt;/h2&gt;
&lt;p&gt;run this script in a terminal, &lt;tt class="docutils literal"&gt;install_yacy.sh&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c1"&gt;# Update package lists&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update

&lt;span class="c1"&gt;# Install Java Development Kit (JDK)&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;openjdk-21-jdk

&lt;span class="c1"&gt;# Check if Java is correctly installed&lt;/span&gt;
java&lt;span class="w"&gt; &lt;/span&gt;-version

&lt;span class="c1"&gt;# Download YaCy&lt;/span&gt;
wget&lt;span class="w"&gt; &lt;/span&gt;https://download.yacy.net/yacy_v1.924_20210209_10069.tar.gz

&lt;span class="c1"&gt;# Extract the downloaded file&lt;/span&gt;
tar&lt;span class="w"&gt; &lt;/span&gt;xfz&lt;span class="w"&gt; &lt;/span&gt;yacy_v1.924_20210209_10069.tar.gz

&lt;span class="c1"&gt;# Change to the extracted directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yacy

&lt;span class="c1"&gt;# Start YaCy&lt;/span&gt;
./startYACY.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="accessing-the-admin-interface-remotely-via-an-ssh-tunnel"&gt;
&lt;h2&gt;Accessing the Admin Interface Remotely via an SSH Tunnel&lt;/h2&gt;
&lt;p&gt;You can create an SSH tunnel to your remote YaCy server, for example &lt;tt class="docutils literal"&gt;yacy.foxhop.net&lt;/tt&gt; so that when you access &lt;tt class="docutils literal"&gt;localhost:8090&lt;/tt&gt;, it tunnels to the remote host.&lt;/p&gt;
&lt;p&gt;Open a terminal on your local machine and run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8090&lt;/span&gt;:localhost:8090&lt;span class="w"&gt; &lt;/span&gt;user@yacy.foxhop.net
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Replace &lt;cite&gt;user&lt;/cite&gt; with your username on the remote host. Now, when you access &lt;cite&gt;localhost:8090&lt;/cite&gt; on your local machine, it will be tunneled to the remote host.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="securing-yacy-portal-and-admin-access-with-ssl-tls"&gt;
&lt;h2&gt;Securing YaCy Portal and admin access with SSL/TLS&lt;/h2&gt;
&lt;p&gt;In the early days of YaCy, encryption was not as prevalent as it is today. However, securing your YaCy instance with SSL/TLS is essential for modern web safety standards. Here's how to enable SSL/TLS encryption for your YaCy server:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Generate a keystore using the &lt;tt class="docutils literal"&gt;keytool&lt;/tt&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;keytool&lt;span class="w"&gt; &lt;/span&gt;-keystore&lt;span class="w"&gt; &lt;/span&gt;mySrvKeystore&lt;span class="w"&gt; &lt;/span&gt;-genkey&lt;span class="w"&gt; &lt;/span&gt;-keyalg&lt;span class="w"&gt; &lt;/span&gt;RSA&lt;span class="w"&gt; &lt;/span&gt;-alias&lt;span class="w"&gt; &lt;/span&gt;yacy.foxhop.net
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command creates a keystore file named &lt;tt class="docutils literal"&gt;mySrvKeystore&lt;/tt&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Move the &lt;tt class="docutils literal"&gt;mySrvKeystore&lt;/tt&gt; file to the &lt;tt class="docutils literal"&gt;DATA/SETTINGS/&lt;/tt&gt; directory in your YaCy installation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;mySrvKeystore&lt;span class="w"&gt; &lt;/span&gt;/path/to/YaCy/DATA/SETTINGS/
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Edit the &lt;tt class="docutils literal"&gt;yacy.conf&lt;/tt&gt; file to configure YaCy to use the new keystore:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;vim&lt;span class="w"&gt; &lt;/span&gt;/path/to/YaCy/DATA/SETTINGS/yacy.conf
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Add or modify the following lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;keyStore=DATA/SETTINGS/mySrvKeystore
keyStorePassword=YourKeystorePassword
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Replace &lt;tt class="docutils literal"&gt;YourKeystorePassword&lt;/tt&gt; with the password you chose when you created the keystore with the &lt;tt class="docutils literal"&gt;keytool&lt;/tt&gt; command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Restart YaCy to apply the SSL/TLS settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/path/to/YaCy/stopYACY.sh
/path/to/YaCy/startYACY.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, you can access the YaCy admin interface securely via &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://localhost:8443&lt;/span&gt;&lt;/tt&gt;. By default, YaCy listens on port &lt;tt class="docutils literal"&gt;8443&lt;/tt&gt; for HTTPS, but this can be changed in the admin console, as was done in this case to use port &lt;tt class="docutils literal"&gt;8091&lt;/tt&gt; so that &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://localhost:8091&lt;/span&gt;&lt;/tt&gt; works instead. Ensure that HTTP remains on port &lt;tt class="docutils literal"&gt;8090&lt;/tt&gt; for DHT network access by peers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-next"&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;With SSL/TLS enabled, it's time to start a local crawl and consider sharing the results with the YaCy network. To do this, you may need to open port &lt;tt class="docutils literal"&gt;8090&lt;/tt&gt; on your router and forward it to your YaCy host. Before making changes to your network configuration, verify your peer mode status in the YaCy admin interface to understand your node's role in the network.&lt;/p&gt;
&lt;p&gt;Sharing your crawl index with the network allows for a distributed scraping effort, meaning that the data you collect can benefit all users of YaCy, not just your local instance. This is the essence of the Distributed Hash Table (DHT) that underpins YaCy's decentralized architecture.&lt;/p&gt;
&lt;p&gt;Continue to explore the capabilities of your YaCy server, and remember to update your documentation to assist others in their journey toward a more open and collaborative internet.`&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Building 'Dry' and 'Park' from Source on Fedora Linux</title><link href="https://russell.ballestrini.net/building-dry-and-park-from-source-on-fedora-linux/" rel="alternate"></link><published>2024-02-03T09:19:00-05:00</published><updated>2024-02-03T09:19:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2024-02-03:/building-dry-and-park-from-source-on-fedora-linux/</id><summary type="html">&lt;p class="first last"&gt;Learn how to compile the 'Dry' game engine and the 'Park' game from source on Fedora Linux with debugging enabled, and how to analyze core dumps for debugging.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In this guide, we'll explore how to compile the 'Dry' game engine and the 'Park' game from source on Fedora Linux with debugging enabled. This process will give you a deeper understanding of the inner workings of game development and the satisfaction of running a game you built yourself.&lt;/p&gt;
&lt;p&gt;For Ubuntu, go here for pre-compiled game:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://luckeyproductions.itch.io/park"&gt;https://luckeyproductions.itch.io/park&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="prerequisites"&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before diving in, ensure you have the necessary tools and libraries installed:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;CMake&lt;/li&gt;
&lt;li&gt;GNU Make&lt;/li&gt;
&lt;li&gt;GCC or Clang&lt;/li&gt;
&lt;li&gt;X11 and audio system development libraries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install these on Fedora with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dnf&lt;span class="w"&gt; &lt;/span&gt;groupinstall&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Development Tools&amp;quot;&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;dnf&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;gcc-c++&lt;span class="w"&gt; &lt;/span&gt;libX11-devel&lt;span class="w"&gt; &lt;/span&gt;libXcursor-devel&lt;span class="w"&gt; &lt;/span&gt;libXinerama-devel&lt;span class="w"&gt; &lt;/span&gt;libXi-devel&lt;span class="w"&gt; &lt;/span&gt;libXrandr-devel&lt;span class="w"&gt; &lt;/span&gt;libXrender-devel&lt;span class="w"&gt; &lt;/span&gt;libXScrnSaver-devel&lt;span class="w"&gt; &lt;/span&gt;libXxf86vm-devel&lt;span class="w"&gt; &lt;/span&gt;pulseaudio-libs-devel&lt;span class="w"&gt; &lt;/span&gt;nas-libs-devel&lt;span class="w"&gt; &lt;/span&gt;qt5-qtbase-devel
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="cloning-the-repositories"&gt;
&lt;h2&gt;Cloning the Repositories&lt;/h2&gt;
&lt;p&gt;Clone the 'Dry' and 'Park' repositories using Git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/git
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://gitlab.com/luckeyproductions/dry.git
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://gitlab.com/luckeyproductions/games/park.git
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="building-dry-with-debugging-enabled"&gt;
&lt;h2&gt;Building Dry with Debugging Enabled&lt;/h2&gt;
&lt;p&gt;Navigate to the 'dry' directory and prepare the build environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dry
mkdir&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Configure the build with CMake and enable debugging:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;..&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug&lt;span class="w"&gt; &lt;/span&gt;-DRY_64BIT&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Compile the Dry library with debug symbols:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="building-park-with-debugging-enabled"&gt;
&lt;h2&gt;Building Park with Debugging Enabled&lt;/h2&gt;
&lt;p&gt;In the 'Park' project, modify the &lt;tt class="docutils literal"&gt;Park.pro&lt;/tt&gt; file to add the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-g&lt;/span&gt; &lt;span class="pre"&gt;-O0&lt;/span&gt;&lt;/tt&gt; flags for debugging, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;QMAKE_CXXFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-std&lt;span class="o"&gt;=&lt;/span&gt;c++17&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;-O0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then set the &lt;tt class="docutils literal"&gt;DRY_HOME&lt;/tt&gt; environment variable to the path of your compiled Dry library:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DRY_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/fox/git/dry/build
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Navigate to the 'park' directory and compile the game:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/fox/git/park
mkdir&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build
qmake&lt;span class="w"&gt; &lt;/span&gt;../Park.pro
make
cp&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;../Resources&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="running-park"&gt;
&lt;h2&gt;Running Park&lt;/h2&gt;
&lt;p&gt;After a successful build, run the Park executable located in the &lt;tt class="docutils literal"&gt;build&lt;/tt&gt; directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./park
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="analyzing-core-dumps-on-fedora"&gt;
&lt;h2&gt;Analyzing Core Dumps on Fedora&lt;/h2&gt;
&lt;p&gt;If your application crashes, Fedora can generate core dumps, which are snapshots of the program's state at the time of the crash. These can be invaluable for debugging.&lt;/p&gt;
&lt;p&gt;List recent core dumps with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;coredumpctl&lt;span class="w"&gt; &lt;/span&gt;list
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To analyze a specific core dump, use &lt;tt class="docutils literal"&gt;gdb&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gdb&lt;span class="w"&gt; &lt;/span&gt;/path/to/executable&lt;span class="w"&gt; &lt;/span&gt;/path/to/coredump
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gdb&lt;span class="w"&gt; &lt;/span&gt;/home/fox/git/park/Park/park&lt;span class="w"&gt; &lt;/span&gt;core.291993
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once in &lt;tt class="docutils literal"&gt;gdb&lt;/tt&gt;, use the &lt;tt class="docutils literal"&gt;bt&lt;/tt&gt; command to print a backtrace:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(gdb) bt
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will show you the call stack at the time of the crash, which can help pinpoint the source of the problem.&lt;/p&gt;
&lt;p&gt;Happy building and debugging!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Programming"></category><category term="Game Development"></category><category term="Fedora"></category><category term="Dry"></category><category term="Park"></category></entry><entry><title>Ubuntu 22.04 Letsencrypt Docker Hints</title><link href="https://russell.ballestrini.net/ubuntu-22-04-letsencrypt-docker-hints/" rel="alternate"></link><published>2022-05-20T20:10:00-04:00</published><updated>2022-05-20T20:10:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2022-05-20:/ubuntu-22-04-letsencrypt-docker-hints/</id><summary type="html">&lt;p&gt;letsencrypt certbot is now installable via snap (the deb apt repository is no longer maintained).&lt;/p&gt;
&lt;p&gt;alternatively you can use certbot via docker if you plan to use the &lt;tt class="docutils literal"&gt;certonly&lt;/tt&gt; mode.&lt;/p&gt;
&lt;p&gt;I did run into some issues &amp;amp; I will document my workarounds here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;domains&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;example.com
&lt;span class="w"&gt;    &lt;/span&gt;shop.example.com
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;domain …&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;letsencrypt certbot is now installable via snap (the deb apt repository is no longer maintained).&lt;/p&gt;
&lt;p&gt;alternatively you can use certbot via docker if you plan to use the &lt;tt class="docutils literal"&gt;certonly&lt;/tt&gt; mode.&lt;/p&gt;
&lt;p&gt;I did run into some issues &amp;amp; I will document my workarounds here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;domains&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;example.com
&lt;span class="w"&gt;    &lt;/span&gt;shop.example.com
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;domain&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;certifying: &lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;domain_parts&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;domain_parts_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;domain_parts&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$domain_parts_length&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-eq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# https://eff-certbot.readthedocs.io/en/stable/install.html#running-with-docker&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;certbot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/letsencrypt:/etc/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/lib/letsencrypt:/var/lib/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/log/letsencrypt:/var/log/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/www:/www&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;certbot/certbot&lt;span class="w"&gt; &lt;/span&gt;certonly&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;--renew-by-default&lt;span class="w"&gt; &lt;/span&gt;--webroot&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;/www&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;www.&lt;span class="nv"&gt;$domain&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# https://eff-certbot.readthedocs.io/en/stable/install.html#running-with-docker&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;certbot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/letsencrypt:/etc/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/lib/letsencrypt:/var/lib/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/log/letsencrypt:/var/log/letsencrypt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/www:/www&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;certbot/certbot&lt;span class="w"&gt; &lt;/span&gt;certonly&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;--renew-by-default&lt;span class="w"&gt; &lt;/span&gt;--webroot&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;/www&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# copy certificate links to a known file path and extention.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/etc/letsencrypt/live/&lt;span class="nv"&gt;$domain&lt;/span&gt;/fullchain.pem&lt;span class="w"&gt; &lt;/span&gt;/etc/letsencrypt/live/&lt;span class="nv"&gt;$domain&lt;/span&gt;/crt.crt
&lt;span class="w"&gt;    &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/etc/letsencrypt/live/&lt;span class="nv"&gt;$domain&lt;/span&gt;/privkey.pem&lt;span class="w"&gt; &lt;/span&gt;/etc/letsencrypt/live/&lt;span class="nv"&gt;$domain&lt;/span&gt;/key.key
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c1"&gt;# use minionfs to stage all certificates onto the salt master.&lt;/span&gt;
salt-call&lt;span class="w"&gt; &lt;/span&gt;cp.push_dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/letsencrypt/live/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*.crt&amp;#39;&lt;/span&gt;
salt-call&lt;span class="w"&gt; &lt;/span&gt;cp.push_dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/etc/letsencrypt/live/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;*.key
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the key is the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-v&lt;/span&gt;&lt;/tt&gt; mounts, I needed one for my &lt;tt class="docutils literal"&gt;webroot&lt;/tt&gt; of &lt;tt class="docutils literal"&gt;/www&lt;/tt&gt; &amp;amp; one for logs.&lt;/p&gt;
&lt;p&gt;This is different or not assumed in the official guide notes.&lt;/p&gt;
&lt;p&gt;Additionally on Ubuntu 22.04 LTS in Linode, I was not any to use the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-v&lt;/span&gt;&lt;/tt&gt; mounts until I ran these commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;root

mkdir&lt;span class="w"&gt; &lt;/span&gt;/sys/fs/cgroup/systemd
mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;cgroup&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;none,name&lt;span class="o"&gt;=&lt;/span&gt;systemd&lt;span class="w"&gt; &lt;/span&gt;cgroup&lt;span class="w"&gt; &lt;/span&gt;/sys/fs/cgroup/systemd
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The mount command never persists across reboots so you will want the following &lt;tt class="docutils literal"&gt;/etc/fstab&lt;/tt&gt; entry:&lt;/p&gt;
&lt;p&gt;Brian Amedro ended up modifying grub to avoid loading the certain systemd subsystem which were found to cause us the trouble:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/docker/for-linux/issues/219#issuecomment-817318014"&gt;https://github.com/docker/for-linux/issues/219#issuecomment-817318014&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For now i will place the &lt;tt class="docutils literal"&gt;mkdir&lt;/tt&gt; &amp;amp; &lt;tt class="docutils literal"&gt;mount&lt;/tt&gt; commands into the renew script since the fstab solution doesn't work&lt;/p&gt;
&lt;p&gt;because the directory doesn't exist, gets deleted on each reboot so it isn't present during boot mount time.&lt;/p&gt;
&lt;p&gt;/etc/fstab&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cgroup&lt;span class="w"&gt;    &lt;/span&gt;/sys/fs/cgroup/systemd&lt;span class="w"&gt;    &lt;/span&gt;cgroup&lt;span class="w"&gt;    &lt;/span&gt;defaults
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Vertically Scaling GitLab Server For As Cheap As Possible</title><link href="https://russell.ballestrini.net/vertically-scaling-gitlab-server-for-as-cheap-as-possible/" rel="alternate"></link><published>2022-01-05T19:13:00-05:00</published><updated>2022-01-05T19:13:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2022-01-05:/vertically-scaling-gitlab-server-for-as-cheap-as-possible/</id><summary type="html">&lt;p&gt;Today's essay acts as a power-up love story for the underdog.&lt;/p&gt;
&lt;p&gt;A living document &amp;amp; quickstart for:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;bootstrappers&lt;/li&gt;
&lt;li&gt;small business under 99 employees&lt;/li&gt;
&lt;li&gt;solo ops or devs&lt;/li&gt;
&lt;li&gt;entrepreneurs&lt;/li&gt;
&lt;li&gt;hackers&lt;/li&gt;
&lt;li&gt;tinkerers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also deserve a quick start to a GitLab Server power house!&lt;/p&gt;
&lt;p&gt;Regardless of my intended audience, this strategy should scale …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today's essay acts as a power-up love story for the underdog.&lt;/p&gt;
&lt;p&gt;A living document &amp;amp; quickstart for:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;bootstrappers&lt;/li&gt;
&lt;li&gt;small business under 99 employees&lt;/li&gt;
&lt;li&gt;solo ops or devs&lt;/li&gt;
&lt;li&gt;entrepreneurs&lt;/li&gt;
&lt;li&gt;hackers&lt;/li&gt;
&lt;li&gt;tinkerers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also deserve a quick start to a GitLab Server power house!&lt;/p&gt;
&lt;p&gt;Regardless of my intended audience, this strategy should scale to
500-1,000 concurrent engineers on the smallest of the instance classes
which satisfy the current GitLab Server &amp;quot;minimum requirements&amp;quot;.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.gitlab.com/ee/install/requirements.html"&gt;https://docs.gitlab.com/ee/install/requirements.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="why-gitlab-server"&gt;
&lt;h2&gt;Why GitLab Server?&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;GitLab is open source (not black box)&lt;/li&gt;
&lt;li&gt;GitLab CI is a game changer&lt;/li&gt;
&lt;li&gt;GitLab CI is extendable to any workflow&lt;/li&gt;
&lt;li&gt;GitLab Server wants 4G of memory&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="expected-results"&gt;
&lt;h2&gt;Expected Results&lt;/h2&gt;
&lt;p&gt;This guide prescribes the following setup:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A Physical or Virtual Machine with at least 4G of RAM&lt;/li&gt;
&lt;li&gt;Docker installed&lt;/li&gt;
&lt;li&gt;Docker container started via docker-compose to manage
a monolithic Omnibus installation of GitLab Server&lt;/li&gt;
&lt;li&gt;A Cronjob to backup GitLab Server to &lt;tt class="docutils literal"&gt;/tmp&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;A Cronjob to collect Gitlab Server backup tarball &amp;amp; &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/etc/gitlab/gitlab-secrets.json&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;A strong recommendation to test the complete backup and recovery process
at least once to prepare for when times get rough.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="gitlab-server-omnibus-in-docker"&gt;
&lt;h2&gt;GitLab Server Omnibus In Docker&lt;/h2&gt;
&lt;p&gt;The installation &amp;amp; configuration is documented using SaltStack to make it easy
to spin up GitLab Servers.&lt;/p&gt;
&lt;p&gt;To install SaltStack on the new host dedicated for GitLab Server, Run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;https://bootstrap.saltstack.com&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;stable&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;master: salt.example.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/salt/minion.d/custom.conf&lt;span class="p"&gt;;&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;salt-minion&lt;span class="w"&gt; &lt;/span&gt;restart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On the instance designated as salt-master, accept the new salt-minion key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;salt-key&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;git2.unturf.com
The&lt;span class="w"&gt; &lt;/span&gt;following&lt;span class="w"&gt; &lt;/span&gt;keys&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;going&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;accepted:
Unaccepted&lt;span class="w"&gt; &lt;/span&gt;Keys:
Git2.unturf.com
Proceed?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;n/Y&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;y
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and make sure communication is established:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;salt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git2*&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test.ping
git2.unturf.com:
&lt;span class="w"&gt;    &lt;/span&gt;True
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and run a highstate to configure that new host instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;salt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git2*&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;state.highstate
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once the command returns, assuming we had no errors or failures we
have docker installed and an Omnibus Gitlab Server booted inside a container.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Summary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git2.unturf.com
--------------
Succeeded:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Failed:&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
--------------
Total&lt;span class="w"&gt; &lt;/span&gt;states&lt;span class="w"&gt; &lt;/span&gt;run:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;
Total&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;time:&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;194&lt;/span&gt;.431&lt;span class="w"&gt; &lt;/span&gt;s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The GitLab Server's internal services take over 3 minutes to come online.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="disaster-recovery"&gt;
&lt;h2&gt;Disaster Recovery&lt;/h2&gt;
&lt;p&gt;Prepare for real disaster by practicing the entire backup and restore process.&lt;/p&gt;
&lt;p&gt;As extra homework, it is high advisable to spin up a 2nd host for GitLab Server
in order to test the backup and restore process from start to finish:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-gitlab"&gt;https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-gitlab&lt;/a&gt;&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;GitLab CI is a game changer.&lt;/dt&gt;
&lt;dd&gt;Gitlab is like Circle CI 2.0 only not black box.&lt;/dd&gt;
&lt;dt&gt;GitLab CI is extendable to any workflow&lt;/dt&gt;
&lt;dd&gt;but you should keep it simple, learn to use the tool as intended,
and rethink legacy workflows rather than port.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Russell Open Sources Remarkbox &amp; MakePostSell into Public Domain!</title><link href="https://russell.ballestrini.net/russell-open-sources-remarkbox-and-make-post-sell-into-public-domain/" rel="alternate"></link><published>2021-12-23T12:08:00-05:00</published><updated>2021-12-23T12:08:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2021-12-23:/russell-open-sources-remarkbox-and-make-post-sell-into-public-domain/</id><summary type="html">&lt;img alt="green pixel art style Christmas tree with animated blinking lights" class="align-right" src="/uploads/2018/pixel-art-yuletide-tree.gif" /&gt;
&lt;p&gt;Merry Christmas &amp;amp; Happy Holidays, Everyone!&lt;/p&gt;
&lt;p&gt;My wife prompts students with this fun holiday writing idea:&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;&amp;quot;If you could gift the world anything, what would it be?&amp;quot;&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;I love to read responses in the comments!&lt;/p&gt;
&lt;img alt="pixel art style seasonal wrapped gift" class="align-right" src="/uploads/2018/pixel-art-gift.png" /&gt;
&lt;p&gt;&lt;strong&gt;As for me, I have wonderful news, my dream gift to the world will come true …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;img alt="green pixel art style Christmas tree with animated blinking lights" class="align-right" src="/uploads/2018/pixel-art-yuletide-tree.gif" /&gt;
&lt;p&gt;Merry Christmas &amp;amp; Happy Holidays, Everyone!&lt;/p&gt;
&lt;p&gt;My wife prompts students with this fun holiday writing idea:&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;&amp;quot;If you could gift the world anything, what would it be?&amp;quot;&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;I love to read responses in the comments!&lt;/p&gt;
&lt;img alt="pixel art style seasonal wrapped gift" class="align-right" src="/uploads/2018/pixel-art-gift.png" /&gt;
&lt;p&gt;&lt;strong&gt;As for me, I have wonderful news, my dream gift to the world will come true this year!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For the holiday season, I AM gifting to the public domain my most essential code in hopes to trigger a &lt;strong&gt;positive trend of doing in 2022&lt;/strong&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;&amp;quot;in 2022, dream &amp;amp; do!&amp;quot;&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;Each project has a link to guide on how to access the source code.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Remarkbox Logo Trademark" src="https://www.remarkbox.com/remarkbox-minified.png" style="width: 400px;" /&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://git.unturf.com/engineering/remarkbox/remarkbox"&gt;https://git.unturf.com/engineering/remarkbox/remarkbox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="MakePostSell Logo Trademark" src="https://www.makepostsell.com/static/mps.png" style="width: 400px;" /&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://git.unturf.com/engineering/make-post-sell/make_post_sell"&gt;https://git.unturf.com/engineering/make-post-sell/make_post_sell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In 2021, I imagined many possible paths, yet all seemed to lead to one word:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OPEN&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I will continue to operate the SaaS under Remarkbox and MakePostSell trademarks &amp;amp; operations will fall under &lt;tt class="docutils literal"&gt;unturf.&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;I will take an additional role of &lt;tt class="docutils literal"&gt;BDFL&lt;/tt&gt; in regards to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;onboarding volunteers to maintain our &lt;tt class="docutils literal"&gt;common code&lt;/tt&gt; in perpetuity&lt;/li&gt;
&lt;li&gt;tie breaking&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It feels vulnerable to share code without team support. Think from end &amp;amp; this feeling will pass.&lt;/p&gt;
&lt;img alt="animated gif of a pixel art style circular bomb with fuse burning down" src="/uploads/2018/pixel-art-bomb.gif" /&gt;
&lt;img alt="steal this image glider.png 1337 h4x0r h4ndb00k" src="/uploads/2019/glider.png" /&gt;
&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Alice may finally read the code &amp;amp; find vulnerabilities,
burning a hole between us crippling our communication, taking down the system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More environments in wild, more eyes on problems, more options communicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The masses may fork &amp;amp; divide the perfect circle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We volunteer to grow &amp;amp; multiply our systems, not worry about forks.
We avoid focus on those who ignorantly mutilate or divide,
instead focus on projects and forks which sprout, multiply, &amp;amp; thrive!&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;At a high level the &lt;tt class="docutils literal"&gt;unturf.&lt;/tt&gt; stack currently consists of the following:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.python.org/"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://sqlite.org/index.html"&gt;Sqlite3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.pylonsproject.org/projects/pyramid/en/latest/index.html"&gt;Pyramid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://uwsgi-docs.readthedocs.io/en/latest/"&gt;uWSGI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://nginx.org/en/"&gt;Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.postfix.org/documentation.html"&gt;Postfix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html"&gt;Object Store (Boto3/S3 Compatible)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course we dogfood:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.makepostsell.com"&gt;MakePostSell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For analytics we self host our own:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://plausible.io"&gt;Plausible&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the documentation works, people should be able to use the services without understanding the fundementals of each keyword listed above.&lt;/p&gt;
&lt;p&gt;Pull requests welcome.&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;The writing of this essay has unfolded liberation in me and so,
I speak words in favor of Truth, Freedom, and Love.&lt;/p&gt;
&lt;p&gt;I AM now free to Grow, Explore, Document, and Multiply!&lt;/p&gt;
&lt;p&gt;I love you, have a great day reader!&lt;/p&gt;
&lt;img alt="pixel art style santa hat" src="/uploads/2018/pixel-art-santa-hat.png" /&gt;
</content><category term="misc"></category><category term="unturf."></category></entry><entry><title>GNUnet GNS Nameserver Operator Notes, Quickstart, &amp; Cheatsheet</title><link href="https://russell.ballestrini.net/gnunet-gns-nameserver-operator-notes-quickstart-cheatsheet/" rel="alternate"></link><published>2021-12-03T18:21:00-05:00</published><updated>2021-12-03T18:21:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2021-12-03:/gnunet-gns-nameserver-operator-notes-quickstart-cheatsheet/</id><summary type="html">&lt;p&gt;We GROW with Truth, Freedom, Love.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&amp;quot;You have an ally&amp;quot;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We scroll through the official GNUnet handbook, everything is covered in details, sections have examples. Seems verbose at first glance but also familiar .&lt;/p&gt;
&lt;p&gt;Freedom of speech is under attack.&lt;/p&gt;
&lt;p&gt;I AM not scared, we understand the technology we have and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;We GROW with Truth, Freedom, Love.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&amp;quot;You have an ally&amp;quot;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We scroll through the official GNUnet handbook, everything is covered in details, sections have examples. Seems verbose at first glance but also familiar .&lt;/p&gt;
&lt;p&gt;Freedom of speech is under attack.&lt;/p&gt;
&lt;p&gt;I AM not scared, we understand the technology we have and the technology we need. We will rescue ourselves and GROW together.&lt;/p&gt;
&lt;p&gt;GNS right NOW!&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Our Collective's Internet Nameservice (DNS) is UNDER ACTIVE ATTACK by groups of governments.
Multitiple reports of DNS Name registrars breaking registrants Internet Domains on the behalf of NON-HUMAN ENTITIES.&lt;/p&gt;
&lt;p&gt;To avoid any erosion of our Freedom of speech, we will use GNS to make sure our web domains continue to map to our resources for people looking for us.&lt;/p&gt;
&lt;p&gt;I AM a seasoned 17 year Bind9 DNS administrator. I had my start on FreeBSD 5.0, virtualized over the years, now on Ubuntu VMs running on &amp;quot;the cloud&amp;quot; &amp;amp; local hypervisors hosting on broken laptops.&lt;/p&gt;
&lt;p&gt;We know how to scale DNS, it sort of just works once you figure out the config files.&lt;/p&gt;
&lt;p&gt;So let's figure out GNS and GROW a new Internet Culture, eh?&lt;/p&gt;
&lt;p&gt;---&lt;/p&gt;
&lt;p&gt;We pull down the source code using git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://git.gnunet.org/gnunet.git
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We see a lot of C code, interesting choice.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gnunet
&lt;span class="o"&gt;[&lt;/span&gt;fox@blanka&lt;span class="w"&gt; &lt;/span&gt;gnunet&lt;span class="o"&gt;]&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-hal&lt;span class="w"&gt; &lt;/span&gt;src/dns/
total&lt;span class="w"&gt; &lt;/span&gt;144K
drwxrwxr-x.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;362&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;.
drwxrwxr-x.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;834&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;..
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.3K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;dns_api.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.3K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;dns.conf.in
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.2K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;dns.h
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;126&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;.gitignore
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;11K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;gnunet-dns-monitor.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.1K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;gnunet-dns-redirector.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;32K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;gnunet-helper-dns.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;35K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;gnunet-service-dns.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt;  &lt;/span&gt;14K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;gnunet-zonewalk.c
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.2K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;Makefile.am
-rw-rw-r--.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;.5K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;plugin_block_dns.c
-rwxrwxr-x.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;fox&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.6K&lt;span class="w"&gt; &lt;/span&gt;Dec&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;18&lt;/span&gt;:09&lt;span class="w"&gt; &lt;/span&gt;test_gnunet_dns.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GNUnet 0.15.0 released: &lt;a class="reference external" href="https://www.gnunet.org/en/news/2021-08-0.15.0.html"&gt;https://www.gnunet.org/en/news/2021-08-0.15.0.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GNS First-come-first-served GNUnet top-level domain &amp;quot;.pin&amp;quot; zone key and website updated&lt;/p&gt;
&lt;p&gt;Register now: &lt;a class="reference external" href="https://fcfs.gnunet.org/"&gt;https://fcfs.gnunet.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wow, Ok - We want to reserve our favorite names, right?&lt;/p&gt;
&lt;p&gt;How do we generate our &lt;tt class="docutils literal"&gt;zone key`&lt;/tt&gt;?&lt;/p&gt;
&lt;p&gt;A section in the GNUnet handbook which seems to cover zones and keys:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.gnunet.org/handbook/gnunet.html#First-steps-_002d-Using-the-GNU-Name-System"&gt;https://docs.gnunet.org/handbook/gnunet.html#First-steps-_002d-Using-the-GNU-Name-System&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;To register a top-level domain of the &lt;tt class="docutils literal"&gt;.pin&lt;/tt&gt; we need to generate a public/private keypair for each zone.&lt;/p&gt;
&lt;p&gt;But before we do that we need to install GNUnet and configure the tool.&lt;/p&gt;
&lt;p&gt;Ok, I would prefer some up-to-date binaries than the source tree.&lt;/p&gt;
&lt;p&gt;Fedora package looks old and abandoned, same with Ubuntu, &amp;amp; Alpine...&lt;/p&gt;
&lt;p&gt;I AM dreaming of a lightweight Alpine docker container to pass around the nets with with GNUnet prebaked in. Maybe a project for another day.&lt;/p&gt;
&lt;p&gt;It seems like the NixOS package manager has an uptodate version 0.15.3!&lt;/p&gt;
&lt;p&gt;To install GNUnet binaries, we must first learn how to install &lt;tt class="docutils literal"&gt;nix&lt;/tt&gt; package manager.&lt;/p&gt;
&lt;p&gt;Installing nix package manager, run the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# reference: https://nixos.org/download.html&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://nixos.org/nix/install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once installed try to install GNUnet, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;nix-env&lt;span class="w"&gt; &lt;/span&gt;-iA&lt;span class="w"&gt; &lt;/span&gt;nixpkgs.gnunet
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Errors complaining about certificates might be an easy fix:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/fox/.nix-profile/etc/profile.d/nix.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example the install script placed the following into my &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.bash_profile&lt;/span&gt;&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;/home/fox/.nix-profile/etc/profile.d/nix.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;/home/fox/.nix-profile/etc/profile.d/nix.sh&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# added by Nix installer&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you get all that sorted you should have installed GNUnet!&lt;/p&gt;
&lt;p&gt;To verify try to interact with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.nix-profile/bin/gnunet-config&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Battleship Operational, A Carrier Has Arrived.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;fox@blanka&lt;span class="w"&gt; &lt;/span&gt;russell.ballestrini.net&lt;span class="o"&gt;]&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;gnunet-config&lt;span class="w"&gt; &lt;/span&gt;--version
gnunet-config&lt;span class="w"&gt; &lt;/span&gt;v0.15.3&lt;span class="w"&gt; &lt;/span&gt;release
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Alright now we must somehow configure zones and public/private keypairs with this tool.&lt;/p&gt;
&lt;p&gt;The handbook gives us this as an example, and we break down the anatomy of the command and outputs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gnunet-config&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;gns&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;.myfriend&lt;span class="w"&gt; &lt;/span&gt;-V&lt;span class="w"&gt; &lt;/span&gt;PUBLIC_KEY
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's try passing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--help&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;fox@blanka&lt;span class="w"&gt; &lt;/span&gt;russell.ballestrini.net&lt;span class="o"&gt;]&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;gnunet-config&lt;span class="w"&gt; &lt;/span&gt;--help

gnunet-config&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;OPTIONS&lt;span class="o"&gt;]&lt;/span&gt;
Manipulate&lt;span class="w"&gt; &lt;/span&gt;GNUnet&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;files
Arguments&lt;span class="w"&gt; &lt;/span&gt;mandatory&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;long&lt;span class="w"&gt; &lt;/span&gt;options&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;also&lt;span class="w"&gt; &lt;/span&gt;mandatory&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;options.
&lt;span class="w"&gt;  &lt;/span&gt;-b,&lt;span class="w"&gt; &lt;/span&gt;--supported-backend&lt;span class="o"&gt;=&lt;/span&gt;BACKEND
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;current&lt;span class="w"&gt; &lt;/span&gt;installation&lt;span class="w"&gt; &lt;/span&gt;supports&lt;span class="w"&gt; &lt;/span&gt;the
&lt;span class="w"&gt;                               &lt;/span&gt;specified&lt;span class="w"&gt; &lt;/span&gt;BACKEND
&lt;span class="w"&gt;  &lt;/span&gt;-c,&lt;span class="w"&gt; &lt;/span&gt;--config&lt;span class="o"&gt;=&lt;/span&gt;FILENAME&lt;span class="w"&gt;      &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;FILENAME
&lt;span class="w"&gt;  &lt;/span&gt;-d,&lt;span class="w"&gt; &lt;/span&gt;--diagnostics&lt;span class="w"&gt;          &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;extra&lt;span class="w"&gt; &lt;/span&gt;diagnostics
&lt;span class="w"&gt;  &lt;/span&gt;-F,&lt;span class="w"&gt; &lt;/span&gt;--full&lt;span class="w"&gt;                 &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;full&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;file,&lt;span class="w"&gt; &lt;/span&gt;including
&lt;span class="w"&gt;                               &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;values
&lt;span class="w"&gt;  &lt;/span&gt;-f,&lt;span class="w"&gt; &lt;/span&gt;--filename&lt;span class="w"&gt;             &lt;/span&gt;interpret&lt;span class="w"&gt; &lt;/span&gt;option&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;filename&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;with
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nv"&gt;$-&lt;/span&gt;expansion&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-h,&lt;span class="w"&gt; &lt;/span&gt;--help&lt;span class="w"&gt;                 &lt;/span&gt;print&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-L,&lt;span class="w"&gt; &lt;/span&gt;--log&lt;span class="o"&gt;=&lt;/span&gt;LOGLEVEL&lt;span class="w"&gt;         &lt;/span&gt;configure&lt;span class="w"&gt; &lt;/span&gt;logging&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;LOGLEVEL
&lt;span class="w"&gt;  &lt;/span&gt;-l,&lt;span class="w"&gt; &lt;/span&gt;--logfile&lt;span class="o"&gt;=&lt;/span&gt;FILENAME&lt;span class="w"&gt;     &lt;/span&gt;configure&lt;span class="w"&gt; &lt;/span&gt;logging&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;write&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;FILENAME
&lt;span class="w"&gt;  &lt;/span&gt;-o,&lt;span class="w"&gt; &lt;/span&gt;--option&lt;span class="o"&gt;=&lt;/span&gt;OPTION&lt;span class="w"&gt;        &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;option&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;access
&lt;span class="w"&gt;  &lt;/span&gt;-r,&lt;span class="w"&gt; &lt;/span&gt;--rewrite&lt;span class="w"&gt;              &lt;/span&gt;rewrite&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;file,&lt;span class="w"&gt; &lt;/span&gt;even&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nothing
&lt;span class="w"&gt;                               &lt;/span&gt;changed
&lt;span class="w"&gt;  &lt;/span&gt;-S,&lt;span class="w"&gt; &lt;/span&gt;--list-sections&lt;span class="w"&gt;        &lt;/span&gt;print&lt;span class="w"&gt; &lt;/span&gt;available&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;sections
&lt;span class="w"&gt;  &lt;/span&gt;-s,&lt;span class="w"&gt; &lt;/span&gt;--section&lt;span class="o"&gt;=&lt;/span&gt;SECTION&lt;span class="w"&gt;      &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;section&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;access
&lt;span class="w"&gt;  &lt;/span&gt;-V,&lt;span class="w"&gt; &lt;/span&gt;--value&lt;span class="o"&gt;=&lt;/span&gt;VALUE&lt;span class="w"&gt;          &lt;/span&gt;value&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-v,&lt;span class="w"&gt; &lt;/span&gt;--version&lt;span class="w"&gt;              &lt;/span&gt;print&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;number
Report&lt;span class="w"&gt; &lt;/span&gt;bugs&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;gnunet-developers@gnu.org.
Home&lt;span class="w"&gt; &lt;/span&gt;page:&lt;span class="w"&gt; &lt;/span&gt;http://www.gnu.org/s/gnunet/
General&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;GNU&lt;span class="w"&gt; &lt;/span&gt;software:&lt;span class="w"&gt; &lt;/span&gt;http://www.gnu.org/gethelp/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok so it seems like this command simply edits config files for GNUnet.&lt;/p&gt;
&lt;p&gt;Ok but where are these enigmatic config files?&lt;/p&gt;
&lt;p&gt;This is a living document we will continue the story toward a resolution and simple quickstart guide!&lt;/p&gt;
&lt;p&gt;Leave comments below!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Copying files between cloud object stores like S3 GCP and Spaces using Boto3</title><link href="https://russell.ballestrini.net/copying-files-between-cloud-object-stores-like-s3-gcp-and-spaces-using-boto3/" rel="alternate"></link><published>2021-05-12T18:50:00-04:00</published><updated>2021-05-12T18:50:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2021-05-12:/copying-files-between-cloud-object-stores-like-s3-gcp-and-spaces-using-boto3/</id><summary type="html">&lt;p&gt;tl;dr if you just want something like &lt;tt class="docutils literal"&gt;aws s3 cp&lt;/tt&gt; cli, try &lt;tt class="docutils literal"&gt;gsutil rsync&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;At work one key item of team's sprint is properly utilizing and securing Google Cloud Platform (GCP).&lt;/p&gt;
&lt;p&gt;For one of my projects, I'm learning GCP's Object Store called Google Cloud Storage (GCS).&lt;/p&gt;
&lt;p&gt;I have prior …&lt;/p&gt;</summary><content type="html">&lt;p&gt;tl;dr if you just want something like &lt;tt class="docutils literal"&gt;aws s3 cp&lt;/tt&gt; cli, try &lt;tt class="docutils literal"&gt;gsutil rsync&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;At work one key item of team's sprint is properly utilizing and securing Google Cloud Platform (GCP).&lt;/p&gt;
&lt;p&gt;For one of my projects, I'm learning GCP's Object Store called Google Cloud Storage (GCS).&lt;/p&gt;
&lt;p&gt;I have prior experience using AWS S3 and Digital Ocean Spaces via &lt;tt class="docutils literal"&gt;aws s3&lt;/tt&gt; CLI and &lt;tt class="docutils literal"&gt;boto3&lt;/tt&gt; and I've even blogged in the past about some pretty advanced topics, like &lt;a class="reference external" href="/pre-signed-get-and-post-for-digital-ocean-spaces/"&gt;Pre-signed GET and POST requests&lt;/a&gt;. Pre-signing allows the client to perform specific actions on specific private objects, which is great for short time-to-live link downloads or direct browser uploads into the object store, all without compromising data security (hampering link sharing and preventing replay attacks)&lt;/p&gt;
&lt;p&gt;At work, learning how to utilize and secure the GCS object store is critical to our teams interopability between clouds. We want to be able to store security artifacts into a single GCS bucket and sync all data to our main AWS S3 bucket as a long term archive.&lt;/p&gt;
&lt;div class="section" id="working-with-gcs-using-boto3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Working with GCS using Boto3&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Given my prior experience with &lt;tt class="docutils literal"&gt;boto3&lt;/tt&gt; I decided to test interoperability between it's &lt;tt class="docutils literal"&gt;s3&lt;/tt&gt; client and GCS.&lt;/p&gt;
&lt;p&gt;For this test I created the following via the GCP Console:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://console.cloud.google.com/iam-admin/iam"&gt;IAM Service Account&lt;/a&gt; (with GCS Editor and GCS Viewer roles)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://console.cloud.google.com/storage/browser"&gt;GCS Bucket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;&lt;a class="reference external" href="https://console.cloud.google.com/storage/settings;tab=interoperabily"&gt;Service Account HMAC&lt;/a&gt; (linked to the new IAM Service Account)&lt;/dt&gt;
&lt;dd&gt;I also set the default GCP project which is important because by default boto3 is not configured to pass the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-amz-project-id&lt;/span&gt;&lt;/tt&gt; HTTP header.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point I have an access key and secret key (&lt;tt class="docutils literal"&gt;hmac&lt;/tt&gt;) which in theory I may pass to my existing code to communicate with GCP in a similar way to how I communicate with S3 or Spaces.&lt;/p&gt;
&lt;p&gt;Armed with this information I ran the following test and it worked:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Reference:&lt;/span&gt;
&lt;span class="c1"&gt;# https://cloud.google.com/storage/docs/migrating#storage-list-objects-s3-python&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_gcs_objects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google_access_key_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;google_access_key_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Lists GCS objects using boto3 SDK&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Create a new client and do the following:&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Change the endpoint URL to use the&lt;/span&gt;
    &lt;span class="c1"&gt;#    Google Cloud Storage XML API endpoint.&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Use Cloud Storage HMAC Credentials.&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;s3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;auto&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://storage.googleapis.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;google_access_key_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;google_access_key_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Call GCS to list objects in bucket_name&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_objects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Print object names&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Objects:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Contents&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you are working with many GCP projects, you'll want to figure out how to configure &lt;tt class="docutils literal"&gt;boto3&lt;/tt&gt; to pass the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;x-amz-project-id&lt;/span&gt;&lt;/tt&gt;. I found a good example reference but have not put it to practice: &lt;a class="reference external" href="https://github.com/boto/boto3/issues/2251"&gt;https://github.com/boto/boto3/issues/2251&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The TL;DR is AWS doesn't have a concept of projects or default projects so this header is something GCP introduced for interopability.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="gcp-iam-first-impressions"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;GCP IAM first impressions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From a surface level look GCP IAM appears less complicated than AWS IAM but also less granular. I'm not sure how I feel at this point but I'm excited to see what the rest of my team uncovers when it comes to permissions, roles, and best practices.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="working-with-gcs-using-a-cli"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Working with GCS using a CLI&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we covered &lt;tt class="docutils literal"&gt;boto3&lt;/tt&gt; (Python) interoperability between object stores, I started looking at some other simple CLI workflows which typically utilize &lt;tt class="docutils literal"&gt;aws s3&lt;/tt&gt;. In the case of GCP the prefered CLI is &lt;tt class="docutils literal"&gt;gsutil&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The subcommand &lt;a class="reference external" href="https://cloud.google.com/storage/docs/gsutil/commands/rsync"&gt;gsutil rsync&lt;/a&gt; in particular caught my eye as a simple way to setup a cross cloud object store synchronization!&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gsutil&lt;span class="w"&gt; &lt;/span&gt;rsync&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;gs://my-gs-bucket&lt;span class="w"&gt; &lt;/span&gt;s3://my-s3-bucket
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For my next test, I'd like to try to setup a cronjob style automation to trigger &lt;tt class="docutils literal"&gt;gsutil rsync&lt;/tt&gt; to copy and sync data from GCP GCS into AWS S3 for our long term security and governance artifacts, likely using a Gitlab CI Pipeline on a schedule.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#working-with-gcs-using-boto3" id="toc-entry-1"&gt;Working with GCS using Boto3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#gcp-iam-first-impressions" id="toc-entry-2"&gt;GCP IAM first impressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#working-with-gcs-using-a-cli" id="toc-entry-3"&gt;Working with GCS using a CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>I tried to install GitLab on TrueNAS and failed</title><link href="https://russell.ballestrini.net/i-tried-to-install-gitlab-on-truenas-and-failed/" rel="alternate"></link><published>2020-10-25T14:28:00-04:00</published><updated>2020-10-25T14:28:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2020-10-25:/i-tried-to-install-gitlab-on-truenas-and-failed/</id><summary type="html">&lt;p&gt;Ok, so a month ago I changed employment and the new company uses GitLab exclusively for centralized code version control system.&lt;/p&gt;
&lt;p&gt;This is my first time using GitLab and my first projects was integrating a &lt;tt class="docutils literal"&gt;dou/cloudmapper&lt;/tt&gt; with a GitLab runner on a schedule.&lt;/p&gt;
&lt;p&gt;After a couple weeks I've learned …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ok, so a month ago I changed employment and the new company uses GitLab exclusively for centralized code version control system.&lt;/p&gt;
&lt;p&gt;This is my first time using GitLab and my first projects was integrating a &lt;tt class="docutils literal"&gt;dou/cloudmapper&lt;/tt&gt; with a GitLab runner on a schedule.&lt;/p&gt;
&lt;p&gt;After a couple weeks I've learned how GitLab runners work and wrote a wrapper to run cloudmapper concurrently so that each accounts is collected in parallel (the basic logic is running docker exec on a custom entrypoint and then backgrounds the task in a bash loop over each account configured in cloudmapper's config file)&lt;/p&gt;
&lt;p&gt;GitLab runners are awesome.&lt;/p&gt;
&lt;p&gt;I've used CircleCI for the better part of 3 years, having lifted the better part of 60 code bases from an internal app called conveyor to CircleCI 1.0 and performing CircleCI 2.0 conversions at Remind.&lt;/p&gt;
&lt;p&gt;CircleCI 2.0 is awesome...&lt;/p&gt;
&lt;p&gt;But GitLab runners are not black box which makes them so powerful.&lt;/p&gt;
&lt;p&gt;Granted not all organizations need or even want this much power, but I'm a nerd and I want it, so I decided I also want to run GitLab at home.&lt;/p&gt;
&lt;p&gt;My first thought was to try to just run GitLab on a VM on my &lt;tt class="docutils literal"&gt;SmartOS&lt;/tt&gt; host called &lt;tt class="docutils literal"&gt;mbison&lt;/tt&gt;, but that's a bit janky of a setup since it's an &lt;tt class="docutils literal"&gt;T420 ThinkPad&lt;/tt&gt; after all. Also I learned the recommended minimum memory footprint of &lt;tt class="docutils literal"&gt;GitLab&lt;/tt&gt; is &lt;tt class="docutils literal"&gt;4G&lt;/tt&gt; so a &lt;tt class="docutils literal"&gt;4 of 16G&lt;/tt&gt; allocation without even discussing a separate KVM for the runner. It feels weird that a single rails application would need _that_ much memory.&lt;/p&gt;
&lt;p&gt;Anyways, I have a FreeNAS &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;FreeNAS-11.3-U5&lt;/span&gt;&lt;/tt&gt; host at my house with &lt;tt class="docutils literal"&gt;46G&lt;/tt&gt; of memory and a bunch of idle cores, So I wondered if they have a plug-in for GitLab... I'm sure they do by now.&lt;/p&gt;
&lt;p&gt;After a bit of research I found that &lt;tt class="docutils literal"&gt;iXsystems&lt;/tt&gt; renamed the &lt;tt class="docutils literal"&gt;FreeNAS&lt;/tt&gt; codebase to &lt;tt class="docutils literal"&gt;TrueNAS&lt;/tt&gt; and version &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;TrueNAS-12.0-RELEASE&lt;/span&gt;&lt;/tt&gt; has a GitLab plugin! Sweet!&lt;/p&gt;
&lt;p&gt;Thankfully I remembered that back in 2019 I replaced my FreeNAS USB boot device with an SSD so it should be a safe operation to attempt a TrueNAS upgrade on a Saturday morning since I'd have the weekend to try to recover if things went sour. Fun side story, I ran into trouble last year trying to upgrade FreeNAS train because I didn't have room for the upgrade because I was using a USB drive as the boot disk and it had like 5 previous version on the 8G file system.&lt;/p&gt;
&lt;p&gt;The kind people in the freenode &lt;tt class="docutils literal"&gt;#FreeNAS&lt;/tt&gt; channel helped me switch back to an older version using SSH and told me I should not boot from a USB drive which was a thing I picked up from back in the FreeNAS 6 or 7 days. Boy were they correct, I moved the OS to an SSD and the server boots _WAY_ faster now, plus I have more capacity to try new versions.&lt;/p&gt;
&lt;p&gt;Anyways, here is a fun command to determine when a ZFS pool was created, I used this to get the exact date and time I moved from USB to SSD for this blog post:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;root@guile&lt;span class="o"&gt;[&lt;/span&gt;~&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;# zpool history -i freenas-boot | grep &amp;quot;create pool&amp;quot;&lt;/span&gt;

&lt;span class="m"&gt;2019&lt;/span&gt;-11-12.13:00:30&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;txg:5&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;pool&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;software&lt;span class="w"&gt; &lt;/span&gt;version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;/5&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uts&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;.2-STABLE&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1102500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;amd64
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So weilding this confidence, I switched FreeNAS trains in the UI and clicked upgrade, following the prompts to create a backup of my NAS config.
After the server rebooted, FreeNAS was now TrueNAS. I logged into the web UI to look around and make sure my data was still there.&lt;/p&gt;
&lt;p&gt;Everything seemed fine, so I went to the plug-in tab and attempted to install GitLab. That failed with an error.&lt;/p&gt;
&lt;p&gt;So I decided oh well, I'll try spinning up an Ubuntu VM on the &lt;tt class="docutils literal"&gt;TrueNAS&lt;/tt&gt; box, apparently &lt;tt class="docutils literal"&gt;FreeBSD&lt;/tt&gt; uses &lt;tt class="docutils literal"&gt;bhyve&lt;/tt&gt; for that.&lt;/p&gt;
&lt;p&gt;Attempting to start the VM does nothing in the UI, it appears like it will start but then the page refreshes and the VM is stopped.&lt;/p&gt;
&lt;p&gt;I did eventually find the error message, by running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/var/log/libvirt/*/*
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/usr/sbin/bhyve&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,sockets&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,cores&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,threads&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:0,hostbridge&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;:0,ahci,cd:/mnt/downloads/iso/ubuntu-20.04.1-live-server-amd64.iso&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;:0,xhci,tablet&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;:0,ahci&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;:0,virtio-net,tap1,mac&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:a0:98:4f:98:13&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;:0,virtio-blk,/dev/zvol/downloads/gitlab-vcrg2z&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;,lpc&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;com1,/dev/nmdm1A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;,fbuf,vncserver,tcp&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0:48009,w&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;,h&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1_gitlab
&lt;span class="m"&gt;25&lt;/span&gt;/10/2020&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:49:58&lt;span class="w"&gt; &lt;/span&gt;Listening&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VNC&lt;span class="w"&gt; &lt;/span&gt;connections&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;TCP&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48009&lt;/span&gt;
&lt;span class="m"&gt;25&lt;/span&gt;/10/2020&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:49:58&lt;span class="w"&gt; &lt;/span&gt;Listening&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;VNC&lt;span class="w"&gt; &lt;/span&gt;connections&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;TCP6&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48009&lt;/span&gt;
ROM&lt;span class="w"&gt; &lt;/span&gt;boot&lt;span class="w"&gt; &lt;/span&gt;failed:&lt;span class="w"&gt; &lt;/span&gt;unrestricted&lt;span class="w"&gt; &lt;/span&gt;guest&lt;span class="w"&gt; &lt;/span&gt;capability&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;available
fbuf&lt;span class="w"&gt; &lt;/span&gt;frame&lt;span class="w"&gt; &lt;/span&gt;buffer&lt;span class="w"&gt; &lt;/span&gt;base:&lt;span class="w"&gt; &lt;/span&gt;0x941e00000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;sz&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16777216&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The important part is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ROM boot failed: unrestricted guest capability not available
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It seems if you ask &lt;tt class="docutils literal"&gt;bhyve&lt;/tt&gt; to use the UEFI bootloader it will require UG support in the Intel CPU : (&lt;/p&gt;
&lt;p&gt;I attempted to switch to the grub bootloader but in that case I get the following error message:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;File &amp;quot;/usr/local/lib/python3.8/site-packages/middlewared/plugins/vm.py&amp;quot;, line 1414, in do_update
  raise verrors
middlewared.service_exception.ValidationErrors: [EINVAL] vm_update.devices.3.dtype: VNC only works with UEFI bootloader.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and the &lt;tt class="docutils literal"&gt;TrueNAS&lt;/tt&gt; UI does not let you disable VNC on VMs at this point...&lt;/p&gt;
&lt;p&gt;Another limitation of a missing UG CPU flag is &lt;tt class="docutils literal"&gt;bhyve&lt;/tt&gt; may only allocate 1 core, and 1 vcpu to a VM.&lt;/p&gt;
&lt;p&gt;Anyways I'm out of luck, I can't use the plugin which uses jails due to a strange error and no other hints as to the issue and &lt;tt class="docutils literal"&gt;byhve&lt;/tt&gt; / &lt;tt class="docutils literal"&gt;TrueNAS&lt;/tt&gt; not supporting VMs for my CPU...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt; I recently upgraded my TrueNAS server hardware to an old &lt;tt class="docutils literal"&gt;Dell PowerEdge R720 R720xd&lt;/tt&gt; with two Intel(R) Xeon(R) CPU E5-2670 &amp;#64; 2.60GHz each with 8 Physical Cores and 128G of ECC Memory! Booyah!&lt;/p&gt;
&lt;p&gt;I was able to flash the included Dell H710 Mini RAID card into IT mode for ZFS support using the awesome guides and tools found here: &lt;a class="reference external" href="https://fohdeesha.com/docs/perc/"&gt;https://fohdeesha.com/docs/perc/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I now have GitLab running on a &lt;tt class="docutils literal"&gt;byhve&lt;/tt&gt; instance and also a dedicated runner instance. It's working great and really fun to have this extremely powerful gear in my home lab!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>WeeChat on-boot in a tmux session</title><link href="https://russell.ballestrini.net/weechat-on-boot-in-a-tmux-session/" rel="alternate"></link><published>2020-09-15T13:32:00-04:00</published><updated>2020-09-15T13:32:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2020-09-15:/weechat-on-boot-in-a-tmux-session/</id><summary type="html">&lt;p&gt;Chronicles of a washed up systems administrator.&lt;/p&gt;
&lt;p&gt;In the basement, M. Bison (&lt;tt class="docutils literal"&gt;mbision.foxhop.net.&lt;/tt&gt;), a T430 with a cracked screen runs quietly with his lid closed, acting as a SmartOS hypervisor to 3 Solaris derived zones and 4 KVM ubuntu guests.&lt;/p&gt;
&lt;p&gt;One of these guests is the oldest of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Chronicles of a washed up systems administrator.&lt;/p&gt;
&lt;p&gt;In the basement, M. Bison (&lt;tt class="docutils literal"&gt;mbision.foxhop.net.&lt;/tt&gt;), a T430 with a cracked screen runs quietly with his lid closed, acting as a SmartOS hypervisor to 3 Solaris derived zones and 4 KVM ubuntu guests.&lt;/p&gt;
&lt;p&gt;One of these guests is the oldest of the bunch and his name is Akuma (&lt;tt class="docutils literal"&gt;akuma.foxhop.net.&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Akuma and I go way back, over 13 years now. At one point Akuma was a physical machine, who ran FreeBSD and then later Ubuntu 6.04 with it's own guest kernel virtual machines.&lt;/p&gt;
&lt;p&gt;Akuma has been &amp;quot;home base&amp;quot; for as long as I remember. A place where shit got done. A place to perform operations. A place to &amp;quot;jump&amp;quot; from with SSH.&lt;/p&gt;
&lt;p&gt;It makes sense that Akuma would evolve and gain many roles over the years.&lt;/p&gt;
&lt;p&gt;Akuma performs DNS caching and forwarding for my LAN, it acts as the internal authoritative Nameserver for &lt;tt class="docutils literal"&gt;foxhop.net.&lt;/tt&gt;, and is the Salt Master for all my hosts both internally (hosted at my house) and externally in the &amp;quot;cloud&amp;quot;.&lt;/p&gt;
&lt;p&gt;Akuma uses &lt;tt class="docutils literal"&gt;tmux&lt;/tt&gt; with default settings to allow for a &amp;quot;remote shell&amp;quot; vibe.&lt;/p&gt;
&lt;p&gt;This allows me attach to my session running on Akuma from anywhere in the world.&lt;/p&gt;
&lt;p&gt;Only problem is, Akuma reboots weekly when M. Bison &lt;a class="reference external" href="/backup-all-virtual-machines-on-a-smartos-hypervisor-with-smart-back-sh/"&gt;triggers a complete backup of all guests&lt;/a&gt;.  Backups are stored on Guile, the FreeNAS server, uncompressed since disk space is cheap and this process allows for downtime, however I try to limit it.&lt;/p&gt;
&lt;p&gt;Since Akuma reboots each week, I needed a way to create tmux sessions on boot if I wanted to continue to use it as my &amp;quot;home base&amp;quot;, and I did so using SaltStack:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;create-tmux-session-on-boot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;cron.present&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tmux&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;boot&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/bin/bash /home/fox/new-tmux&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;create-tmux-session-on-boot&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;special&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@reboot&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fox&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;require&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;fox&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This salt state results in the following &lt;tt class="docutils literal"&gt;crontab &lt;span class="pre"&gt;-l&lt;/span&gt;&lt;/tt&gt; entry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# create a new tmux session on system boot SALT_CRON_IDENTIFIER:create-tmux-session-on-boot&lt;/span&gt;
@reboot&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;/home/fox/new-tmux
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And of course I what tutorial wouldn't be complete without a script! (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/home/fox/new-tmux&lt;/span&gt;&lt;/tt&gt;) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;# The -A flag makes new-session behave like attach-session if session-name&lt;/span&gt;
&lt;span class="c1"&gt;# already exists; in this case, -D behaves like -d to attach-session.&lt;/span&gt;
tmux&lt;span class="w"&gt; &lt;/span&gt;new-session&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;remote-shell&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;weechat-curses&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# also create a couple windows to use as &amp;quot;remote shells&amp;quot;.&lt;/span&gt;
tmux&lt;span class="w"&gt; &lt;/span&gt;new-window
tmux&lt;span class="w"&gt; &lt;/span&gt;new-window
&lt;span class="c1"&gt;# finally attach to the new session!&lt;/span&gt;
tmux&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;remote-shell&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thanks for joining me on this escape into the world of computers.&lt;/p&gt;
&lt;p&gt;This website is hosted on Ryu (&lt;tt class="docutils literal"&gt;ryu.foxhop.net&lt;/tt&gt;) another Ubuntu guest running on M. Bison, humming away downstairs on that T430 with a cracked screen, but thats a story for another time.&lt;/p&gt;
&lt;p&gt;Please feel free to comment below if you have suggestions for this post.&lt;/p&gt;
</content><category term="misc"></category><category term="automation"></category></entry><entry><title>Dreaming of unlimited home computer storage capacity</title><link href="https://russell.ballestrini.net/dreaming-of-unlimited-home-computer-storage-capacity/" rel="alternate"></link><published>2019-12-15T09:52:00-05:00</published><updated>2019-12-15T09:52:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-12-15:/dreaming-of-unlimited-home-computer-storage-capacity/</id><summary type="html">&lt;p&gt;Unlimited computer storage capacity is a common science fiction trope and fun thought experiment.&lt;/p&gt;
&lt;p&gt;What would be possible if we could potentially store near infinite data at your house in a normal sized desktop computer?&lt;/p&gt;
&lt;p&gt;Even better using today's tech, what ideas could we dream up?&lt;/p&gt;
&lt;p&gt;This post will explore …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Unlimited computer storage capacity is a common science fiction trope and fun thought experiment.&lt;/p&gt;
&lt;p&gt;What would be possible if we could potentially store near infinite data at your house in a normal sized desktop computer?&lt;/p&gt;
&lt;p&gt;Even better using today's tech, what ideas could we dream up?&lt;/p&gt;
&lt;p&gt;This post will explore ideas as we surf the bleeding edge of home computing.&lt;/p&gt;
&lt;div class="section" id="idea-1"&gt;
&lt;h2&gt;Idea 1&lt;/h2&gt;
&lt;p&gt;Live stream everything in your life in HD and store it locally at your house as a hyper real augment reality. A person could store and recall any event that he or she has witnessed using a headcam with wifi syncing capabilities.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One of the many new features within the iPhone 6s is the ability for the 12MP camera on the back to record 4K video. It’s a big selling point for Apple, but what about storage space?&lt;/p&gt;
&lt;p&gt;According to a video published by MKBHD, just how much a one-minute long video shot in 4K will take up in your iPhone 6s’ storage has been discovered. According to the screenshot captured by Ryan Jones (&amp;#64;rjones) of the hands-on video, a one-minute video captured at 720p HD at 30fps will take up 60MB of space on the iPhone 6s. At 1080p HD and 30fps, it’s 130MB of space. A 1080p HD video at 60fps will take up 200MB of space. And, finally, the 4K video at 30fps will take up 375MB of space.&lt;/p&gt;
&lt;p&gt;reference: &lt;a class="reference external" href="http://www.iphonehacks.com/2015/09/iphone-6s-4k-video-space.html"&gt;http://www.iphonehacks.com/2015/09/iphone-6s-4k-video-space.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we were streaming at 4k for 24 hours, it would be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 60 * 24 * 375
540000
&lt;/pre&gt;
&lt;p&gt;540,000MB or 540GB or .54TB per day of streaming everything.&lt;/p&gt;
&lt;p&gt;A years worth of 4k streaming from some guys headcam would consume:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 365.25 * .54
197.235
&lt;/pre&gt;
&lt;p&gt;or 197TB worth of capacity.&lt;/p&gt;
&lt;p&gt;With today's technology and hardware available as home computing, including NAS systems, streaming 4k of a persons headcam would be possible... but not really practical without advancements in hard drive density.&lt;/p&gt;
&lt;p&gt;A 6TB drive is $100, and by my math, we would need 34 drives (not really even counting for redundancy):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 197.235 / 6
32.8725
&lt;/pre&gt;
&lt;p&gt;So the cost of a years worth a capacity would be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
(197.235 / 6) * 100
3287.25
&lt;/pre&gt;
&lt;p&gt;or $3,287 for 34 x 6TB drives.&lt;/p&gt;
&lt;p&gt;It would fit in an oversized NAS, just barely... in another section I'll figure out what hard drive density is needed to make 4k video at 30fps more practical.&lt;/p&gt;
&lt;p&gt;Ok so, 4k streaming is currently not practical, so how about 1080p HD at 30fps? Could we do that with today's home tech?&lt;/p&gt;
&lt;p&gt;One minute of 1080p HD at 30fps is 130MB of space.&lt;/p&gt;
&lt;p&gt;If we were streaming at 1080p at 30fps for 24 hours, it would be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 60 * 24 * 130
187200
&lt;/pre&gt;
&lt;p&gt;187,200MB or 187GB or .187TB per day.&lt;/p&gt;
&lt;p&gt;A years worth of non-stop 1080P streaming at 30fps off some guys headcam would consume:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 365.25 * .184
67.235
&lt;/pre&gt;
&lt;p&gt;Or about 68TB per year (rounded up).&lt;/p&gt;
&lt;p&gt;Like before a 6TB drive is $100, and by my math, we would need 12 drives (not really even counting for redundancy):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 67.206 / 6
11.201
&lt;/pre&gt;
&lt;p&gt;so the cost of a years worth a capacity would be:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; 12 * 100
1200
&lt;/pre&gt;
&lt;p&gt;or $1,200 for 12 x 6TB drives.&lt;/p&gt;
&lt;p&gt;12 drives should easily fit in a mid-range home NAS. That said, a mid-range home NAS holding 12 drives is a clunky machine for most home owners.&lt;/p&gt;
&lt;p&gt;It might be possible, but likely not practical without a support plan, in home repairs, and on-going maintenance.&lt;/p&gt;
&lt;p&gt;It would likely require having a home NAS specialist in every region to offer up white gloved support for anyone who would be willing to pay for it.&lt;/p&gt;
&lt;p&gt;How could we make this even easier with today's tech?&lt;/p&gt;
&lt;p&gt;We could lower the quality even more...&lt;/p&gt;
&lt;p&gt;Streaming 720P at 30fps you would need 36T per year or about 6 x 6TB drives for about $600 plus a NAS.&lt;/p&gt;
&lt;p&gt;So at this time, if seems practical and economical to stream 720P at 30fps every minute of the year.&lt;/p&gt;
&lt;p&gt;What would we need to build to make all this data useful?&lt;/p&gt;
&lt;p&gt;Well we would need a way to index all this footage, and likely edit out useless footage.&lt;/p&gt;
&lt;p&gt;We would need at least two headcams with wifi sync, so that we could have one charging while we use the other.&lt;/p&gt;
&lt;p&gt;We would need software to sync the footage to the NAS, like &lt;tt class="docutils literal"&gt;syncthing&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Besides that, all we need is some software to host the videos, edit, and index them to be searchable.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Idea"></category></entry><entry><title>Unity Collab not working on Fedora because of an invalid CA certificate path</title><link href="https://russell.ballestrini.net/unity-collab-not-working-on-fedora-because-of-invalid-ca-certificate-path/" rel="alternate"></link><published>2019-11-08T12:19:00-05:00</published><updated>2019-11-08T12:19:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-11-08:/unity-collab-not-working-on-fedora-because-of-invalid-ca-certificate-path/</id><summary type="html">&lt;p&gt;This issue blocked me for about two days and prevented me from collaborating with another engineer at &lt;a class="reference external" href="https://www.gumyum.com"&gt;gumyum co-operative video game studio&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="An image showing that Unity Collab is not linked" class="align-center" src="/uploads/2019/unity-collab-fedora-ca-certificate-error-no-link.png" /&gt;
&lt;p&gt;The other engineer's environment was Windows and he was able to interact with collab and our shared project owned by our shared org. My environment was Fedora 30 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This issue blocked me for about two days and prevented me from collaborating with another engineer at &lt;a class="reference external" href="https://www.gumyum.com"&gt;gumyum co-operative video game studio&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="An image showing that Unity Collab is not linked" class="align-center" src="/uploads/2019/unity-collab-fedora-ca-certificate-error-no-link.png" /&gt;
&lt;p&gt;The other engineer's environment was Windows and he was able to interact with collab and our shared project owned by our shared org. My environment was Fedora 30 and was not working with collab, however I was able to download the project files.&lt;/p&gt;
&lt;img alt="An image showing that Unity is looking for the CA Certificates in the wrong location" class="align-center" src="/uploads/2019/unity-collab-fedora-ca-certificate-error-curl.png" /&gt;
&lt;p&gt;The issue?&lt;/p&gt;
&lt;p&gt;Apparently Unity naively assumes that a Linux system's copy of the root CA certificates is located &lt;cite&gt;/etc/ssl/certs/ca-certificates.crt&lt;/cite&gt;. This is the default location on Debian and Ubuntu based systems but not Fedora or Redhat, which uses the &lt;cite&gt;/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem&lt;/cite&gt; file.&lt;/p&gt;
&lt;p&gt;The work around is simple, create a soft link between the paths:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo ln -s /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /etc/ssl/certs/ca-certificates.crt
&lt;/pre&gt;
&lt;img alt="An image showing that Unity Collab is working again!" class="align-center" src="/uploads/2019/unity-collab-fedora-ca-certificate-error-fixed.png" /&gt;
&lt;p&gt;Please come back again soon to learn more about the games we are building over here at &lt;cite&gt;gumyum&lt;/cite&gt;!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Games"></category><category term="gumyum"></category></entry><entry><title>How to fallback to an older FreeNAS version on update failure</title><link href="https://russell.ballestrini.net/how-to-fallback-to-an-older-freenas-version-on-update-failure/" rel="alternate"></link><published>2019-10-31T17:06:00-04:00</published><updated>2019-10-31T17:06:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-10-31:/how-to-fallback-to-an-older-freenas-version-on-update-failure/</id><summary type="html">&lt;p&gt;try to log in as root locally, I needed to press &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ctrl-alt-f7&lt;/span&gt;&lt;/tt&gt; to get to a log in prompt.&lt;/p&gt;
&lt;p&gt;Once logged in as root, I used the &lt;tt class="docutils literal"&gt;beadm&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;For example, &lt;tt class="docutils literal"&gt;beadm list&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;beadm activate &lt;span class="pre"&gt;&amp;lt;old-version&amp;gt;&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Finally &lt;tt class="docutils literal"&gt;reboot&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I followed this process and then got my FreeNAS server to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;try to log in as root locally, I needed to press &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ctrl-alt-f7&lt;/span&gt;&lt;/tt&gt; to get to a log in prompt.&lt;/p&gt;
&lt;p&gt;Once logged in as root, I used the &lt;tt class="docutils literal"&gt;beadm&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;For example, &lt;tt class="docutils literal"&gt;beadm list&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;beadm activate &lt;span class="pre"&gt;&amp;lt;old-version&amp;gt;&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Finally &lt;tt class="docutils literal"&gt;reboot&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I followed this process and then got my FreeNAS server to boot again.&lt;/p&gt;
&lt;p&gt;From the GUI I was able to create myself a config backup.&lt;/p&gt;
&lt;p&gt;I then burned a new FreeNAS CD and used that to install a fresh new install on my USB drive and then once that booted I used the GUI to restore my config.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Installing Unity on Linux</title><link href="https://russell.ballestrini.net/installing-unity-on-linux/" rel="alternate"></link><published>2019-09-25T18:55:00-04:00</published><updated>2019-09-25T18:55:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-09-25:/installing-unity-on-linux/</id><summary type="html">&lt;p&gt;Russell, why are you installing Unity? I thought you were an open source developer and &lt;a class="reference external" href="/yuletide-trains-and-homegrown-video-games/"&gt;homegrown video game engine builder&lt;/a&gt;?!&lt;/p&gt;
&lt;p&gt;&lt;img alt="gumyum logo" src="/uploads/2010/12/gumyumgameslogo.png" /&gt;&lt;/p&gt;
&lt;p&gt;The TL;DR we are starting &lt;a class="reference external" href="https://gumyum.com"&gt;a video game co-operative called gumyum&lt;/a&gt;. The basic idea is to build a large video game company owned completely by members. There will be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Russell, why are you installing Unity? I thought you were an open source developer and &lt;a class="reference external" href="/yuletide-trains-and-homegrown-video-games/"&gt;homegrown video game engine builder&lt;/a&gt;?!&lt;/p&gt;
&lt;p&gt;&lt;img alt="gumyum logo" src="/uploads/2010/12/gumyumgameslogo.png" /&gt;&lt;/p&gt;
&lt;p&gt;The TL;DR we are starting &lt;a class="reference external" href="https://gumyum.com"&gt;a video game co-operative called gumyum&lt;/a&gt;. The basic idea is to build a large video game company owned completely by members. There will be no employees and most revenue after paying taxes, operational expenses, and marketing, will be divided among members.&lt;/p&gt;
&lt;p&gt;We are actively looking for members, specifically engineers who want to build games. We already have a distributed team of 4 artists and 2 engineers who meet on Slack as we progress toward delivering our first two games.&lt;/p&gt;
&lt;p&gt;Adopting Unity, at least for our first couple games, will lower the barrier of entry for other engineers when joining the co-operative.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installing Unity On Linux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To get started first we will create a Unity Account and install the &lt;tt class="docutils literal"&gt;Unity Hub&lt;/tt&gt;.
You need both in order to aquire a free licence for personal use.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;Unity Hub&lt;/tt&gt; software will help you download various Unity versions, manage your licence, and manage game projects.&lt;/p&gt;
&lt;p&gt;You should go to the Unity website and created an account.&lt;/p&gt;
&lt;p&gt;I found the Linux &lt;tt class="docutils literal"&gt;Unity Hub&lt;/tt&gt; installer in &lt;a class="reference external" href="https://forum.unity.com/threads/unity-hub-v-1-0-0-is-now-available.555547/"&gt;this thread&lt;/a&gt; and downloaded it to my &lt;tt class="docutils literal"&gt;~/Downloads&lt;/tt&gt; directory.&lt;/p&gt;
&lt;p&gt;Next I opened a terminal and made the file executable, and executed it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/Downloads
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;755&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;UnityHub.AppImage
./UnityHub.AppImage
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From there I needed to sign in and request a licence. I chose the free option since at this point we have a $0 revenue project.&lt;/p&gt;
&lt;p&gt;On the &lt;tt class="docutils literal"&gt;Installs&lt;/tt&gt; tab in &lt;tt class="docutils literal"&gt;Unity Hub&lt;/tt&gt; you may download various stable and LTS (long-time-supported) versions of Unity. I suggest using one of these stable versions, I accidentally grabbed a non-stable one and all I had was pink screens inside of Unity!&lt;/p&gt;
&lt;p&gt;Anyways, now you may start a Unity project. &lt;tt class="docutils literal"&gt;Unity Hub&lt;/tt&gt; seems to store files locally and also allows you to sync to the cloud. This enables easy collaboration on the same project.&lt;/p&gt;
&lt;p&gt;Thats all for now, please check back for more posts about gumyum!&lt;/p&gt;
&lt;img alt="" src="/uploads/2019/unity-hub-gumyum.png" /&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Games"></category><category term="gumyum"></category></entry><entry><title>Pre-signed GET and POST for Digital Ocean Spaces</title><link href="https://russell.ballestrini.net/pre-signed-get-and-post-for-digital-ocean-spaces/" rel="alternate"></link><published>2019-07-12T16:01:00-04:00</published><updated>2019-07-12T16:01:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-07-12:/pre-signed-get-and-post-for-digital-ocean-spaces/</id><summary type="html">&lt;p&gt;A pre-signed request grants a semi-trusted user temporary access to a private resource.&lt;/p&gt;
&lt;p&gt;Let's unpack that statement ...&lt;/p&gt;
&lt;p&gt;Pre-signed means, we bless a specific action on a specific private resource
for a short duration of time.&lt;/p&gt;
&lt;p&gt;Semi-trusted means, we have authenticated the user, but we don't trust them
to have full …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A pre-signed request grants a semi-trusted user temporary access to a private resource.&lt;/p&gt;
&lt;p&gt;Let's unpack that statement ...&lt;/p&gt;
&lt;p&gt;Pre-signed means, we bless a specific action on a specific private resource
for a short duration of time.&lt;/p&gt;
&lt;p&gt;Semi-trusted means, we have authenticated the user, but we don't trust them
to have full access to our private resources.&lt;/p&gt;
&lt;p&gt;That still seems a bit generic ...&lt;/p&gt;
&lt;p&gt;No worries, our next example will appear more concrete:&lt;/p&gt;
&lt;p&gt;Today we will pre-sign HTTP &lt;tt class="docutils literal"&gt;POST&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;GET&lt;/tt&gt; requests to grant a
semi-trusted user temporary access to objects in a private Digital Ocean Space.&lt;/p&gt;
&lt;p&gt;Pre-signing allows the client to perform specific actions on specific private
objects while still protecting us from link sharing and replay attacks.&lt;/p&gt;
&lt;p&gt;The following examples use Python (Boto3), and Curl.&lt;/p&gt;
&lt;p&gt;Before we start, you should reference the official &lt;a class="reference external" href="https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-presigned-urls.html"&gt;Boto3 Presigned Urls&lt;/a&gt; documentation.&lt;/p&gt;
&lt;div class="section" id="pre-signed-get-or-download"&gt;
&lt;h2&gt;Pre-signed GET or Download&lt;/h2&gt;
&lt;p&gt;Allow a semi-trusted user the ability to download a specific file named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;my-object.zip&lt;/span&gt;&lt;/tt&gt;
from a private Digital Ocean Space named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;example-bucket&lt;/span&gt;&lt;/tt&gt; for a short duration of &lt;tt class="docutils literal"&gt;30&lt;/tt&gt; seconds.&lt;/p&gt;
&lt;p&gt;Save this code into a file named &lt;tt class="docutils literal"&gt;pre_sign_get_test.py&lt;/tt&gt; and run it with &lt;tt class="docutils literal"&gt;python pre_sign_get_test.py&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The result will be a URL that you may redirect a user to. For testing you should be able to load the URL into a web browser and download the file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;botocore.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize an S3 session/client to talk to DigitalOcean Spaces.&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure the region you created the space in.&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nyc3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure the region you created the space in.&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://nyc3.digitaloceanspaces.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure your access key (generate this on the dashboard).&lt;/span&gt;
    &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOXXXXXXXXXXXXXXXXXX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure your secret key (generate this on the dashboard).&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOO/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;signed_get_object_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;get_object&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example-bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-object.zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# print the pre-signed GET request URL for downloading the object.&lt;/span&gt;
&lt;span class="c1"&gt;# You may redirect the user to this URL when they click a &amp;quot;download&amp;quot; button.&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signed_get_object_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="pre-signed-post-or-upload"&gt;
&lt;h2&gt;Pre-signed POST or Upload&lt;/h2&gt;
&lt;p&gt;Allow a semi-trusted user the ability to upload a specific file named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;my-object2.zip&lt;/span&gt;&lt;/tt&gt;
into a private Digital Ocean Space named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;example-bucket&lt;/span&gt;&lt;/tt&gt; for a short duration of &lt;tt class="docutils literal"&gt;60&lt;/tt&gt; seconds.&lt;/p&gt;
&lt;p&gt;Save this code into a file named &lt;tt class="docutils literal"&gt;pre_sign_post_test.py&lt;/tt&gt; and run it with &lt;tt class="docutils literal"&gt;python pre_sign_get_test.py&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The result will be a &lt;tt class="docutils literal"&gt;cURL&lt;/tt&gt; command that you may run on your console for testing.&lt;/p&gt;
&lt;p&gt;In a real situation, you would want to pass these parameters to the client and let it perform the authenticated upload.
The file never hits your server and is uploaded directly to the private Space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT:&lt;/strong&gt; parameter order matters! The &lt;tt class="docutils literal"&gt;file&lt;/tt&gt; parameter must be last.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;botocore.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize an S3 session/client to talk to DigitalOcean Spaces.&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;s3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure the region you created the space in.&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nyc3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure the region you created the space in.&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://nyc3.digitaloceanspaces.com&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure your access key (generate this on the dashboard).&lt;/span&gt;
    &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOXXXXXXXXXXXXXXXXXX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# configure your secret key (generate this on the dashboard).&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOO/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;signed_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;example-bucket&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;my-object2.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signed_post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fields&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--form &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;curl &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; --form &amp;quot;file=@my-object2.zip;filename=my-object2.zip&amp;quot; &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;signed_post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Hybrid Hot Water Heater Saves 69 Percent On Energy Consumption</title><link href="https://russell.ballestrini.net/hybrid-hot-water-heater-saves-69-percent-on-energy-consumption/" rel="alternate"></link><published>2019-02-05T09:28:00-05:00</published><updated>2019-02-05T09:28:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-02-05:/hybrid-hot-water-heater-saves-69-percent-on-energy-consumption/</id><summary type="html">&lt;p&gt;&lt;em&gt;Disclosure: This post contains affiliate links.&lt;/em&gt; See &lt;a class="reference external" href="/disclosures-and-terms/"&gt;full disclosure page here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Exactly one year ago I fufilled a &lt;a class="reference external" href="/fulfilling-childhood-dreams-solar/"&gt;longtime childhood dream&lt;/a&gt; when I invested in roof top solar on my house in Eastern Connecticut (Zone 6b).&lt;/p&gt;
&lt;p&gt;Over the past 12 months, I have religiously tracked my families energy consumption using …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Disclosure: This post contains affiliate links.&lt;/em&gt; See &lt;a class="reference external" href="/disclosures-and-terms/"&gt;full disclosure page here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Exactly one year ago I fufilled a &lt;a class="reference external" href="/fulfilling-childhood-dreams-solar/"&gt;longtime childhood dream&lt;/a&gt; when I invested in roof top solar on my house in Eastern Connecticut (Zone 6b).&lt;/p&gt;
&lt;p&gt;Over the past 12 months, I have religiously tracked my families energy consumption using both a &amp;quot;&lt;a class="reference external" href="https://www.amazon.com/gp/product/B00009MDBU/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B00009MDBU&amp;amp;linkCode=as2&amp;amp;tag=russellball0b-20&amp;amp;linkId=b3410667dcccb96e343e7cda77ff46ff"&gt;Kill A Watt&lt;/a&gt;&amp;quot; and a &lt;a class="reference external" href="https://www.amazon.com/gp/product/B015IY0Z3E/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B015IY0Z3E&amp;amp;linkCode=as2&amp;amp;tag=russellball0b-20&amp;amp;linkId=727da547a2b0a22fa53016191c2cf313"&gt;Curb&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Along the way, I have found my largest energy consumer (abuser).&lt;/p&gt;
&lt;p&gt;Heating water.&lt;/p&gt;
&lt;p&gt;My traditional electric water heater had a monthly operating cost of $85. Worst yet, during some winter months, 100% of my solar production was soaked up by heating water!&lt;/p&gt;
&lt;p&gt;I demanded a better way!&lt;/p&gt;
&lt;p&gt;... and I found one ...&lt;/p&gt;
&lt;div class="section" id="hybrid-electric-hot-water-heat-pump"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Hybrid Electric Hot Water Heat Pump&lt;/a&gt;&lt;/h2&gt;
&lt;img alt="AO Smith Hybrid Hot Water Heater" class="align-right" src="/uploads/2019/ao-smith-hybrid-hot-water-heater.jpg" style="width: 300px;" /&gt;
&lt;p&gt;My new unit (&lt;a class="reference external" href="https://www.amazon.com/gp/product/B079RCGK12/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B079RCGK12&amp;amp;linkCode=as2&amp;amp;tag=russellball0b-20&amp;amp;linkId=7590d68023bc0d6b244587826aea587e"&gt;A.O. Smith 50 Gallon Voltex Hybrid Electric Heat Pump&lt;/a&gt;) transfers ambient heat from the air into water.&lt;/p&gt;
&lt;p&gt;The technology is relatively old, basically an air conditioner or refrigerator run in reverse.
Instead of dumping the cold into the tank, it dumps hot and as a by-product produces cold air.&lt;/p&gt;
&lt;p&gt;Yes that's right, this unit will suppliment my summer cooling because it will dump cold air while it produces hot water. The heat pump also serves as a dehumidifier, so I no longer run one of those!&lt;/p&gt;
&lt;p&gt;The install was mostly identical to a normal water heater, however there are a couple extra requirements:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The unit needs at least 400 square feet to pull ambient air&lt;/li&gt;
&lt;li&gt;The unit produces condensate, similar to a dehumidifier or air conditioner, which needs to drain somewhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally the heat pump fan produces a bit of sound, not terrible, and seems quiet to me.&lt;/p&gt;
&lt;p&gt;If you have a traditional electric water heater, you meet the extra requirements, and you consider yourself an environmentalist, you need to get a hybrid water heater.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="finances"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Finances&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These units currently run between $1200 - $1500 but I'll break down the complete finances of my project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;+ $1,149 purchase price
+    $65 delivery
+   $100 pipes and fittings
-   $300 instant rebate
-   $200 mail-in rebate
-   $200 craigslist 3 year old unit
===================================
+   $614
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I replaced the unit myself, with help from my father-in-law. I don't own a truck, so I decided to have it delivered for $65. I bought $100 worth of pipe and fittings, mostly because I don't want to learn how to sweat copper, so I use &lt;a class="reference external" href="https://www.amazon.com/gp/product/B01AS48PBS/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;amp;tag=russellball0b-20&amp;amp;creative=9325&amp;amp;linkCode=as2&amp;amp;creativeASIN=B01AS48PBS&amp;amp;linkId=81ade3de2fc030c163112c53c7049885"&gt;sharkbite fittings&lt;/a&gt; which cost more but are fast and simple to use.&lt;/p&gt;
&lt;p&gt;My state offers an instant $300 in store rebate, plus a $200 mail in rebate.&lt;/p&gt;
&lt;p&gt;The old unit was only 3 years old (came with the new house purchase) so I sold it on craigslist for $200 (MSRP was $899).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="return-on-investment"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Return on investment&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ok, so $614 to replace a working unit, am I crazy? What is the expected ROI?&lt;/p&gt;
&lt;p&gt;Well, I've only had the unit for a month, however my energy consumption for heating hot water went from $85/mo to $30/mo.&lt;/p&gt;
&lt;p&gt;Let's just round down to $50/mo savings.&lt;/p&gt;
&lt;p&gt;Ok so in 12 months the unit will pay for itself. Even without the rebates you're still looking at only a 2 year ROI before the unit starts paying dividends.&lt;/p&gt;
&lt;p&gt;That's not all.&lt;/p&gt;
&lt;p&gt;I don't need to run a separate dehumidifier in my basement because the new unit removes humidity as a by-product!&lt;/p&gt;
&lt;p&gt;... but wait there's more!&lt;/p&gt;
&lt;p&gt;This unit also produces cold air as a by-product which means free supplimental cooling during the summer!&lt;/p&gt;
&lt;p&gt;A water heater, dehumidifier, and air conditioner all-in-one!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-is-the-catch"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;What is the catch?&lt;/a&gt;&lt;/h2&gt;
&lt;img alt="Curb Realtime Energy Consumption of AO Smith Hybrid Hot Water Heater" class="align-right" src="/uploads/2019/curb-ao-smith-hybrid-hot-water-heater-usage.png" /&gt;
&lt;p&gt;There is no catch, but you do have to agree on a small trade off, hot water recovery time.&lt;/p&gt;
&lt;p&gt;Instead of using 4,500 watts for 45 minutes (.75 hours), a hybrid hot water heat pump uses 350 watts for 3 hours.&lt;/p&gt;
&lt;p&gt;Trading the slightly longer hot water recovery times, gives you:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a significant reduction on your energy bill&lt;/li&gt;
&lt;li&gt;dehumidification&lt;/li&gt;
&lt;li&gt;supplimental air conditioning / cooling&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# traditional.
4500 * .75 # == 3375 watts or 3.375 kWhr to recover

# hybrid heatpump.
350 * 3 # == 1050 watts or 1.050 kWhr to recover
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The decrease in consumption means a huge savings of 69%!!!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# percentage decrease.
(3375 - 1050) / 3375 # == 69% !!!
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So what are you waiting for? Honestly, if you are thinking about going solar, you should tackle this project first, right now!&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#hybrid-electric-hot-water-heat-pump" id="toc-entry-1"&gt;Hybrid Electric Hot Water Heat Pump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#finances" id="toc-entry-2"&gt;Finances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#return-on-investment" id="toc-entry-3"&gt;Return on investment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-is-the-catch" id="toc-entry-4"&gt;What is the catch?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="solar"></category><category term="project"></category></entry><entry><title>AWS nvme to block mapping</title><link href="https://russell.ballestrini.net/aws-nvme-to-block-mapping/" rel="alternate"></link><published>2019-01-19T14:50:00-05:00</published><updated>2019-01-19T14:50:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2019-01-19:/aws-nvme-to-block-mapping/</id><summary type="html">&lt;p&gt;Recently at work I transitioned our fleet from Ubuntu 14.04 LTS to Ubuntu 18.04 LTS. During the process I noticed an issue with our newer generation AWS EC2 &amp;quot;nitro&amp;quot; based instance types (specifically &lt;tt class="docutils literal"&gt;c5.2xlarge&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;AWS was presenting my &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; block device as &lt;tt class="docutils literal"&gt;/dev/nvme1n1&lt;/tt&gt; and my &lt;tt class="docutils literal"&gt;data …&lt;/tt&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently at work I transitioned our fleet from Ubuntu 14.04 LTS to Ubuntu 18.04 LTS. During the process I noticed an issue with our newer generation AWS EC2 &amp;quot;nitro&amp;quot; based instance types (specifically &lt;tt class="docutils literal"&gt;c5.2xlarge&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;AWS was presenting my &lt;tt class="docutils literal"&gt;root&lt;/tt&gt; block device as &lt;tt class="docutils literal"&gt;/dev/nvme1n1&lt;/tt&gt; and my &lt;tt class="docutils literal"&gt;data&lt;/tt&gt; device as &lt;tt class="docutils literal"&gt;/dev/nvme0n1&lt;/tt&gt;. For obvious reasons, this seemingly random and out-of-order assignment breaks my provisioning scripts.&lt;/p&gt;
&lt;p&gt;After much research and deep thought, I came up with a solid solution.&lt;/p&gt;
&lt;p&gt;I found a barely documented tool called &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ebsnvme-id&lt;/span&gt;&lt;/tt&gt; on the official Amazon Linux AMI and wrote a wrapper (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nvme-to-block-mapping&lt;/span&gt;&lt;/tt&gt;) to iterate over all possible combinations of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/dev/nvme[0-26]n1&lt;/span&gt;&lt;/tt&gt; to create a symlink to the block mapping selected when we launch the EC2 instance.&lt;/p&gt;
&lt;p&gt;Since we have control over the block mapping, we end up with consistent and known symlink which we may confidently pass to our provisioning scripts when the time comes to partition, format, and use the block devices.&lt;/p&gt;
&lt;p&gt;To save you time, I have added these scripts to this blog post!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please consider&lt;/strong&gt; &lt;a class="reference external" href="https://www.paypal.me/russellbal/5"&gt;donating here&lt;/a&gt; &lt;strong&gt;if my work has helped you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Place the following two scripts into your AMI under &lt;tt class="docutils literal"&gt;/usr/sbin/&lt;/tt&gt; and trigger the wrapper just once prior to using the devices (don't worry the wrapper script is idempotent, running it more than once won't break anything).&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/_OYInsj7SYw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;div class="section" id="nvme-to-block-mapping"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;nvme-to-block-mapping&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="/uploads/2019/nvme-to-block-mapping"&gt;/usr/sbin/nvme-to-block-mapping&lt;/a&gt; (click for file):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c1"&gt;# for details:&lt;/span&gt;
&lt;span class="c1"&gt;# https://russell.ballestrini.net/aws-nvme-to-block-mapping/&lt;/span&gt;

&lt;span class="c1"&gt;# this will create a symlinks like:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     /dev/nvme1n1 -&amp;gt; /dev/xvdh&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# these ebs block device paths are set by stacker and assumed by ansible.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# if the device is non ebs, it will use the following mapping:&lt;/span&gt;
&lt;span class="nv"&gt;non_ebs_mapping&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdb1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdc1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdd1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sde1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdf1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/sdg1&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# nvme0n1 uses ${non_ebs_mapping[0]} (the 0 index item in the array)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     /dev/nvme0n1 -&amp;gt; /dev/sdb1&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# nvme3n1 uses ${non_ebs_mapping[3]} (the 3 index item in the array)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     /dev/nvme3n1 -&amp;gt; /dev/sde1&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;

&lt;span class="c1"&gt;# why we only iterate from 0 to 26:&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;seq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;nvme_block_device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/nvme&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;n1&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# skip any nvme paths which don&amp;#39;t exist.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;$nvme_block_device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# get ebs block mapping device path set by stacker (or base AMI).&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/usr/sbin/ebsnvme-id&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;nvme_block_device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--block-dev&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# if the mapping_device is empty, it isn&amp;#39;t an EBS device so&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# we will use the non_ebs_mapping to translate the device.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$mapping_device&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;non_ebs_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# if block mapping device path does not start with /dev/ fix it.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$mapping_device&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# if the block mapping device path already exists, skip it.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mapping_device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path exists: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# otherwise, create a symlink from nvme block device to mapping device.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;symlink created: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;nvme_block_device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;mapping_device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$nvme_block_device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mapping_device&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ebsnvme-id"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;ebsnvme-id&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="/uploads/2019/ebsnvme-id"&gt;/usr/sbin/ebsnvme-id&lt;/a&gt; (click for file):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env python2.7&lt;/span&gt;

&lt;span class="c1"&gt;# Copyright (C) 2017 Amazon.com, Inc. or its affiliates.&lt;/span&gt;
&lt;span class="c1"&gt;# All Rights Reserved.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;).&lt;/span&gt;
&lt;span class="c1"&gt;# You may not use this file except in compliance with the License.&lt;/span&gt;
&lt;span class="c1"&gt;# A copy of the License is located at&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#    http://aws.amazon.com/apache2.0/&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# or in the &amp;quot;license&amp;quot; file accompanying this file. This file is&lt;/span&gt;
&lt;span class="c1"&gt;# distributed on an &amp;quot;AS IS&amp;quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS&lt;/span&gt;
&lt;span class="c1"&gt;# OF ANY KIND, either express or implied. See the License for the&lt;/span&gt;
&lt;span class="c1"&gt;# specific language governing permissions and limitations under the&lt;/span&gt;
&lt;span class="c1"&gt;# License.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Reference:&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html&lt;/span&gt;

&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;Usage:&lt;/span&gt;
&lt;span class="sd"&gt;Read EBS device information and provide information about&lt;/span&gt;
&lt;span class="sd"&gt;the volume.&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;ctypes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fcntl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ioctl&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;NVME_ADMIN_IDENTIFY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x06&lt;/span&gt;
&lt;span class="n"&gt;NVME_IOCTL_ADMIN_CMD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xC0484E41&lt;/span&gt;
&lt;span class="n"&gt;AMZN_NVME_VID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x1D0F&lt;/span&gt;
&lt;span class="n"&gt;AMZN_NVME_EBS_MN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Amazon Elastic Block Store&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;nvme_admin_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_pack_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;_fields_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;opcode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;# op code&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# fused operation&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# command id&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nsid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# namespace id&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mptr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# metadata pointer&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;addr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# data pointer&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mlen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# metadata length&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;alen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# data length&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw10&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw11&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw13&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw14&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cdw15&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint64&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;nvme_identify_controller_amzn_vs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_pack_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;_fields_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bdev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# block device name&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;nvme_identify_controller_psd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_pack_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;_fields_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# maximum power&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;enlat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;# entry latency&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;exlat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;# exit latency&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rrt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# relative read throughput&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rrl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# relative read latency&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rwt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# relative write throughput&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rwl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# relative write latency&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;nvme_identify_controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Structure&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_pack_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;_fields_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# PCI Vendor ID&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssvid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# PCI Subsystem Vendor ID&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# Serial Number&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# Module Number&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# Firmware Revision&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rab&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Recommend Arbitration Burst&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ieee&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;# IEEE OUI Identifier&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Multi-Interface Capabilities&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mdts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Maximum Data Transfer Size&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;oacs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# Optional Admin Command Support&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;acl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Abort Command Limit&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;aerl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Asynchronous Event Request Limit&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frmw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Firmware Updates&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lpa&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Log Page Attributes&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;elpe&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Error Log Page Entries&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;npss&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Number of Power States Support&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;avscc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# Admin Vendor Specific Command Configuration&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;265&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sqes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Submission Queue Entry Size&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cqes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;# Completion Queue Entry Size&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;            &lt;span class="c1"&gt;# Number of Namespaces&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;oncs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# Optional NVM Command Support&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fuses&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# Fused Operation Support&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fna&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Format NVM Attributes&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vwc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;           &lt;span class="c1"&gt;# Volatile Write Cache&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;awun&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# Atomic Write Unit Normal&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;awupf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# Atomic Write Unit Power Fail&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nvscc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;         &lt;span class="c1"&gt;# NVM Vendor Specific Command Configuration&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;704&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;531&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;reserved4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_uint8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;704&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;psd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nvme_identify_controller_psd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;# Power State Descriptor&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nvme_identify_controller_amzn_vs&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;# Vendor Specific&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ebs_nvme_device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctrl_identify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_nvme_ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_len&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;admin_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nvme_admin_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opcode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NVME_ADMIN_IDENTIFY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;alen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;cdw10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rw&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nvme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nvme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NVME_IOCTL_ADMIN_CMD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin_cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ctrl_identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nvme_identify_controller&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_nvme_ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addressof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;AMZN_NVME_VID&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;AMZN_NVME_EBS_MN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[ERROR] Not an EBS device: &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_volume_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sn&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vol&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;vol-&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_block_device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bdev&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dev/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Reads EBS information from NVMe devices.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;device&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Device to query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Display Options&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--volume&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;store_true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Return volume-id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--block-dev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;store_true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Return block device mapping&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-u&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--udev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;store_true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Output data in format suitable for udev rules&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print_help&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;get_all&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udev&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_dev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ebs_nvme_device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;IOError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;get_all&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Volume ID: &lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_volume_id&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;get_all&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_dev&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_block_device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#nvme-to-block-mapping" id="toc-entry-1"&gt;nvme-to-block-mapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#ebsnvme-id" id="toc-entry-2"&gt;ebsnvme-id&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Yuletide Trains and Homegrown Video Games</title><link href="https://russell.ballestrini.net/yuletide-trains-and-homegrown-video-games/" rel="alternate"></link><published>2018-12-25T17:15:00-05:00</published><updated>2018-12-25T17:15:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-12-25:/yuletide-trains-and-homegrown-video-games/</id><summary type="html">&lt;img alt="pixel art style seasonal wrapped gift" class="align-right" src="/uploads/2018/pixel-art-gift.png" /&gt;
&lt;p&gt;Each holiday season I find myself drawn to a side passion of mine.&lt;/p&gt;
&lt;p&gt;While some build model trains and others create Christmas light shows with synchronized music you'll find me on my sofa where I build, explore, and tinker on my own video game engine.&lt;/p&gt;
&lt;p&gt;Maybe the sound of wrapping …&lt;/p&gt;</summary><content type="html">&lt;img alt="pixel art style seasonal wrapped gift" class="align-right" src="/uploads/2018/pixel-art-gift.png" /&gt;
&lt;p&gt;Each holiday season I find myself drawn to a side passion of mine.&lt;/p&gt;
&lt;p&gt;While some build model trains and others create Christmas light shows with synchronized music you'll find me on my sofa where I build, explore, and tinker on my own video game engine.&lt;/p&gt;
&lt;p&gt;Maybe the sound of wrapping paper tearing acts as a trigger, but I only prioritize time on my game engine during the holiday season.&lt;/p&gt;
&lt;p&gt;Do you have a specific hobby that you do during the holiday season but not during other parts of the year?&lt;/p&gt;
&lt;blockquote&gt;
Why am I working on Christmas every year - Am I a work-a-holic?&lt;/blockquote&gt;
&lt;div class="section" id="work-or-play"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Work or Play.&lt;/a&gt;&lt;/h2&gt;
&lt;img alt="green pixel art style Christmas tree with animated blinking lights" class="align-right" src="/uploads/2018/pixel-art-yuletide-tree.gif" /&gt;
&lt;p&gt;Why do I absorb myself into this seasonal hobby? Am I escaping? Or am I making use of down time to work on a project that I am too occupied to even think about during the rest of the year?&lt;/p&gt;
&lt;p&gt;For me, building my game on Christmas is fun, not &lt;em&gt;work&lt;/em&gt;. Similar to how fishing while camping is fun, not &lt;em&gt;work&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
What is the last activity a commercial fisherman would do &amp;quot;for fun&amp;quot; on Christmas?
Fishing.&lt;/blockquote&gt;
&lt;p&gt;What is fun for one person might be work for another. It is relative and depends on context.&lt;/p&gt;
&lt;p&gt;I suspect that most people experience a tipping point, when an activity switches from fun to work.&lt;/p&gt;
&lt;p&gt;I think, for an average person, this moment occurs when the answer to the following question changes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Do you rely on the activity for the majority of your income?&lt;ul&gt;
&lt;li&gt;if yes, the activity often feels like work&lt;/li&gt;
&lt;li&gt;if no, the activity often feels like fun&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="work-and-play"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Work and Play?&lt;/a&gt;&lt;/h2&gt;
&lt;img alt="red pixel art style santa hat" class="align-right" src="/uploads/2018/pixel-art-santa-hat.png" /&gt;
&lt;p&gt;Now that's not to say you cannot find fun while at work, and vice versa. Personally, I aim to land a perfect balance between the two with anything I do.&lt;/p&gt;
&lt;p&gt;In my situation, I use the computer &amp;quot;professionally&amp;quot; each work day, for 7-10 hours.&lt;/p&gt;
&lt;p&gt;If I was in the games industry, I doubt I would find much fun in building a video game on Christmas. But I'm not in the games industry and I have more fun building games than playing games.&lt;/p&gt;
&lt;p&gt;That is to say, for me building a game feels fun while playing a game feels like work. Crazy right?&lt;/p&gt;
&lt;img alt="animated gif of a pixel art style circular bomb with fuse burning down" class="align-right" src="/uploads/2018/pixel-art-bomb.gif" /&gt;
&lt;p&gt;Even though the activities of my &amp;quot;day job&amp;quot; overlap with the activities involved with building my game, there are enough differences. These differences make one feel like work while the other feel like fun.&lt;/p&gt;
&lt;p&gt;I'm not making an excuse for working during Christmas; I honestly have a blast coding and tinkering on my game.&lt;/p&gt;
&lt;p&gt;I love getting stumped and learning. I love showing different ideas to my boys and seeing their eyes light up. I love to see how fast I can implement a small suggestion one of them have. I love how there are no rules and only my own skills and understanding can block me.&lt;/p&gt;
&lt;p&gt;While my family is occupied with their new holiday gifts, I use my free time to explore creative ideas on a non-stressful personal project. A project where the stakes are low and the pleasure is high.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="show-me-the-game-already"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Show me the game already!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This years game engine modifications:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;ability to save the game state to a &lt;cite&gt;json&lt;/cite&gt; file&lt;/li&gt;
&lt;li&gt;ability to load the game state from a &lt;cite&gt;json&lt;/cite&gt; file&lt;/li&gt;
&lt;li&gt;added a few algorithms for auto-generating maps&lt;/li&gt;
&lt;li&gt;refactored some common code (so I'm not reapeating myself)&lt;/li&gt;
&lt;li&gt;implemented an action charge / recharge system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a short video of the current state of the engine. Let me know what you think in the comments.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/qeMPV6aXKzk" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;I also took the time to learn how to make pixel art using &lt;cite&gt;GIMP&lt;/cite&gt;, a useful skill for making game assets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="who-else-makes-games-during-christmas"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Who else makes games during Christmas?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I reached out to a number of indie game developers while formulating this blog post and I had a solid conversation with &lt;a class="reference external" href="https://github.com/TheMaverickProgrammer"&gt;Maverick Peppers&lt;/a&gt; a developer who runs a &lt;a class="reference external" href="https://protocomplete.com/"&gt;software company called ProtoComplete&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;He was working during Christmas on his game while making pudding and drinking white russians (&lt;a class="reference external" href="/yuletide-trains-and-homegrown-video-games/#white-russian-recipe"&gt;recipe in the foot notes&lt;/a&gt;). He arrived at the conclusion that I was working from a passion-oriented mindset while he was from a goal-oriented mindset.&lt;/p&gt;
&lt;p&gt;Maverick doesn't rely on the income from his games, so I asked &amp;quot;why are you 'working' on Christmas?&amp;quot; He explained that because he often has a &lt;cite&gt;type-a&lt;/cite&gt; or &amp;quot;goal mindset&amp;quot;, he has trouble relaxing until he finishes whatever he is working on. In a sense, he was working Christmas &lt;em&gt;so that he could&lt;/em&gt; relax.&lt;/p&gt;
&lt;p&gt;As for myself, building games is a passion-oreiented process, I don't rely on the income and I use it to unwind.&lt;/p&gt;
&lt;p&gt;We talked about how people can approach activities with either:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;cite&gt;type-a&lt;/cite&gt; (goal-oriented mindset)&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;type-b&lt;/cite&gt; (passion-oriented mindset)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fun part is people are often both, depending on the activity or project. For example Maverick has a passion mindset when making music and DJing, but always takes a goal mindset when it comes to business.&lt;/p&gt;
&lt;p&gt;Do you have a specific hobby that you do during the holiday season but not during other parts of the year?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="footnotes"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="unwrapping-my-christmas-commit-history"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Unwrapping My Christmas Commit History&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking at my commit history, the first iteration of my current game engine was saved on Jan 01, 2014 with a few commits until Jan 19, 2014, at which point nothing until Dec 25, 2014 (Christmas itself) when I sprinted until Jan 10, 2015.&lt;/p&gt;
&lt;p&gt;The next year, I must have hacked on something else, with no changes until Oct 09, 2016 where I had two commits.&lt;/p&gt;
&lt;p&gt;Like clockwork on Dec 25, 2016 (Christmas) I tried to fix a regression in the engine's collision and intersection code. I left myself some breadcrumb comments to help me debug in the future... Nothing in 2017.&lt;/p&gt;
&lt;p&gt;Today is Christmas 2018 and finally I have a work around for the regression I was looking into from Christmas 2016!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="porting-sfml-rect-from-c-to-python"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Porting SFML Rect from C++ to Python&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This work around ports the &lt;cite&gt;Rect&lt;/cite&gt; intersection logic of &lt;cite&gt;SFML&lt;/cite&gt; from C++ to pure Python and avoids the following error message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;cite&gt;terminated by signal SIGSEGV (Address boundary error)&lt;/cite&gt;&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_rect_intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Accept two sfml.graphics.Rect objects.&lt;/span&gt;
&lt;span class="sd"&gt;    Return a new sfml.graphics.Rect of the overlap or None.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# We allow Rects with negative dimensions, so handle them correctly.&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute the min and max of the first Rect (r1).&lt;/span&gt;
    &lt;span class="n"&gt;r1_min_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r1_max_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r1_min_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r1_max_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute the min and max of the second Rect (r2).&lt;/span&gt;
    &lt;span class="n"&gt;r2_min_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2_max_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2_min_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2_max_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# compute the intersection boundaries.&lt;/span&gt;
    &lt;span class="n"&gt;i_left&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1_min_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2_min_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;i_top&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1_min_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2_min_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;i_right&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1_max_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2_max_x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;i_bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1_max_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2_max_y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# if the intersection is valid (positive non zero area),&lt;/span&gt;
    &lt;span class="c1"&gt;# then there is an intersection.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i_left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i_right&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i_top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i_bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sfml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i_left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_top&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i_right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i_left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_bottom&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i_top&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="white-russian-recipe"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;White Russian Recipe&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;1/4 cup distilled водка&lt;/li&gt;
&lt;li&gt;1/4 cup Kahlua coffee rum&lt;/li&gt;
&lt;li&gt;1/2 cup cream&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="older-versions-of-the-game-engine"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Older versions of the game engine&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some &lt;a class="reference external" href="https://russell.ballestrini.net/test-game-engine-with-python-and-sfml/"&gt;videos of older versions&lt;/a&gt; of this game engine.&lt;/p&gt;
&lt;div class="contents topic" id="index"&gt;
&lt;p class="topic-title"&gt;index&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#work-or-play" id="toc-entry-1"&gt;Work or Play.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#work-and-play" id="toc-entry-2"&gt;Work and Play?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#show-me-the-game-already" id="toc-entry-3"&gt;Show me the game already!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#who-else-makes-games-during-christmas" id="toc-entry-4"&gt;Who else makes games during Christmas?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#footnotes" id="toc-entry-5"&gt;Footnotes&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#unwrapping-my-christmas-commit-history" id="toc-entry-6"&gt;Unwrapping My Christmas Commit History&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#porting-sfml-rect-from-c-to-python" id="toc-entry-7"&gt;Porting SFML Rect from C++ to Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#white-russian-recipe" id="toc-entry-8"&gt;White Russian Recipe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#older-versions-of-the-game-engine" id="toc-entry-9"&gt;Older versions of the game engine&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Games"></category></entry><entry><title>All Local Heros need a Gig Side Kick</title><link href="https://russell.ballestrini.net/all-local-heros-need-a-gig-side-kick/" rel="alternate"></link><published>2018-08-03T11:20:00-04:00</published><updated>2018-08-03T11:20:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-08-03:/all-local-heros-need-a-gig-side-kick/</id><summary type="html">&lt;p&gt;Five months ago during my preparation and ramp up to gardening season, I started thinking about what it would be like to have a partner, a side kick, a secretary to keep me honest and on task.&lt;/p&gt;
&lt;p&gt;Somebody who could review old journal notes from previous years and give me …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Five months ago during my preparation and ramp up to gardening season, I started thinking about what it would be like to have a partner, a side kick, a secretary to keep me honest and on task.&lt;/p&gt;
&lt;p&gt;Somebody who could review old journal notes from previous years and give me hints about what seeds I should be starting, what worked well, what didn't, and why.&lt;/p&gt;
&lt;p&gt;I started thinking I'm likely not alone and that other hobby and market gardeners likely feel the same way. I then expanded this, likely anyone bootstrapping a small business &amp;quot;enterprise&amp;quot; could use a side kick. Small craft brewers, artists, sign makers, the list goes on and on.&lt;/p&gt;
&lt;p&gt;As you might know I'm deeply interested in systems like computer science and permaculture. Computer scientists often try to model complex natural systems. Often natural systems give us clues on how to make computers perform certain tasks.&lt;/p&gt;
&lt;p&gt;One reoccuring theme I find between computer science and permaculture is the idea of inputs and outputs.&lt;/p&gt;
&lt;p&gt;Brewing wine from berries requires the following inputs: boiling water, berries, sugar, yeast, and yeast nutrient. Combined all these inputs together in the right way, wait for a certain time, and the system returns an a tasty, alchoholic, product with a long shelf life as an output. The spent berries go straight into the compost for next years garden. Natural systems and unnatural systems model after natural ones, have no waste by-products (pollution). Every output of one process is always an input of another.&lt;/p&gt;
&lt;p&gt;Only when we mess up the natural order of organic systems do we end up with polution. Polution is simply a holistically bad output. For example, when we artifically produce outputs, like plastics there is no natural system to take it as an input, it becomes polution and makes the system sick.&lt;/p&gt;
&lt;p&gt;To fight off the production of unnatural outputs, like single use plastics, we need to create other human managed systems to use the pollution as inputs. For example, I'm insulating my shed with bags of single use polyethylene wrappers that wrap my families purchases. I'm keeping the valuable (although un-natural) resource out of the waste stream and using it to shrink the tempurature swing deltas in my shed. When we model the material world as inputs and outputs we can learn how to best use what we have on hand.&lt;/p&gt;
&lt;p&gt;We can boost each of our local economies by providing proper tools to the local makers.&lt;/p&gt;
&lt;p&gt;This is why today I'm announcing GigSideKick to flip the script on globalization and large enterprise giants.&lt;/p&gt;
&lt;p&gt;GigSideKick is a &amp;quot;field&amp;quot; journal app to track inputs and outputs. The primary goal is to collect the right amount of data with the least amount of friction, have an intuitive user interface, and a user experience which mostly stays out of the way. In addition to tracking inputs and outputs, the user will optionally set ETAs on processes set in motion which will power reminders.&lt;/p&gt;
&lt;blockquote&gt;
Each local hero deserves the unfair advantage that a smart and capabile Side Kick provides.&lt;/blockquote&gt;
&lt;p&gt;This type of input/output journal will not interfere with the task at hand, and would only require a few button presses. The data created from such a journal is important for the user's future self, and if properly aggregated and crowd sourced, the data will enable a powerful recommendation engine.&lt;/p&gt;
&lt;p&gt;For example, a person subscribing to a &amp;quot;garden&amp;quot; knowledge pack might get the following recommendation: &amp;quot;gardeners in your area are known to typically start on average of 24 zucchini seeds this month, which typically result in 18 viable zucchini seedlings in 6 days&amp;quot;, &amp;quot;How many will you start?&amp;quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;None&lt;/li&gt;
&lt;li&gt;12&lt;/li&gt;
&lt;li&gt;24&lt;/li&gt;
&lt;li&gt;48&lt;/li&gt;
&lt;li&gt;Other&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the background, the app would also track human labor (which itself is valuable input), track inventory of parts, work-in-process, and finished goods. Finally the app's data would power a local marketplace to help the maker trade and sell finished products. After all, if you produce a surplus of apples without a system in place to use them, the apples will end up as an input to the compost pile instead of the bellies of deserving humans. : )&lt;/p&gt;
&lt;p&gt;I believe we must give power back to local communities and economies. We, the side gig heros and local businesses operators, need all the help we can get to compete in today's global economy.&lt;/p&gt;
&lt;p&gt;Local makers have many often un-exploited unfair advantages. We do not need to ship our goods which means we should have lower margins. We need to figure out how to market our products (outputs) to local people looking for our superior goods.&lt;/p&gt;
&lt;p&gt;Another often untapped unfair advantage is the sale or trade of our by-products. We can even trade our waste! For example as a gardener, I would gladly trade a heaping bowl of same day cut, &amp;quot;beyond organic&amp;quot; salad greens, for your bucket of leaves, coffee grounds, sticks, sawdust, or grass clippings! It's honestly the timeless addage of &amp;quot;one mans trash is another mans treasure&amp;quot;. Your waste is a very useful input to the correct system and local people trading outputs is by far the best thing we can do for our planet.&lt;/p&gt;
&lt;p&gt;I plan to document my journy of building this vision, I hope you join me.&lt;/p&gt;
&lt;p&gt;Like always, please feel free to leave comments below, I always respond.&lt;/p&gt;
</content><category term="misc"></category><category term="bootstrapping"></category></entry><entry><title>Running DynamoDB Local service container on CircleCI 2.0</title><link href="https://russell.ballestrini.net/running-dynamodb-local-service-container-on-circleci-2/" rel="alternate"></link><published>2018-07-18T11:56:00-04:00</published><updated>2018-07-18T11:56:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-07-18:/running-dynamodb-local-service-container-on-circleci-2/</id><summary type="html">&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; use a custom entrypoint in your CircleCI 2.0 config to limit Java memory to 1G.&lt;/p&gt;
&lt;p&gt;The new CircleCI 2.0 docker configuration supports a &amp;quot;primary image&amp;quot; (listed first) which runs all the &amp;quot;steps&amp;quot; as well as zero or many &amp;quot;service images&amp;quot; (listed subsequently). The &amp;quot;service images&amp;quot;, although …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; use a custom entrypoint in your CircleCI 2.0 config to limit Java memory to 1G.&lt;/p&gt;
&lt;p&gt;The new CircleCI 2.0 docker configuration supports a &amp;quot;primary image&amp;quot; (listed first) which runs all the &amp;quot;steps&amp;quot; as well as zero or many &amp;quot;service images&amp;quot; (listed subsequently). The &amp;quot;service images&amp;quot;, although not running in the same container as the primary present as if local to the primary.&lt;/p&gt;
&lt;p&gt;It sort of feels like mixing many docker containers together.&lt;/p&gt;
&lt;p&gt;Enough talk, in this example, I show how easy it is to run &lt;a class="reference external" href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html"&gt;DynamoDB Local&lt;/a&gt; in a separate container but at the same time present it to the primary as &lt;tt class="docutils literal"&gt;127.0.0.1:8000&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="nt"&gt;workflows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;test&lt;/span&gt;

&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;working_directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/go/src/github.com/remind101/r101-myapp&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Primary container image where all the steps run.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;circleci/golang:1.8&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Service container image made available to the primary container at `host: localhost`&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;dwmkerr/dynamodb:41&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# custom entrypoint to limit RAM to 1G to prevent OOM on CircleCI 2.0.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# https://circleci.com/docs/2.0/configuration-reference/#docker--machine--macosexecutor&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;java&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-Xmx1G&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;-jar&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;DynamoDBLocal.jar&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;checkout&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;make test_verbose&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="troubleshooting"&gt;
&lt;h2&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;When I first tried to use this image, any test which tried to reach out to DynamoDB via &lt;tt class="docutils literal"&gt;127.0.0.1:8000&lt;/tt&gt; had the following exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Test&lt;span class="w"&gt; &lt;/span&gt;Panicked
Error&lt;span class="w"&gt; &lt;/span&gt;creating&lt;span class="w"&gt; &lt;/span&gt;table&lt;span class="w"&gt; &lt;/span&gt;group_associations:&lt;span class="w"&gt; &lt;/span&gt;RequestError:&lt;span class="w"&gt; &lt;/span&gt;send&lt;span class="w"&gt; &lt;/span&gt;request&lt;span class="w"&gt; &lt;/span&gt;failed
caused&lt;span class="w"&gt; &lt;/span&gt;by:&lt;span class="w"&gt; &lt;/span&gt;Post&lt;span class="w"&gt; &lt;/span&gt;http://127.0.0.1:8000/:&lt;span class="w"&gt; &lt;/span&gt;dial&lt;span class="w"&gt; &lt;/span&gt;tcp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:8000:&lt;span class="w"&gt; &lt;/span&gt;getsockopt:&lt;span class="w"&gt; &lt;/span&gt;connection&lt;span class="w"&gt; &lt;/span&gt;refused
/usr/local/go/src/runtime/panic.go:489
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It seems the docker-dynamodb (DynamoDB Local) container failed to start and exited like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Initializing&lt;span class="w"&gt; &lt;/span&gt;DynamoDB&lt;span class="w"&gt; &lt;/span&gt;Local&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;following&lt;span class="w"&gt; &lt;/span&gt;configuration:
Port:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8000&lt;/span&gt;
InMemory:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
DbPath:&lt;span class="w"&gt;       &lt;/span&gt;null
SharedDb:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
shouldDelayTransientStatuses:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
CorsParams:&lt;span class="w"&gt;   &lt;/span&gt;*


Exited&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;code&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;137&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Apparently &lt;tt class="docutils literal"&gt;Exit code 137&lt;/tt&gt; is a classic Docker + Java error code caused by an OOM (out of memory).&lt;/p&gt;
&lt;p&gt;By default, projects on CircleCI build in virtual environments with 4GB of RAM but this is shared and Java acts greedy when inside a container so it needs a limit by adding &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-Xmx1G&lt;/span&gt;&lt;/tt&gt; to the &lt;tt class="docutils literal"&gt;entrypoint&lt;/tt&gt;!&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://circleci.com/blog/how-to-handle-java-oom-errors/"&gt;https://circleci.com/blog/how-to-handle-java-oom-errors/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://circleci.com/docs/2.0/postgres-config/"&gt;https://circleci.com/docs/2.0/postgres-config/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Pyramid SQLAlchemy bootstrap console script with transaction.manager</title><link href="https://russell.ballestrini.net/pyramid-sqlalchemy-bootstrap-console-script-with-transaction-manager/" rel="alternate"></link><published>2018-06-15T10:54:00-04:00</published><updated>2018-06-15T10:54:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-06-15:/pyramid-sqlalchemy-bootstrap-console-script-with-transaction-manager/</id><summary type="html">&lt;p&gt;So I've struggled for a while with the best way to properly setup a console script for my SQLAlchemy Pyramid apps.&lt;/p&gt;
&lt;p&gt;I use the &lt;a class="reference external" href="https://github.com/Pylons/pyramid-cookiecutter-alchemy"&gt;Pyramid Cookiecutter Alchemy&lt;/a&gt; to setup my projects and as such, I do not have a global and thus importable DBSession object. Instead my database session is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So I've struggled for a while with the best way to properly setup a console script for my SQLAlchemy Pyramid apps.&lt;/p&gt;
&lt;p&gt;I use the &lt;a class="reference external" href="https://github.com/Pylons/pyramid-cookiecutter-alchemy"&gt;Pyramid Cookiecutter Alchemy&lt;/a&gt; to setup my projects and as such, I do not have a global and thus importable DBSession object. Instead my database session is attached to the request on creation.&lt;/p&gt;
&lt;p&gt;Anyways, here is my recipe:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.paster&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup_logging&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;remarkbox.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;invalidate_all_node_cache_objects&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_arg_parser&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalidate all NodeCache objs to force recomputation.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;--config&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;development.ini&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_arg_parser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;setup_logging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# use bootstrap context manager to prepare app and request,&lt;/span&gt;
    &lt;span class="c1"&gt;# next use the resulting request&amp;#39;s transaction manager!&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;request&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;invalidate_all_node_cache_objects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbsession&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This pattern should help you solve this error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;NoTransaction&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>Quickstart to DKIM Sign Email with Python</title><link href="https://russell.ballestrini.net/quickstart-to-dkim-sign-email-with-python/" rel="alternate"></link><published>2018-06-04T09:03:00-04:00</published><updated>2018-06-04T09:03:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-06-04:/quickstart-to-dkim-sign-email-with-python/</id><summary type="html">&lt;p&gt;For a long time I have put off DKIM signing email sent from  my web services because I couldn't wrap my head around all the indirection Postfix requires to make it work.&lt;/p&gt;
&lt;p&gt;Honestly, I put it off for over 5 years...&lt;/p&gt;
&lt;p&gt;Today a thought sprung into my head:&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;Could I …&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;For a long time I have put off DKIM signing email sent from  my web services because I couldn't wrap my head around all the indirection Postfix requires to make it work.&lt;/p&gt;
&lt;p&gt;Honestly, I put it off for over 5 years...&lt;/p&gt;
&lt;p&gt;Today a thought sprung into my head:&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;Could I sign Email at the application level before passing to Postfix?&amp;quot;&lt;/blockquote&gt;
&lt;p&gt;As you may know, I primarily use the Python programming language, so I did some research and found a reference to a single library called &lt;tt class="docutils literal"&gt;dkimpy&lt;/tt&gt; (previously &lt;tt class="docutils literal"&gt;pydkim&lt;/tt&gt;). The codebase started over 10 years ago and appeared stable and mature.&lt;/p&gt;
&lt;p&gt;The part that sold me was that &lt;tt class="docutils literal"&gt;dkimpy&lt;/tt&gt; seemed compatible with the two Python standard library modules which I already use:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;email&lt;/tt&gt; which I use to prepare messages&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;smtplib&lt;/tt&gt; which I use to transport messages to Postfix running on localhost&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One issue I did have with &lt;tt class="docutils literal"&gt;dkimpy&lt;/tt&gt; was the complete lack of examples or even a quickstart guide.&lt;/p&gt;
&lt;p&gt;For this reason, I have written this post!&lt;/p&gt;
&lt;div class="section" id="the-missing-dkimpy-quickstart-guide"&gt;
&lt;h2&gt;The Missing dkimpy Quickstart Guide&lt;/h2&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;To install &lt;tt class="docutils literal"&gt;dkimpy&lt;/tt&gt; you may use &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt; (&lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt;) or in my case I added it to my &lt;tt class="docutils literal"&gt;setup.py&lt;/tt&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Generate a public / private keypair. Don't let this step trip you up, the process easy. In a Unix -like environment you may run the following commands to create the keys.&lt;/p&gt;
&lt;p&gt;generate private key (I name my file after the domain and DKIM selector I plan to use).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;genrsa&lt;span class="w"&gt; &lt;/span&gt;-out&lt;span class="w"&gt; &lt;/span&gt;remarkbox.com.20180605.pem&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;generate public key from the private key.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;rsa&lt;span class="w"&gt; &lt;/span&gt;-in&lt;span class="w"&gt; &lt;/span&gt;remarkbox.com.20180605.pem&lt;span class="w"&gt; &lt;/span&gt;-out&lt;span class="w"&gt; &lt;/span&gt;remarkbox.com.20180605.pub&lt;span class="w"&gt; &lt;/span&gt;-pubout
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Install the public key (&lt;tt class="docutils literal"&gt;.pub&lt;/tt&gt;) as a DNS TXT record, where the record name (&amp;quot;selector&amp;quot;) is &lt;tt class="docutils literal"&gt;20180605._domainkey&lt;/tt&gt; and the value body is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DKIM1&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rsa&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcplYPRsqIFwXuggtH2XgQDMX+e+6sGnWdV8ld/FR9zgRAxB+DeiCEVooVvYt2JRZUEokgDFvys82Q+JTbN4qHNz19bdcBGrnTsnIFaQYpgeQYmPLdDtcWRKzTYMRNCnRmmEXyGv7WIDcaTapIq9NFgLmy1QT7ZTxuNjQtDB/2LwIDAQAB&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You may choose any selector, I happen to like to use YearMonthDay. Additionally you will substitute your public key in place of mine. Put each the line of the public key on a single line in the TXT record.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;On each of my application servers I store my private portion of my DKIM key in &lt;tt class="docutils literal"&gt;/etc/dkim/remarkbox.com.20180605.pem&lt;/tt&gt;. You may store your key any where on the filesystem that is accessible to the user or group running the application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;This is how I used to send Email with Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;smtplib&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;

&lt;span class="c1"&gt;# catch socket errors when postfix isn&amp;#39;t running...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;socket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;socket_error&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;alternative&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;plain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Subject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;From&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender_email&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;To&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_email&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: react if connecting to postfix is a socket error.&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;This is how I now send DKIM signed Email with Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ref: https://github.com/russellballestrini/miscutils/blob/master/miscutils/mail.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dkim&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;smtplib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;
&lt;span class="c1"&gt;# catch socket errors when postfix isn&amp;#39;t running...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;socket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;socket_error&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dkim_private_key_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dkim_selector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# the `email` library assumes it is working with string objects.&lt;/span&gt;
    &lt;span class="c1"&gt;# the `dkim` library assumes it is working with byte objects.&lt;/span&gt;
    &lt;span class="c1"&gt;# this function performs the acrobatics to make them both happy.&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# needed for Python 3.&lt;/span&gt;
        &lt;span class="n"&gt;message_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# needed for Python 3.&lt;/span&gt;
        &lt;span class="n"&gt;message_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;sender_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;alternative&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;plain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;To&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_email&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;From&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender_email&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Subject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Python 3 libraries expect bytes.&lt;/span&gt;
        &lt;span class="n"&gt;msg_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Python 2 libraries expect strings.&lt;/span&gt;
        &lt;span class="n"&gt;msg_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dkim_private_key_path&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;dkim_selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# the dkim library uses regex on byte strings so everything&lt;/span&gt;
        &lt;span class="c1"&gt;# needs to be encoded from strings to bytes.&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dkim_private_key_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dkim_private_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;To&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;From&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Subject&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dkim&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dkim_selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;privkey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dkim_private_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;include_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# add the dkim signature to the email message headers.&lt;/span&gt;
        &lt;span class="c1"&gt;# decode the signature back to string_type because later on&lt;/span&gt;
        &lt;span class="c1"&gt;# the call to msg.as_string() performs it&amp;#39;s own bytes encoding...&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DKIM-Signature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DKIM-Signature: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Python 3 libraries expect bytes.&lt;/span&gt;
            &lt;span class="n"&gt;msg_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Python 2 libraries expect strings.&lt;/span&gt;
            &lt;span class="n"&gt;msg_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# TODO: react if connecting to relay (localhost postfix) is a socket error.&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;As always, leave a comment or contact me for questions or help.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>Fulfilling Childhood Dreams: Solar</title><link href="https://russell.ballestrini.net/fulfilling-childhood-dreams-solar/" rel="alternate"></link><published>2018-03-16T08:03:00-04:00</published><updated>2018-03-16T08:03:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2018-03-16:/fulfilling-childhood-dreams-solar/</id><summary type="html">&lt;p&gt;Ever since I was an 8 year old boy I have wanted solar. I remember reading about the environment and alternative energy sources in a monthly &amp;quot;socal studies&amp;quot; flyer my school subscribed our classroom to. I questioned, even at my young age, why the world wasn't actively switching over to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ever since I was an 8 year old boy I have wanted solar. I remember reading about the environment and alternative energy sources in a monthly &amp;quot;socal studies&amp;quot; flyer my school subscribed our classroom to. I questioned, even at my young age, why the world wasn't actively switching over to solar, water, and wind!&lt;/p&gt;
&lt;p&gt;25 years later I have finally fulfilled one of my childhood dreams: I installed a 11.375kWh solar system on my roof.&lt;/p&gt;
&lt;p&gt;This post will act as an overview of the stuff I learned about solar, I hope you learn something too!&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#solar-monitoring" id="toc-entry-1"&gt;Solar Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#home-energy-monitoring" id="toc-entry-2"&gt;Home Energy Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#solar-financing" id="toc-entry-3"&gt;Solar Financing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#contact-me" id="toc-entry-4"&gt;Contact me&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Disclosure: This post contains affiliate links.&lt;/em&gt; See &lt;a class="reference external" href="/disclosures-and-terms/"&gt;full disclosure page here&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="325 watt panasonic solar Panels on front of house" src="/uploads/2018/panasonic-325watt-panels-front.jpg" /&gt;
&lt;img alt="325 watt panasonic solar Panels on back of house" src="/uploads/2018/panasonic-325watt-panels-back.jpg" /&gt;
&lt;div class="section" id="solar-monitoring"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Solar Monitoring&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The system is composed 35 x Panasonic 325 watt panels (VBHN325SA16). The panels are isolated into 3 arrays (&amp;quot;strings&amp;quot;) of 8, 13, and 14.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# 35 panels x 325 watts ~= 11.375kWh&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;.325&lt;/span&gt;
&lt;span class="mf"&gt;11.375&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each panel has an &amp;quot;optimizer&amp;quot; which communicates to the centralized SolarEdge inverter installed in my basement near the breaker box. The optimizers allow each array to work efficently even when a panel is shaded or broken.&lt;/p&gt;
&lt;p&gt;Here is the layout of the panels on my house:&lt;/p&gt;
&lt;img alt="My 11.375kWh Solar Panel Layout" src="/uploads/2018/11kWh-solar-panel-layout.png" /&gt;
&lt;p&gt;Additionally the optimizers emit energy production metrics to the inverter, which in turn forwards this data to &amp;quot;the cloud&amp;quot; (aka SolarEdge Monitoring System). From there, I can watch daily playbacks of the whole system.&lt;/p&gt;
&lt;p&gt;For example, this playback from March 19th, 2018 was a good &amp;quot;solar day&amp;quot; as my family calls:&lt;/p&gt;
&lt;img alt="My 11.375kWh Solar Playback of March 19th, 2018" src="/uploads/2018/solar-playback-2018-03-19.gif" /&gt;
&lt;p&gt;You can see how the sun rises to cover the 8 panels on the front of the house and then later moves to cover the panels on the back of the house.&lt;/p&gt;
&lt;img alt="325 watt panasonic solar Panels on back of house" src="/uploads/2018/solaredge-10k-central-inverter.jpg" /&gt;
&lt;p&gt;SolarEdge Monitoring Dashboard also keeps track of various high level metrics. For example, I input my electricity rates with date ranges and the dashboard calculates the &amp;quot;lifetime revenue&amp;quot; of my solar system. I plan to track my ROI on this number!&lt;/p&gt;
&lt;img alt="" src="/uploads/2018/2018-03-16-solar-overview.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="home-energy-monitoring"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Home Energy Monitoring&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most of my key insights into home energy actually come from another device I had installed called &lt;a class="reference external" href="https://www.amazon.com/gp/product/B015IY0Z3E/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B015IY0Z3E&amp;amp;linkCode=as2&amp;amp;tag=russellball0b-20&amp;amp;linkId=727da547a2b0a22fa53016191c2cf313"&gt;Curb Home Energy Monitor&lt;/a&gt;. Curb uses a bunch of non-invasive CT clamps on each breaker circut to gather consumption. Metrics are gathered and sent to &amp;quot;the cloud&amp;quot; and power some really cool realtime dashboards.&lt;/p&gt;
&lt;p&gt;For example, this chart shows solar production versus consumption in dollars for the last 15 days:&lt;/p&gt;
&lt;img alt="15 day solar production versus household breaker consumption" src="/uploads/2018/solar-15-day-production-consumption-in-dollars.png" /&gt;
&lt;p&gt;As you can see, about &lt;strong&gt;50% of my solar production is being consumed by heating hot water&lt;/strong&gt; for a family of 5!&lt;/p&gt;
&lt;p&gt;This feels absurd...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I switched to a &lt;a class="reference external" href="/hybrid-hot-water-heater-saves-69-percent-on-energy-consumption/"&gt;Hybrid Water Heater&lt;/a&gt; and wrote a post to show how I now use 69% less electricity when heating water!&lt;/p&gt;
&lt;p&gt;Anyways, here is a short video showing the Curb user interface.&lt;/p&gt;
&lt;center&gt;
&lt;video src="/uploads/2018/curb-user-interface.webm" width="620px" controls&gt;
Sorry, your browser doesn't support embedded videos,
but don't worry, you can &lt;a href="/uploads/2018/curb-user-interface.webm"&gt;download it&lt;/a&gt;
and watch it with your favorite video player!
&lt;/video&gt;
&lt;/center&gt;&lt;p&gt;The &lt;a class="reference external" href="https://www.amazon.com/gp/product/B015IY0Z3E/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B015IY0Z3E&amp;amp;linkCode=as2&amp;amp;tag=russellball0b-20&amp;amp;linkId=727da547a2b0a22fa53016191c2cf313"&gt;Curb&lt;/a&gt; is likely the most accurate home energy monitor on the market. The draw back is the cost of parts and labor; I used my solar installer's electrician and the total cost was $800.&lt;/p&gt;
&lt;p&gt;The competition has less accuracy but I could have likely installed a Sense myself and saved on the labor cost.&lt;/p&gt;
&lt;p&gt;Here are the two other brands I was looking at, for reference:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.amazon.com/gp/product/B075K6PHJ9/ref=as_li_tl?ie=UTF8&amp;amp;tag=russellball0b-20&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;linkCode=as2&amp;amp;creativeASIN=B075K6PHJ9&amp;amp;linkId=cc8e52d403b4b24da1f7b6a27a96ff74"&gt;Sense Home Energy Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.amazon.com/gp/product/B0149EE5KS/ref=as_li_tl?ie=UTF8&amp;amp;tag=russellball0b-20&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;linkCode=as2&amp;amp;creativeASIN=B0149EE5KS&amp;amp;linkId=7e3e5d1063e980892649ea98351034bd"&gt;Neurio Home Electricity Monitor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="solar-financing"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Solar Financing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When talking with Tesla, I fully expected to pay for my solar system in full with cash savings. It wasn't until I reached out to &lt;a class="reference external" href="http://sunlightsolar.com"&gt;SunLight Solar&lt;/a&gt; did I change my strategy. SunLight urged me to take advantage of the &amp;quot;.99% CT Green Bank&amp;quot; finance option. I took out a loan for the whole project and dumped my cash savings into my home mortgage as a bulk payment to the principle.&lt;/p&gt;
&lt;p&gt;Additionally, the CT Green Bank granted me $3,600 toward my project and the US federal government will grant 30% or $8,400 on my next tax return.&lt;/p&gt;
&lt;p&gt;After all the incentives, the parts and labor of my system came in just under $20,000:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$32,000 - $3,600 - 8,400 = $20,000
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Putting solar on my house actually opened up my financial options and diversified my portfolio!&lt;/p&gt;
&lt;p&gt;I now have:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a power plant on my roof with an expected 9-10 year ROI; after 10 years I'll be generating wealth, capital, and positive &amp;quot;cash flow&amp;quot; in the form of energy&lt;/li&gt;
&lt;li&gt;paid down my 4.125% house mortgage by $35,000; saving tens of thousands over the life of the loan&lt;/li&gt;
&lt;li&gt;increased the value of my house by $20-30,000; this is an asset I can sell with or without my house&lt;/li&gt;
&lt;li&gt;shielded or insulated myself from electricity rate hikes; who knows what electricity will cost in 5 to 10 years&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="contact-me"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Contact me&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As always, please feel free to leave comments below. I live in New England so you may also &lt;a class="reference external" href="/contact/"&gt;contact me&lt;/a&gt; to setup a time to tour my setup and ask questions. I look forward to meeting you!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Solar"></category><category term="Project"></category></entry><entry><title>How-to Work From Home</title><link href="https://russell.ballestrini.net/how-to-work-from-home-the-road-to-remote-chapter-1/" rel="alternate"></link><published>2017-11-09T09:51:00-05:00</published><updated>2017-11-09T09:51:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-11-09:/how-to-work-from-home-the-road-to-remote-chapter-1/</id><summary type="html">&lt;p&gt;&amp;quot;I work from home&amp;quot; — a phrase I have uttered hundreds of times and is often met with instant amazement, envy, or jokes about pants. My goal for this book is to teach you the hacks I have learned in my career to help you land your dream job. If you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&amp;quot;I work from home&amp;quot; — a phrase I have uttered hundreds of times and is often met with instant amazement, envy, or jokes about pants. My goal for this book is to teach you the hacks I have learned in my career to help you land your dream job. If you feel stuck in the job market and you don't want to relocate, this is the book for you.&lt;/p&gt;
&lt;div class="section" id="chapter-1-the-road-to-remote"&gt;
&lt;h2&gt;Chapter 1: The Road to Remote&lt;/h2&gt;
&lt;p&gt;So you have made up your mind, you want to work from home.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The first step to attaining this goal is to make it a priority.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The suggestions and advice in this book come from my life experiences. Not all of my experiences will apply to you but I hope that we might find, together, at least a single thread of universal truth. In order to know, I think it makes sense to briefly review the anatomy of my life from childhood until now.&lt;/p&gt;
&lt;p&gt;I'm currently 32 years old, but I started working with computers at a young age. As an elementary schooler, I had a bright childhood friend named &lt;a class="reference external" href="https://www.youtube.com/watch?v=LlO2_GecWo8"&gt;Brian Brennan&lt;/a&gt; who although younger than I, was my first mentor regarding computers. At age 9 we played lots of video games, original playstation and Nintendo 64, as well as countless hours of computer games on his Gateway computer.&lt;/p&gt;
&lt;p&gt;At one point Brian learned how to make the computer work for him instead of other the other way around. Together we started learning HTML and Javascript. I didn't have the Internet on my family computer at the time, but my parents did have a WebTV so I started building HTML web pages for things I enjoyed and used the WebTV to upload my pages Geocities.&lt;/p&gt;
&lt;p&gt;Flash forward to age 13, I finally had dial-up. Now we didn't pay for dial-up, in fact my parents didn't know I was &amp;quot;connected&amp;quot; as we used to call it, but I was. I had the &amp;quot;setup&amp;quot; credentials for our regional phone company — I was living the dream of free Internet!&lt;/p&gt;
&lt;p&gt;I covertly routed speaker wire from our pantry closet to the computer room. Yes, I learned early on that in a pinch speaker wire can work as telephone wire, although I don't recommend it. This was one of many hacks I stumbled on. Hacks, the combination of science, trial &amp;amp; error, and creativity, are a common theme through out my life.&lt;/p&gt;
&lt;p&gt;I spent my highschool career building HTML pages instead of powerpoints slides for school projects. At home I built my own computers out of parts, as so many of us did back then and gamed all night during LAN parties where we traded &amp;quot;warez&amp;quot; and &amp;quot;pr0n&amp;quot;. I was 15 when we got an A-DSL connection (125kbps down / 15kbps up) and I started hosting my websites from my house on a Windows server hiding in my bedroom closet — later this server was reborn running FreeBSD 3.&lt;/p&gt;
&lt;p&gt;Most of my highschool education was spent figuring out how to get out of class and either into the computer lab or the library with friends. We basically met as an informal computer and tech club. For more details about this era, checkout my long time friend Charles Hooper's blog post titled &lt;a class="reference external" href="http://www.charleshooper.net/blog/how-i-hacked-my-high-school/"&gt;&amp;quot;How I hacked my high school&amp;quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I was going to community college for Computer Science, I worked two part time jobs — I worked for my parents and separately at a large retail chain in the electronics department, where I slung &amp;quot;HDTVs&amp;quot;. I was working 40 hours a week, doing 4 courses a semester, and I didn't have time for anything.&lt;/p&gt;
&lt;p&gt;I started hosting my parent's small family business website which I coded by hand and I mostly resented my mall job. I hated how cruel companies were and how they actively shit on their employees. I spent my lunch breaks in Borders reading computer and business books and dreaming of my perfect job.&lt;/p&gt;
&lt;p&gt;In between Calculus and selling TVs, I also found time to build the best damn real estate search engine a local agency in South Eastern Connecticut could buy. I was taken advantage of; I worked at least 500 hours on that site which was commissioned for under $500. I did however learn a lot about business and tech. I was brave and dumb and took on more then I could chew but it was successful. Hands down it was the best solution on the market at the time, this was long before Zillow existed.&lt;/p&gt;
&lt;p&gt;While working in the retail store, I met George, an IT guy working for my region's pharmaceutical company. George referred me to a job making $18/hr migrating Windows 2000 computers to Windows XP. I accepted the contracting job thinking I was on the top of the world, I finally had a &amp;quot;real&amp;quot; computer job.&lt;/p&gt;
&lt;p&gt;I did awesome work but what I didn't know was this world is cruel to contractors. They don't get sick time, they don't get vacation, and they certainly don't get respect. Contractors are fed lies and false promises and they are the first to get cut. Managers treat contractors like physical resources. It's honestly sick.&lt;/p&gt;
&lt;p&gt;From there I moved to a technical call center helpdesk, also as a contractor, of a contractor, of a contractor of the DoD. If you follow the money, I was making $17/hr but at the top of the pyramid they were billing me out at $55/hr. After some time I managed to move onsite and successfully cut out one of the contractor layers.&lt;/p&gt;
&lt;p&gt;I still lacked negotiation skills and confidence so my increase was only a dollar, bringing me to $18/hr as a desktop support tech. I fixed computer problems whether it was hardware or software and I was fast and could basically solve anything handed to me. I quickly transitoned to a desktop support engineer role where I learned how to package and push software to small fleets of computers. A couple years later I transitioned to the Unix server team.&lt;/p&gt;
&lt;p&gt;Each step was a very big promotion as far as responsibility (I was one of two employees who designed and operated a multi million dollar Hitachi SAN) but my salary didn't increase proportionally. People were coming &amp;quot;off the street&amp;quot; for the same or lower position and making more then double I was. At the time I was working my ass off for $32k a year and hardly had enough for what my family needed. I was watching people get hired for the same job for $65k a year ... After being let down for so long, this was the tipping point, this is where I started to shift my thinking.&lt;/p&gt;
&lt;blockquote&gt;
The only way to move up is to move out.&lt;/blockquote&gt;
&lt;p&gt;I felt stuck. I worked for both the big industries for my region and they both failed me. I knew I did not want to relocate but I also didn't want to work for companies with obscene priorities and worse culture.&lt;/p&gt;
&lt;p&gt;I started moonlighting on side projects. I had so many flops and failures, to many to count. Finally I launched an idea with traction called &lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek&lt;/a&gt;. During the code development phase of building LinkPeek I became active in the open source community for the framework I was using called &lt;a class="reference external" href="https://trypyramid.com/"&gt;Pyramid&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was networking, even though I didn't know what it was called at the time. I taught myself some tricks to &lt;a class="reference external" href="/career-development-is-a-game-of-chutes-and-ladders/"&gt;career development&lt;/a&gt; and ended up meeting a hiring manager who appreciated my skill and was willing to take a chance with me working remote. I flew across the country to Santa Monica and had a bit of culture shock but I landed the position!&lt;/p&gt;
&lt;p&gt;This victory would be the first of 4 remote positions I have held since finally taking the leap to &amp;quot;work from home&amp;quot;.&lt;/p&gt;
&lt;p&gt;Tune in to the next chapter where I describe how I positioned myself to win the interview.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="guide"></category></entry><entry><title>webwords: a minimal viable web app with docker in as many languages as possible</title><link href="https://russell.ballestrini.net/webwords-is-a-minimal-viable-web-app-with-docker-in-as-many-languages-as-possible/" rel="alternate"></link><published>2017-10-24T12:11:00-04:00</published><updated>2017-10-24T12:11:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-10-24:/webwords-is-a-minimal-viable-web-app-with-docker-in-as-many-languages-as-possible/</id><summary type="html">&lt;p&gt;&lt;strong&gt;The companion&lt;/strong&gt; &lt;a class="reference external" href="https://github.com/russellballestrini/webwords"&gt;webwords git repo&lt;/a&gt; &lt;strong&gt;lives here.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project shows how to code the same minimal web app called &lt;tt class="docutils literal"&gt;webwords&lt;/tt&gt; in as many different programming languages as possible.
It also provides guides for building and running &lt;tt class="docutils literal"&gt;webwords&lt;/tt&gt; as a docker image.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-is-webwords" id="toc-entry-1"&gt;what is webwords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-is-webwords" id="toc-entry-2"&gt;why is webwords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#go" id="toc-entry-3"&gt;go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python" id="toc-entry-4"&gt;python …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;The companion&lt;/strong&gt; &lt;a class="reference external" href="https://github.com/russellballestrini/webwords"&gt;webwords git repo&lt;/a&gt; &lt;strong&gt;lives here.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project shows how to code the same minimal web app called &lt;tt class="docutils literal"&gt;webwords&lt;/tt&gt; in as many different programming languages as possible.
It also provides guides for building and running &lt;tt class="docutils literal"&gt;webwords&lt;/tt&gt; as a docker image.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#what-is-webwords" id="toc-entry-1"&gt;what is webwords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-is-webwords" id="toc-entry-2"&gt;why is webwords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#go" id="toc-entry-3"&gt;go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#python" id="toc-entry-4"&gt;python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#ruby" id="toc-entry-5"&gt;ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#js" id="toc-entry-6"&gt;js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#debugging" id="toc-entry-7"&gt;debugging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-is-webwords"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;what is webwords&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A simple web application whose spec accepts the following two query parameters —&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;keyword:&lt;/dt&gt;
&lt;dd&gt;The &lt;tt class="docutils literal"&gt;keyword&lt;/tt&gt; you want to search for.&lt;/dd&gt;
&lt;dt&gt;target:&lt;/dt&gt;
&lt;dd&gt;The URI &lt;tt class="docutils literal"&gt;target&lt;/tt&gt; that you want to search.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The application always returns an &lt;tt class="docutils literal"&gt;HTTP 200&lt;/tt&gt; response with the string &lt;tt class="docutils literal"&gt;true&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;false&lt;/tt&gt; depending on if the keyword is found in the &lt;tt class="docutils literal"&gt;target&lt;/tt&gt; web page body.&lt;/p&gt;
&lt;p&gt;For example, to see if the word &lt;tt class="docutils literal"&gt;potato&lt;/tt&gt; exists on &lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt;, put the follwing in a browser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;http://127.0.0.1:32779/?keyword=potato&amp;amp;target=https://www.remarkbox.com
&lt;/pre&gt;&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Spoiler:&lt;/dt&gt;
&lt;dd&gt;potato does exist on Remarkbox, : )&lt;/dd&gt;
&lt;dt&gt;Note:&lt;/dt&gt;
&lt;dd&gt;You will need to replace the port of &lt;tt class="docutils literal"&gt;32779&lt;/tt&gt; with the port from the &lt;tt class="docutils literal"&gt;docker ps&lt;/tt&gt; output.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="why-is-webwords"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;why is webwords&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Webwords started as a programming Kata to practice writing code in different programming languages. Each port of webwords should behave the same to make comparing and functional testing simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;why did you choose this programming problem?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I think the spec of webwords is small enough for people new to any language to digest but complete in that it does something useful and demonstrates two common tasks: running an HTTP server and using an HTTP client.&lt;/p&gt;
&lt;p&gt;Also, I needed a way to verify if a user had possession of a domain name for the &lt;a class="reference external" href="https://www.remarkbox.com"&gt;comment service&lt;/a&gt; I'm building and chose to code this verification program as a micro service, first with Python and later with Go. The tiny end result was webwords.&lt;/p&gt;
&lt;p&gt;Shortly after, during a hackathon I used webwords to learn how to build Docker images for various languages and formalized the idea into a single project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's next?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Webwords is for tinkering. If you want to add a version or touch up an existing version, send a PR.
Maybe a future fork will show a guide for adding a cache layer or teach how to add logging or gather metrics.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="go"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;go&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build the docker image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go
docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;webwords-go&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To run a test container from the new image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;webwords-go
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="python"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;python&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build the docker image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python
docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;webwords-python&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To run a test container from the new image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;webwords-python
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="ruby"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;ruby&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build the docker image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ruby
docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;webwords-ruby&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To run a test container from the new image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;webwords-ruby
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="js"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;js&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build the docker image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;js
docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;webwords-js&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To run a test container from the new image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;webwords-js
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="debugging"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;debugging&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you're anything like me, your programs rarely compile or work properly on the first try.
Just like with programming, a docker image will rarely build correct the first time so you will need to learn how to debug.&lt;/p&gt;
&lt;p&gt;To debug, get the failed docker container's id:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;--all
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you have the id, you can run the following to see the error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;container-id&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Debug the issue, fix your &lt;tt class="docutils literal"&gt;Dockerfile&lt;/tt&gt;, and retry the build process until you have it working.&lt;/p&gt;
&lt;p&gt;You can delete old attempts by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;container-id&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>So You're Planning a Beta Test</title><link href="https://russell.ballestrini.net/so-you-are-planning-a-beta-test/" rel="alternate"></link><published>2017-10-22T13:20:00-04:00</published><updated>2017-10-22T13:20:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-10-22:/so-you-are-planning-a-beta-test/</id><summary type="html">&lt;p&gt;I'm running a beta for &lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt; and here is my biggest take away:&lt;/p&gt;
&lt;blockquote&gt;
Always collect information from potential customers as soon as possible.
Catch customers while they pursue a solution to their problems.&lt;/blockquote&gt;
&lt;p&gt;What do I mean?&lt;/p&gt;
&lt;p&gt;Well for starters, don't just ask for an email address, have them fill …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm running a beta for &lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt; and here is my biggest take away:&lt;/p&gt;
&lt;blockquote&gt;
Always collect information from potential customers as soon as possible.
Catch customers while they pursue a solution to their problems.&lt;/blockquote&gt;
&lt;p&gt;What do I mean?&lt;/p&gt;
&lt;p&gt;Well for starters, don't just ask for an email address, have them fill out a short survey right away.
If you only ask for an email, your out of luck if you need more information; people do not often respond later.&lt;/p&gt;
&lt;p&gt;I know, I know ... nobody likes to fill out surveys, so power-up their motivation with an incentive or discount. Use this survey to earn qualifed leads, remember quality over quantity.&lt;/p&gt;
&lt;p&gt;Come up with a few questions. Keep most of the questions open ended to allow them guide the dialog. Later, use the responses to break the ice when you send one-on-one emails. Each lead will present a unique situation, so take advantage of this and keep your on boarding emails personal!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; - Gather interest for the Beta as soon as possible and ask questions up front as part of the sign up.&lt;/p&gt;
</content><category term="misc"></category><category term="Remarkbox"></category></entry><entry><title>My Search for the Perfect Alternative Hosted Comment System</title><link href="https://russell.ballestrini.net/my-search-for-the-perfect-alternative-hosted-comment-system/" rel="alternate"></link><published>2017-10-08T10:47:00-04:00</published><updated>2017-10-08T10:47:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-10-08:/my-search-for-the-perfect-alternative-hosted-comment-system/</id><summary type="html">&lt;p&gt;Nearly 6 years ago I launched the beta for &lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek, a web page screenshot service&lt;/a&gt;. After all this time, I'm writing to share something new I've been passionately working on&amp;nbsp;called Remarkbox.&lt;/p&gt;
&lt;p&gt;I started working on &lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt; back in 2014 to solve a problem I had after moving my &lt;a class="reference external" href="/migrating-from-wordpress-to-pelican/"&gt;Wordpress …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Nearly 6 years ago I launched the beta for &lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek, a web page screenshot service&lt;/a&gt;. After all this time, I'm writing to share something new I've been passionately working on&amp;nbsp;called Remarkbox.&lt;/p&gt;
&lt;p&gt;I started working on &lt;a class="reference external" href="https://www.remarkbox.com"&gt;Remarkbox&lt;/a&gt; back in 2014 to solve a problem I had after moving my &lt;a class="reference external" href="/migrating-from-wordpress-to-pelican/"&gt;Wordpress blog to a static site&lt;/a&gt;. I knew my readers would still want to communicate with me so I started the search for the &lt;strong&gt;perfect hosted comment system&lt;/strong&gt; to promote discussion.&lt;/p&gt;
&lt;p&gt;My research found solutions which would slow down my site, or worse, serve ads! That very day, I set out to build my own solution because -&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;How long could it&amp;nbsp;take to build a&amp;nbsp;comment system?&amp;quot; &amp;nbsp;...&lt;/blockquote&gt;
&lt;p&gt;3 years later, I'm finally ready to share Remarkbox: a hosted comment service that &lt;strong&gt;embeds in your pages&lt;/strong&gt; to keep the conversation in the same place as your content.&lt;/p&gt;
&lt;p&gt;Remarkbox &lt;strong&gt;increases user engagement&lt;/strong&gt; because it:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;allows a visitor to discuss your content right away without an account&lt;/li&gt;
&lt;li&gt;supports &lt;strong&gt;Markdown&lt;/strong&gt; with real-time comment previews&lt;/li&gt;
&lt;li&gt;supports deeply nested replies and has an orderly user interface&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;... and the best part - fast page load speeds and&amp;nbsp;absolutely&amp;nbsp;&lt;strong&gt;NO ADS!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm in the process of hand selecting group of users to help me test. Would you be interested in joining a list to hear more about it?&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.remarkbox.com#beta"&gt;Yes! I would like to follow the Remarkbox&amp;nbsp;journey.&lt;/a&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;img alt="Remarkbox Logo" class="align-center" src="https://www.remarkbox.com/remarkbox-minified.png" style="width: 260px;" /&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Remarkbox"></category></entry><entry><title>Selenium grid on Kubernetes</title><link href="https://russell.ballestrini.net/selenium-grid-on-kubernetes/" rel="alternate"></link><published>2017-03-23T16:48:00-04:00</published><updated>2017-03-23T16:48:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-03-23:/selenium-grid-on-kubernetes/</id><summary type="html">&lt;p&gt;This post continues from where we left off on
&lt;a class="reference external" href="/minikube/"&gt;the Minikube guide&lt;/a&gt;.
If you do not already have a Kubernetes cluster, you should read that first.&lt;/p&gt;
&lt;p&gt;Selenium Grid allows you to build a cluster of Selenium nodes.
Today we will create a Selenium cluster with 1 hub and 4 nodes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post continues from where we left off on
&lt;a class="reference external" href="/minikube/"&gt;the Minikube guide&lt;/a&gt;.
If you do not already have a Kubernetes cluster, you should read that first.&lt;/p&gt;
&lt;p&gt;Selenium Grid allows you to build a cluster of Selenium nodes.
Today we will create a Selenium cluster with 1 hub and 4 nodes on Kubernetes.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#selenium-hub" id="toc-entry-1"&gt;Selenium Hub&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#selenium-hub-overlearning" id="toc-entry-2"&gt;Selenium Hub Overlearning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#selenium-node" id="toc-entry-3"&gt;Selenium Node&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#selenium-node-overlearning" id="toc-entry-4"&gt;Selenium Node Overlearning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#selenium-scale" id="toc-entry-5"&gt;Selenium Scale&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="selenium-hub"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Selenium Hub&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let's launch the hub:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;selenium-hub&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="w"&gt; &lt;/span&gt;selenium/hub:2.53.1&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4444&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To access the &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt; externally, we need to expose it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;services
kubectl&lt;span class="w"&gt; &lt;/span&gt;expose&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;selenium-hub&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="o"&gt;=&lt;/span&gt;NodePort
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;services
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;My &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt; was exposed here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;http://192.168.99.100:31136/grid/console
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To find out where your &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt; was exposed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;selenium-hub&lt;span class="w"&gt; &lt;/span&gt;--url
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You may open this URI in a web browser.&lt;/p&gt;
&lt;div class="section" id="selenium-hub-overlearning"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Selenium Hub Overlearning&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can skip this section or try it for extra credit.&lt;/p&gt;
&lt;p&gt;We may use &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; exec for container introspection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selenium-hub-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see we have a &lt;tt class="docutils literal"&gt;java&lt;/tt&gt; process running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selenium-hub-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux
USER&lt;span class="w"&gt;       &lt;/span&gt;PID&lt;span class="w"&gt; &lt;/span&gt;%CPU&lt;span class="w"&gt; &lt;/span&gt;%MEM&lt;span class="w"&gt;    &lt;/span&gt;VSZ&lt;span class="w"&gt;   &lt;/span&gt;RSS&lt;span class="w"&gt; &lt;/span&gt;TTY&lt;span class="w"&gt;      &lt;/span&gt;STAT&lt;span class="w"&gt; &lt;/span&gt;START&lt;span class="w"&gt;   &lt;/span&gt;TIME&lt;span class="w"&gt; &lt;/span&gt;COMMAND
seluser&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;18044&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2688&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt;        &lt;/span&gt;Ss&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:20&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;/opt/bin/entry_point.sh
seluser&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2928868&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64864&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt;       &lt;/span&gt;Sl&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;:20&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:13&lt;span class="w"&gt; &lt;/span&gt;java&lt;span class="w"&gt; &lt;/span&gt;-jar&lt;span class="w"&gt; &lt;/span&gt;/opt/selenium/selenium-server-standalone.jar&lt;span class="w"&gt; &lt;/span&gt;-role&lt;span class="w"&gt; &lt;/span&gt;hub&lt;span class="w"&gt; &lt;/span&gt;-hubConfig&lt;span class="w"&gt; &lt;/span&gt;/opt/selenium/config.json
seluser&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;87&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.1&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;34424&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2856&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt;        &lt;/span&gt;Rs&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:54&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:00&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;aux
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We may also look at the config file and test the service internally:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# inspect the selenium config file.&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selenium-hub-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/opt/selenium/config.json

&lt;span class="c1"&gt;# see if selenium is really listening on port 4444.&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selenium-hub-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;127&lt;/span&gt;.0.0.1:4444&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can even shell into the container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;elenium-grid-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;/bin/bash
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Exit out of the container and lets setup some Selenium Nodes!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="selenium-node"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Selenium Node&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lets spin up a Selenium Chrome node:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="w"&gt; &lt;/span&gt;selenium/node-chrome:2.53.1&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HUB_PORT_4444_TCP_ADDR=selenium-hub&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HUB_PORT_4444_TCP_PORT=4444&amp;quot;&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kubernetes will use service discovery to resolve &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;selenium-hub&lt;/span&gt;&lt;/tt&gt; to the service (pods) running the hub!&lt;/p&gt;
&lt;p&gt;If you refresh the hub browser window, you should see a connected Chrome Node, like this:&lt;/p&gt;
&lt;img alt="Selenium Hub with one connected Chrome Node." src="/uploads/2017/selenium-grid-on-kubernetes.png" style="width: 500px;" /&gt;
&lt;div class="section" id="selenium-node-overlearning"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Selenium Node Overlearning&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can skip this section or try it for extra credit.&lt;/p&gt;
&lt;p&gt;The first time I tried to launch a Selenium node and I had trouble.&lt;/p&gt;
&lt;p&gt;I ran this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="w"&gt; &lt;/span&gt;selenium/node-chrome:2.53.1
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The new &lt;tt class="docutils literal"&gt;pod&lt;/tt&gt; went into status &lt;tt class="docutils literal"&gt;CrashLoopBackOff&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;NAME&lt;span class="w"&gt;                                    &lt;/span&gt;READY&lt;span class="w"&gt;     &lt;/span&gt;STATUS&lt;span class="w"&gt;             &lt;/span&gt;RESTARTS&lt;span class="w"&gt;   &lt;/span&gt;AGE
selenium-grid-3216163580-7pqtx&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/1&lt;span class="w"&gt;       &lt;/span&gt;Running&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;3d
selenium-node-chrome-4019562870-mcpfg&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/1&lt;span class="w"&gt;       &lt;/span&gt;CrashLoopBackOff&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;6m
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To troubleshoot, I used the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;pod&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command allowed me to review the Kubernetes level logs.
Everything seemed healthy so next I looked at the Docker level logs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;logs&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome-4019562870-mcpfg
Not&lt;span class="w"&gt; &lt;/span&gt;linked&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;running&lt;span class="w"&gt; &lt;/span&gt;Hub&lt;span class="w"&gt; &lt;/span&gt;container
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, the error &lt;tt class="docutils literal"&gt;Not linked with a running Hub container&lt;/tt&gt; appears when Selenium node cannot find the hub.&lt;/p&gt;
&lt;p&gt;Docker has a &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--link&lt;/span&gt;&lt;/tt&gt; flag to link containers together, Kubernetes doesn't have this.
After some research, it seems &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--link&lt;/span&gt;&lt;/tt&gt; manages ENV vars.&lt;/p&gt;
&lt;p&gt;You can see the environment vars of a &lt;tt class="docutils literal"&gt;pod&lt;/tt&gt; using this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;selenium-hub-3216163580-7pqtx&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;printenv
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I learned that the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;selinum-node-chrome&lt;/span&gt;&lt;/tt&gt; docker image expects some ENV vars and if it doesn't get them, it goes into a crash loop.&lt;/p&gt;
&lt;p&gt;I reached out over IRC in the &lt;tt class="docutils literal"&gt;#kubernetes&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;#selenium&lt;/tt&gt; channels to ask about the ENV vars needed.
A really helpful user named &lt;cite&gt;smccarthy&lt;/cite&gt; linked me to this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;a class="reference external" href="https://github.com/kubernetes/kubernetes/tree/master/examples/selenium"&gt;https://github.com/kubernetes/kubernetes/tree/master/examples/selenium&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Apparently one of the example Kubernetes clusters is a Selenium Grid setup!&lt;/p&gt;
&lt;p&gt;Looking over the example, I found the ENV vars that the selenium-node containers expect: &lt;tt class="docutils literal"&gt;HUB_PORT_4444_TCP_ADDR&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;HUB_PORT_4444_TCP_PORT&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Man, why would they put the port (4444) in the key?&lt;/p&gt;
&lt;p&gt;Anyways, we pass these key/values when creating the container like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="w"&gt; &lt;/span&gt;selenium/node-chrome:2.53.1&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HUB_PORT_4444_TCP_ADDR=selenium-hub&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--env&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HUB_PORT_4444_TCP_PORT=4444&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="selenium-scale"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Selenium Scale&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can scale up and down the Selenium Grid cluster. Lets scale the &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt; to 4 &lt;tt class="docutils literal"&gt;replica&lt;/tt&gt; node-chrome &lt;tt class="docutils literal"&gt;pods&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
kubectl&lt;span class="w"&gt; &lt;/span&gt;scale&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;selenium-node-chrome&lt;span class="w"&gt; &lt;/span&gt;--replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, if you refresh the hub browser window, you should see 4 connected Chrome nodes!&lt;/p&gt;
&lt;img alt="Selenium Hub with four connected Chrome Nodes." src="/uploads/2017/selenium-grid-on-kubernetes-scaled.png" style="width: 500px;" /&gt;
&lt;p&gt;If you liked this post, leave me a message in the comments!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Minikube</title><link href="https://russell.ballestrini.net/minikube/" rel="alternate"></link><published>2017-03-21T12:38:00-04:00</published><updated>2017-03-21T12:38:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2017-03-21:/minikube/</id><summary type="html">&lt;p&gt;Minikube allows you to run a self contained, single node, Kubernetes cluster on your workstation.
Once installed and configured, you may use &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; to interact with it, just like a production Kubernetes cluster.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install" id="toc-entry-1"&gt;install&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-kubectl" id="toc-entry-2"&gt;install kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-minikube" id="toc-entry-3"&gt;install minikube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#start" id="toc-entry-4"&gt;start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#demo" id="toc-entry-5"&gt;demo&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#create-a-deployment" id="toc-entry-6"&gt;create a deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#scale-a-deployment" id="toc-entry-7"&gt;scale a deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#delete-a-deployment" id="toc-entry-8"&gt;delete a deployment …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Minikube allows you to run a self contained, single node, Kubernetes cluster on your workstation.
Once installed and configured, you may use &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; to interact with it, just like a production Kubernetes cluster.&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install" id="toc-entry-1"&gt;install&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-kubectl" id="toc-entry-2"&gt;install kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-minikube" id="toc-entry-3"&gt;install minikube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#start" id="toc-entry-4"&gt;start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#demo" id="toc-entry-5"&gt;demo&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#create-a-deployment" id="toc-entry-6"&gt;create a deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#scale-a-deployment" id="toc-entry-7"&gt;scale a deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#delete-a-deployment" id="toc-entry-8"&gt;delete a deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Reference: &lt;a class="reference external" href="https://kubernetes.io/docs/getting-started-guides/minikube/"&gt;https://kubernetes.io/docs/getting-started-guides/minikube/&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="install"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;install&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Requirements: Virtualbox&lt;/p&gt;
&lt;div class="section" id="install-kubectl"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;install kubectl&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Reference: &lt;a class="reference external" href="https://kubernetes.io/docs/tasks/kubectl/install/"&gt;https://kubernetes.io/docs/tasks/kubectl/install/&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-LO&lt;span class="w"&gt; &lt;/span&gt;https://storage.googleapis.com/kubernetes-release/release/&lt;span class="k"&gt;$(&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;https://storage.googleapis.com/kubernetes-release/release/stable.txt&lt;span class="k"&gt;)&lt;/span&gt;/bin/darwin/amd64/kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="install-minikube"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;install minikube&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Reference: &lt;a class="reference external" href="https://github.com/kubernetes/minikube/releases"&gt;https://github.com/kubernetes/minikube/releases&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-Lo&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;https://storage.googleapis.com/minikube/releases/v0.17.1/minikube-darwin-amd64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="start"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;start&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Supported hypervisors: virtualbox, vmwarefusion, kvm, xhyve&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;--vm-driver&lt;span class="o"&gt;=&lt;/span&gt;virtualbox
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If this is your first time starting minikube, it will perform the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Starting&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Kubernetes&lt;span class="w"&gt; &lt;/span&gt;cluster...
Starting&lt;span class="w"&gt; &lt;/span&gt;VM...
Downloading&lt;span class="w"&gt; &lt;/span&gt;Minikube&lt;span class="w"&gt; &lt;/span&gt;ISO&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;89&lt;/span&gt;.24&lt;span class="w"&gt; &lt;/span&gt;MB&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;89&lt;/span&gt;.24&lt;span class="w"&gt; &lt;/span&gt;MB&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[==============================================]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;.00%&lt;span class="w"&gt; &lt;/span&gt;0s
SSH-ing&lt;span class="w"&gt; &lt;/span&gt;files&lt;span class="w"&gt; &lt;/span&gt;into&lt;span class="w"&gt; &lt;/span&gt;VM...
Setting&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;certs...
Starting&lt;span class="w"&gt; &lt;/span&gt;cluster&lt;span class="w"&gt; &lt;/span&gt;components...
Connecting&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;cluster...
Setting&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;kubeconfig...
Kubectl&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;configured&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;cluster
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also, you should see a new VM running in VirtualBox, like this:&lt;/p&gt;
&lt;img alt="virtualbox running minikube." src="/uploads/2017/virtualbox-running-minikube.png" style="width: 350px;" /&gt;
&lt;p&gt;To verify that &lt;cite&gt;kubectl&lt;/cite&gt; is configured to use minikube look at the config file (&lt;cite&gt;~/.kube/config&lt;/cite&gt;).&lt;/p&gt;
&lt;p&gt;Also try running:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;cite&gt;kubectl get nodes&lt;/cite&gt;&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;kubectl get services&lt;/cite&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also connect to the VirtualBox guest using SSH to have a look around.
In my case the Minikube VM was assigned &lt;tt class="docutils literal"&gt;192.168.99.100&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;~/.minikube/machines/minikube/id_rsa&lt;span class="w"&gt; &lt;/span&gt;docker@192.168.99.100
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can see all the containers running with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;ps
ps&lt;span class="w"&gt; &lt;/span&gt;aux
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Exit out, you really don't need to interact at this level&lt;/p&gt;
&lt;p&gt;Instead we will treat Minikube as a &amp;quot;real&amp;quot; Kubernetes cluster and only use the &lt;tt class="docutils literal"&gt;kubectl&lt;/tt&gt; tool.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="demo"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;demo&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="create-a-deployment"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;create a deployment&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this example we create an echoserver cluster.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;hello-minikube&lt;span class="w"&gt; &lt;/span&gt;--image&lt;span class="o"&gt;=&lt;/span&gt;gcr.io/google_containers/echoserver:1.4&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;this command will create -&lt;/p&gt;
&lt;p&gt;1 &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;deployments
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1 &lt;tt class="docutils literal"&gt;replicaset&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;replicasets
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;1 &lt;tt class="docutils literal"&gt;pod&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To make the echoserver accessible externally, you need to &lt;tt class="docutils literal"&gt;expose&lt;/tt&gt; the &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt;, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;expose&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;hello-minikube&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="o"&gt;=&lt;/span&gt;NodePort
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The expose command creates -&lt;/p&gt;
&lt;p&gt;1 &lt;tt class="docutils literal"&gt;service&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;services
NAME&lt;span class="w"&gt;            &lt;/span&gt;CLUSTER-IP&lt;span class="w"&gt;   &lt;/span&gt;EXTERNAL-IP&lt;span class="w"&gt;   &lt;/span&gt;PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;AGE
kubernetes&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.1&lt;span class="w"&gt;     &lt;/span&gt;&amp;lt;none&amp;gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;/TCP&lt;span class="w"&gt;          &lt;/span&gt;2d
hello-minikube&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.0.0.225&lt;span class="w"&gt;   &lt;/span&gt;&amp;lt;nodes&amp;gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;:31136/TCP&lt;span class="w"&gt;   &lt;/span&gt;58m
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To access the service, you connect to the Minikube's IP address on the exposed port.&lt;/p&gt;
&lt;p&gt;In my case the Minikube VirtualBox IP is &lt;tt class="docutils literal"&gt;192.168.99.100&lt;/tt&gt; and the exposed port is &lt;tt class="docutils literal"&gt;31136&lt;/tt&gt; as listed above.&lt;/p&gt;
&lt;p&gt;The minikube tool has a shortcut for this info, try:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;minikube&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;hello-minikube&lt;span class="w"&gt; &lt;/span&gt;--url
http://192.168.99.100:31136
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Toss this into a web browser on your local machine and it should echo back!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="scale-a-deployment"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;scale a deployment&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Scale up the &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt; named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;hello-minikube&lt;/span&gt;&lt;/tt&gt; by setting the number of &lt;tt class="docutils literal"&gt;replicas&lt;/tt&gt; to &lt;tt class="docutils literal"&gt;3&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;scale&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;hello-minikube&lt;span class="w"&gt; &lt;/span&gt;--replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;verify:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;deployments
kubectl&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;pods
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="delete-a-deployment"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;delete a deployment&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;trash this demo (delete the &lt;tt class="docutils literal"&gt;deployment&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;replicaset&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;pods&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;service&lt;/tt&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;kubectl&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;deployment&lt;span class="w"&gt; &lt;/span&gt;hello-minikube
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Guide"></category><category term="Kubernetes"></category></entry><entry><title>Nginx throw HTTP 503 maintenance JSON for all requests</title><link href="https://russell.ballestrini.net/nginx-throw-503-maintenance-json-for-all-requests/" rel="alternate"></link><published>2016-12-21T15:09:00-05:00</published><updated>2016-12-21T15:09:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-12-21:/nginx-throw-503-maintenance-json-for-all-requests/</id><summary type="html">&lt;p&gt;I found this technique after stumbling on Aaron Parecki's blog.  You can read his post here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://aaronparecki.com/2014/09/03/28/custom-json-503-error-page-nginx-http-content-type-headers"&gt;Setting a custom json 503 error page in nginx with proper http and content-type headers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lets pretend you have an API and you need to turn on maintenance for a major change.
All your …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I found this technique after stumbling on Aaron Parecki's blog.  You can read his post here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://aaronparecki.com/2014/09/03/28/custom-json-503-error-page-nginx-http-content-type-headers"&gt;Setting a custom json 503 error page in nginx with proper http and content-type headers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lets pretend you have an API and you need to turn on maintenance for a major change.
All your client's (phones, web frontends) know what to do when they get an HTTP 503 Error code with JSON body.&lt;/p&gt;
&lt;p&gt;Now you want to cause all requests to get the following JSON -&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;/opt/maint/maint.json&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;errorCode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;maintenance&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;errorDesc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;We are currently performing scheduled maintenance. Please try again later.&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The nginx configuration looks like this -&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;maint.conf&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/opt/maint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;error_page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/maint.json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# all API calls will miss try_files on purpose and return custom 503.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;try_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All API calls will miss &lt;tt class="docutils literal"&gt;try_files&lt;/tt&gt; on purpose and return our custom 503 &lt;tt class="docutils literal"&gt;maint.json&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Proof that this method rocks, it supports &lt;tt class="docutils literal"&gt;GET&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;POST&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;PUT&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;UPDATE&lt;/tt&gt;, and &lt;tt class="docutils literal"&gt;DELETE&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;It also automatically serves the proper &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;Content-Type&lt;/span&gt;&lt;/tt&gt; header, in this case &lt;tt class="docutils literal"&gt;application/json&lt;/tt&gt; -&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl -v -X POST --data &amp;quot;param1=value1&amp;amp;param2=value2&amp;quot; http://127.0.0.1:80/my-very-favorite-api-call

&amp;gt; POST /my-very-favorite-api-call HTTP/1.1
&amp;gt; User-Agent: curl/7.29.0
&amp;gt; Host: 127.0.0.1
&amp;gt; Accept: */*
&amp;gt; Content-Length: 27
&amp;gt; Content-Type: application/x-www-form-urlencoded
&amp;gt;
* upload completely sent off: 27 out of 27 bytes
&amp;lt; HTTP/1.1 503 Service Temporarily Unavailable
&amp;lt; Server: nginx/1.10.2
&amp;lt; Date: Wed, 21 Dec 2016 20:32:44 GMT
&amp;lt; Content-Type: application/json
&amp;lt; Content-Length: 150
&amp;lt; Connection: keep-alive
&amp;lt; ETag: &amp;quot;585addfe-96&amp;quot;
&amp;lt; Retry-After: 30
&amp;lt;
{
  &amp;quot;errorCode&amp;quot;: &amp;quot;maintenance&amp;quot;,
  &amp;quot;errorDesc&amp;quot;: &amp;quot;We are currently performing scheduled maintenance. Please try again later.&amp;quot;
}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Need to make sure you always set a custom header? Don't forget the &lt;tt class="docutils literal"&gt;always&lt;/tt&gt; directive:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
add_header Retry-After 30 always;
&lt;/pre&gt;
&lt;p&gt;Leave a comment if this helps you!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Capability driven Presentation</title><link href="https://russell.ballestrini.net/capability-driven-presentation/" rel="alternate"></link><published>2016-12-18T18:21:00-05:00</published><updated>2016-12-18T18:21:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-12-18:/capability-driven-presentation/</id><summary type="html">&lt;p&gt;A web page does not need to look the same on every browser or device.
We cannot control the capabilities of a user's browser or device.
As web designers, we have the duty to give the viewer the best experience possible.
A user will come with what they have and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A web page does not need to look the same on every browser or device.
We cannot control the capabilities of a user's browser or device.
As web designers, we have the duty to give the viewer the best experience possible.
A user will come with what they have and we should do our best to accommodate.&lt;/p&gt;
&lt;p&gt;TL;DR: Capability drives presentation.&lt;/p&gt;
&lt;div class="section" id="resilient-web-design"&gt;
&lt;h2&gt;Resilient Web Design&lt;/h2&gt;
&lt;p&gt;A resilient web page should:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;have a single canonical URI&lt;/li&gt;
&lt;li&gt;serve the same content, regardless of a viewer's capibilities&lt;/li&gt;
&lt;li&gt;use the viewer's capibilities to gracefully enhance and/or downgrade the presentation of the content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more background and history please read this free online book: &lt;a class="reference external" href="https://resilientwebdesign.com/"&gt;https://resilientwebdesign.com/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="techniques"&gt;
&lt;h2&gt;Techniques&lt;/h2&gt;
&lt;div class="contents local topic" id="contents"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-js-only-class" id="toc-entry-1"&gt;The js-only class&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-js-only-class"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;The js-only class&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Purpose: hide HTML elements when Javascript does not work.&lt;/p&gt;
&lt;p&gt;In this technique we:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;use a &lt;tt class="docutils literal"&gt;&amp;lt;noscript&amp;gt;&lt;/tt&gt; tag to nest a class named &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;js-only&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;declare &lt;tt class="docutils literal"&gt;display: none&lt;/tt&gt; in the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;js-only&lt;/span&gt;&lt;/tt&gt; class to hide elements&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;apply the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;js-only&lt;/span&gt;&lt;/tt&gt; class to any element which should hide without js&lt;/p&gt;
&lt;p&gt;Note: The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;js-only&lt;/span&gt;&lt;/tt&gt; class will only exists when Javascript does not work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;noscript&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;js-only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;noscript&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, suppose we have a notification message which clears when a user clicks 'x'.
To clear the notification, we must use a Javascript &lt;tt class="docutils literal"&gt;onclick&lt;/tt&gt; event handler.&lt;/p&gt;
&lt;p&gt;Without the Javascript capability the notification cannot clear.
Therefore, to prevent confusion we should remove the 'x' from the presentation.&lt;/p&gt;
&lt;p&gt;For example, with Javascript the notification will look like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;img alt="notification with javascript has an x" src="/uploads/2016/12/notification-with-javascript.png" /&gt;
&lt;/blockquote&gt;
&lt;p&gt;But without Javascript the 'x' is removed and the notification will look this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;img alt="notification without javascript does not have an x" src="/uploads/2016/12/notification-without-javascript.png" /&gt;
&lt;/blockquote&gt;
&lt;p&gt;To do this we assign the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;js-only&lt;/span&gt;&lt;/tt&gt; class to our label holding 'x':&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alerts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{%- for message, level in request.session.pop_flash() %}
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alert alert-{{level}}&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;this.style.display=&amp;#39;none&amp;#39;&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;close js-only&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dismiss&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;dismiss&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;x&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    {{message}}
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{%- endfor %}
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The content is the same, but we hide the interface based on the user's capability!
This small change has a huge impact on the overall user experience.&lt;/p&gt;
&lt;p&gt;We no longer present a broken button to the user, and I think that is worth the effort.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>Build RPM or DEB packages for Node.js using Jenkins and FPM</title><link href="https://russell.ballestrini.net/rpm-deb-packages-for-nodejs-using-jenkins-and-fpm/" rel="alternate"></link><published>2016-12-01T15:30:00-05:00</published><updated>2016-12-01T15:30:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-12-01:/rpm-deb-packages-for-nodejs-using-jenkins-and-fpm/</id><summary type="html">&lt;p&gt;This blog post assumes you already have:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a Jenkins master and none or many build servers&lt;/li&gt;
&lt;li&gt;FPM installed on the build servers&lt;/li&gt;
&lt;li&gt;Node.js installed on the build servers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I add &lt;em&gt;jenkins-build.sh&lt;/em&gt; in the root of the Node.js code repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# example usage: JOB_NAME=example-api BUILD_NUMBER=101 bash jenkins-build …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;This blog post assumes you already have:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a Jenkins master and none or many build servers&lt;/li&gt;
&lt;li&gt;FPM installed on the build servers&lt;/li&gt;
&lt;li&gt;Node.js installed on the build servers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I add &lt;em&gt;jenkins-build.sh&lt;/em&gt; in the root of the Node.js code repo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# example usage: JOB_NAME=example-api BUILD_NUMBER=101 bash jenkins-build.sh&lt;/span&gt;

&lt;span class="c1"&gt;# download and build nodejs application and 3rd party modules.&lt;/span&gt;
npm&lt;span class="w"&gt; &lt;/span&gt;rebuild
npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-l

&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;package.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;import sys, json; print json.load(sys.stdin)[&amp;quot;version&amp;quot;]&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# change directory down below checkout directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;..

&lt;span class="c1"&gt;# create an RPM using fpm.&lt;/span&gt;
&lt;span class="c1"&gt;#   JOB_NAME     = jenkins job name&lt;/span&gt;
&lt;span class="c1"&gt;#   BUILD_NUMBER = jenkins auto incremented build number&lt;/span&gt;
/usr/local/bin/fpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;dir&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;rpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--iteration&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$BUILD_NUMBER&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--version&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--vendor&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Example, Inc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--rpm-user&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;--deb-user&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--rpm-group&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--deb-group&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--directories&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/opt/&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;.service=/lib/systemd/system/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;=/opt/&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# make a copy of the rpm with generic name (without version or build).&lt;/span&gt;
cp&lt;span class="w"&gt; &lt;/span&gt;*.rpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;.rpm&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# mv both the rpm and genericly named rpm to the workdir so jenkins can archive it.&lt;/span&gt;
mv&lt;span class="w"&gt; &lt;/span&gt;*.rpm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$JOB_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then in the Jenkins job, I have the following &lt;cite&gt;execute shell&lt;/cite&gt; tasks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# clean out old rpms.&lt;/span&gt;
rm&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;*.rpm

&lt;span class="c1"&gt;# download all 3rd party Node.js modules and package into an installable RPM.&lt;/span&gt;
bash&lt;span class="w"&gt; &lt;/span&gt;jenkins-build.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I then simply upload the rpm to an S3 repo which is accessible to my API hosts.&lt;/p&gt;
&lt;p&gt;The same basic strategy may be used for other languages with subtle differences to the build script.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Register Super Powers with Pyramid add_request_method</title><link href="https://russell.ballestrini.net/register-super-powers-with-pyramid-add-request-method/" rel="alternate"></link><published>2016-11-24T15:30:00-05:00</published><updated>2016-11-24T15:30:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-11-24:/register-super-powers-with-pyramid-add-request-method/</id><summary type="html">&lt;p&gt;The Pyramid web application framework uses a request object to hold state regarding an inbound HTTP connection.
A view must accept a request object as the first argument which makes it always available to our views and templates.&lt;/p&gt;
&lt;p&gt;This behavior rocks, but Pyramid makes it even better by allowing us …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Pyramid web application framework uses a request object to hold state regarding an inbound HTTP connection.
A view must accept a request object as the first argument which makes it always available to our views and templates.&lt;/p&gt;
&lt;p&gt;This behavior rocks, but Pyramid makes it even better by allowing us to enrich the request object!&lt;/p&gt;
&lt;p&gt;As an example, lets pretend we want to randomly generate an integer from 1 to 999 and attach it to each request:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Configurator&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;randint&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; This function returns a Pyramid WSGI application.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# build app config object from ini.&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configurator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_random_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;return a random number.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="c1"&gt;# register request methods.&lt;/span&gt;
   &lt;span class="c1"&gt;# each request instance will run these functions.&lt;/span&gt;
   &lt;span class="c1"&gt;# the result attaches to the request as an attribute.&lt;/span&gt;
   &lt;span class="c1"&gt;# cache the result with `reify=True` to prevent multiple computations.&lt;/span&gt;
   &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_request_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_random_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;random_number&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we have access to this attribute in our views and templates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_number&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This strategy has a number of uses. For example, I use it to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;attach configuration settings and secrets like API keys&lt;/li&gt;
&lt;li&gt;attach a user object, by querying the database for the user_id sourced from the cookie session&lt;/li&gt;
&lt;li&gt;analyze requests for misuse like spam or flooding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What super powers do you register to your request? You should let the world know in the comments.&lt;/p&gt;
&lt;p&gt;Another Pyramid related post: &lt;a class="reference external" href="/sharing-a-pyramid-cookie-with-flask-or-tornado/"&gt;Sharing a Pyramid cookie with Flask or Tornado&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Sharing a Pyramid cookie with Flask or Tornado</title><link href="https://russell.ballestrini.net/sharing-a-pyramid-cookie-with-flask-or-tornado/" rel="alternate"></link><published>2016-11-21T18:35:00-05:00</published><updated>2016-11-21T18:35:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-11-21:/sharing-a-pyramid-cookie-with-flask-or-tornado/</id><summary type="html">&lt;p&gt;Do you have a Pyramid application which authenticates users and uses a signed cookie as a session?
Do you want to build a microservice using another framework and allow it to use the same cookie and session? Me too!&lt;/p&gt;
&lt;p&gt;First we will review a bit of Pyramid code which describes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Do you have a Pyramid application which authenticates users and uses a signed cookie as a session?
Do you want to build a microservice using another framework and allow it to use the same cookie and session? Me too!&lt;/p&gt;
&lt;p&gt;First we will review a bit of Pyramid code which describes the cookie session.&lt;/p&gt;
&lt;div class="section" id="setup-pyramid-signed-cookie-session"&gt;
&lt;h2&gt;Setup Pyramid Signed Cookie Session&lt;/h2&gt;
&lt;p&gt;At the top of your &lt;tt class="docutils literal"&gt;__init__.py&lt;/tt&gt; you will have the following import:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# cookie only session, not encrypted but signed to prevent tampering!&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.session&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SignedCookieSessionFactory&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the &lt;tt class="docutils literal"&gt;main()&lt;/tt&gt; function of &lt;tt class="docutils literal"&gt;__init__.py&lt;/tt&gt; you will create a &lt;tt class="docutils literal"&gt;Configurator&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; This function returns a Pyramid WSGI application.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# setup session factory to use unencrypted but signed cookies.&lt;/span&gt;
    &lt;span class="n"&gt;session_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SignedCookieSessionFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;test-secret&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# build app config object from ini.&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configurator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;session_factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session_factory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see for this test example the &lt;tt class="docutils literal"&gt;secret&lt;/tt&gt; is hardcoded. A real application would define the secret in the .ini and access it using the &lt;tt class="docutils literal"&gt;settings&lt;/tt&gt; object.&lt;/p&gt;
&lt;p&gt;Now all new requests will have a &lt;tt class="docutils literal"&gt;request.session&lt;/tt&gt; attribute.&lt;/p&gt;
&lt;p&gt;You use it like a dictionary, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# set some data into the cookie. for me, this logs the user in.&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;authenticated_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

&lt;span class="c1"&gt;# get some data from the cookie. for me this gets the user_id or None.&lt;/span&gt;
&lt;span class="n"&gt;authenticated_user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;authenticated_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# delete a key / value from cookie. for me this will log out the user.&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;authenticated_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Reference: &lt;a class="reference external" href="https://docs.pylonsproject.org/projects/pyramid/en/latest/api/session.html#pyramid.session.SignedCookieSessionFactory"&gt;SignedCookieSessionFactory&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="teach-tornado-how-to-read-pyramid-cookies"&gt;
&lt;h2&gt;Teach Tornado how to read Pyramid cookies&lt;/h2&gt;
&lt;p&gt;In this section I'll show you how to access and deserialize the Pyramid cookie from a Tornado application.&lt;/p&gt;
&lt;p&gt;To do this, I'm going to extend the Tornado &lt;tt class="docutils literal"&gt;Hello, world&lt;/tt&gt; application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tornado.ioloop&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tornado.web&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MainHandler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ioloop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a simple application which listens to port 8888 and serves the text &lt;tt class="docutils literal"&gt;Hello, world&lt;/tt&gt; when &lt;tt class="docutils literal"&gt;/&lt;/tt&gt; is requested.&lt;/p&gt;
&lt;p&gt;Add the following imports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# accessing Pyramid cookies.&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;webob.cookies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SignedSerializer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.session&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PickleSerializer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.compat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bytes_&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For testing purposes, create a global &lt;tt class="docutils literal"&gt;serializer&lt;/tt&gt; object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# https://docs.webob.org/en/stable/api/cookies.html#webob.cookies.SignedSerializer&lt;/span&gt;
&lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SignedSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test-secret&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyramid.session.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PickleSerializer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Adjust the &lt;tt class="docutils literal"&gt;get&lt;/tt&gt; method in the &lt;tt class="docutils literal"&gt;MainHandler&lt;/tt&gt; to look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw_cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;session&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_cookie&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;session_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_cookie&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;br/&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The complete program follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tornado.ioloop&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tornado.web&lt;/span&gt;

&lt;span class="c1"&gt;# accessing Pyramid cookies.&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;webob.cookies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SignedSerializer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.session&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PickleSerializer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid.compat&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bytes_&lt;/span&gt;

&lt;span class="c1"&gt;# https://docs.webob.org/en/stable/api/cookies.html#webob.cookies.SignedSerializer&lt;/span&gt;
&lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SignedSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test-secret&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pyramid.session.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PickleSerializer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;raw_cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;session&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_cookie&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;session_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_cookie&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;br/&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MainHandler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ioloop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IOLoop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again, we are hardcoding the same &lt;tt class="docutils literal"&gt;secret&lt;/tt&gt;. If you set everything up properly, loading &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://127.0.0.1:8888&lt;/span&gt;&lt;/tt&gt; in a web browser should print the cookie session_data in plain-text.&lt;/p&gt;
&lt;p&gt;In my testing, I saw my cookie and it looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1479520270&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1479516714.062414&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;authenticated_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nodes_pending_verify&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]})&lt;/span&gt;
&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thats all for now, let me know what you think in the comments!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>My first Systemd Service Script and Override</title><link href="https://russell.ballestrini.net/my-first-systemd-service-script-and-override/" rel="alternate"></link><published>2016-10-28T15:54:00-04:00</published><updated>2016-10-28T15:54:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-10-28:/my-first-systemd-service-script-and-override/</id><summary type="html">&lt;p&gt;At work we mostly run Centos and I have some NodeJS services to deploy.
I feel most familiar with Ubuntu / Upstart so this post serves as my notes on systemd.&lt;/p&gt;
&lt;p&gt;In this contrived example, we define a service for our &lt;cite&gt;taco-api&lt;/cite&gt; application.
The &lt;cite&gt;taco-api&lt;/cite&gt; source code lives in &lt;cite&gt;/opt/taco-api …&lt;/cite&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;At work we mostly run Centos and I have some NodeJS services to deploy.
I feel most familiar with Ubuntu / Upstart so this post serves as my notes on systemd.&lt;/p&gt;
&lt;p&gt;In this contrived example, we define a service for our &lt;cite&gt;taco-api&lt;/cite&gt; application.
The &lt;cite&gt;taco-api&lt;/cite&gt; source code lives in &lt;cite&gt;/opt/taco-api&lt;/cite&gt;.&lt;/p&gt;
&lt;p&gt;We manage a static &lt;cite&gt;.service&lt;/cite&gt; file using a package or config management.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;[/usr]/lib/systemd/system/taco-api.service&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Node.js service for taco API&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;

&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;NODE_ENV=development&lt;/span&gt;

&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/node /opt/taco-api/app.js&lt;/span&gt;

&lt;span class="c1"&gt;# Restart service after all crashes but wait 10 seconds between restarts.&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# output stdout and stderr to syslog. (/var/log/[syslog|messages])&lt;/span&gt;
&lt;span class="na"&gt;StandardOutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;syslog&lt;/span&gt;
&lt;span class="na"&gt;StandardError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;syslog&lt;/span&gt;
&lt;span class="na"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%N&lt;/span&gt;

&lt;span class="c1"&gt;# define the user and group to own the process.&lt;/span&gt;
&lt;span class="c1"&gt;#User=node&lt;/span&gt;
&lt;span class="c1"&gt;#Group=node&lt;/span&gt;

&lt;span class="c1"&gt;# change directory before running ExecStart command.&lt;/span&gt;
&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/taco-api&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We also manage a static &lt;cite&gt;override.conf&lt;/cite&gt; file using config management.
In this file, we customize the environment variables present.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;/etc/systemd/system/taco-api.service.d/override.conf&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This allows us to only override and keep track of the deltas.&lt;/p&gt;
&lt;p&gt;You can test, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# emit the status of the service.&lt;/span&gt;
service&lt;span class="w"&gt; &lt;/span&gt;taco-api&lt;span class="w"&gt; &lt;/span&gt;status

&lt;span class="c1"&gt;# start the service.&lt;/span&gt;
service&lt;span class="w"&gt; &lt;/span&gt;taco-api&lt;span class="w"&gt; &lt;/span&gt;start

&lt;span class="c1"&gt;# look at the process list, note that node is running.&lt;/span&gt;
ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;node

&lt;span class="c1"&gt;# emit the status of the service.&lt;/span&gt;
service&lt;span class="w"&gt; &lt;/span&gt;taco-api&lt;span class="w"&gt; &lt;/span&gt;status

&lt;span class="c1"&gt;# start the service.&lt;/span&gt;
service&lt;span class="w"&gt; &lt;/span&gt;taco-api&lt;span class="w"&gt; &lt;/span&gt;stop

&lt;span class="c1"&gt;# look at the process list, note that node is not running.&lt;/span&gt;
ps&lt;span class="w"&gt; &lt;/span&gt;aux&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;node
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thank you!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Set static hostname for RHEL Centos 7 on AWS</title><link href="https://russell.ballestrini.net/set-static-hostname-for-rhel-centos-7-on-aws/" rel="alternate"></link><published>2016-09-06T11:18:00-04:00</published><updated>2016-09-06T11:18:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-09-06:/set-static-hostname-for-rhel-centos-7-on-aws/</id><content type="html">&lt;p&gt;This took me about 2 hours to figure out, hopefully it saves you time.&lt;/p&gt;
&lt;p&gt;/etc/sysconfig/network:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
HOSTNAME=desired-hostname.example.com
&lt;/pre&gt;
&lt;p&gt;/etc/hostname:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
desired-hostname.example.com
&lt;/pre&gt;
&lt;p&gt;/etc/cloud/cloud.cfg:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
preserve_hostname: true
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>If I swallow lots of air will I be lighter?</title><link href="https://russell.ballestrini.net/if-i-swallow-lots-of-air-will-i-be-lighter/" rel="alternate"></link><published>2016-06-22T19:17:00-04:00</published><updated>2016-06-22T19:17:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-06-22:/if-i-swallow-lots-of-air-will-i-be-lighter/</id><summary type="html">&lt;p&gt;After dinner tonight, Carter, my four year old asked me, &amp;quot;If I swallow lots of air will I be lighter?&amp;quot;. I thought about the question for a moment and then told him it depends on what surrounds you.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;If you are surrounded by:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;air and you swallow lots of air …&lt;/li&gt;&lt;/ul&gt;&lt;/dd&gt;&lt;/dl&gt;</summary><content type="html">&lt;p&gt;After dinner tonight, Carter, my four year old asked me, &amp;quot;If I swallow lots of air will I be lighter?&amp;quot;. I thought about the question for a moment and then told him it depends on what surrounds you.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;If you are surrounded by:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;air and you swallow lots of air you will not be lighter.&lt;/li&gt;
&lt;li&gt;water and and swallow lots of air you will be lighter, relative to the water, and you will float better.&lt;/li&gt;
&lt;li&gt;helium and you swallow lots of air you will become heavier, relative to the surrounding helium.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Whaaaaaat???!!?!1!&lt;/p&gt;
&lt;p&gt;: )&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Output all instance identifiers of an AWS VPC to JSON</title><link href="https://russell.ballestrini.net/output-all-instance-identifiers-of-an-aws-vpc-to-json/" rel="alternate"></link><published>2016-06-21T10:25:00-04:00</published><updated>2016-06-21T10:25:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2016-06-21:/output-all-instance-identifiers-of-an-aws-vpc-to-json/</id><summary type="html">&lt;p&gt;At work today I needed an easy way to collect private IP addresses of every instance in one of our production VPCs.&lt;/p&gt;
&lt;p&gt;I ended up adding a tool to &lt;a class="reference external" href="https://botoform.com"&gt;https://botoform.com&lt;/a&gt; to perform this task.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bf&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;aws_profile&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;dump&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;vpc_name_tag&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;instances&lt;span class="w"&gt; &lt;/span&gt;--output-format&lt;span class="w"&gt; &lt;/span&gt;json
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bf&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;customer3&lt;span class="w"&gt; &lt;/span&gt;dump …&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;At work today I needed an easy way to collect private IP addresses of every instance in one of our production VPCs.&lt;/p&gt;
&lt;p&gt;I ended up adding a tool to &lt;a class="reference external" href="https://botoform.com"&gt;https://botoform.com&lt;/a&gt; to perform this task.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bf&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;aws_profile&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;dump&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;vpc_name_tag&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;instances&lt;span class="w"&gt; &lt;/span&gt;--output-format&lt;span class="w"&gt; &lt;/span&gt;json
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bf&lt;span class="w"&gt; &lt;/span&gt;--profile&lt;span class="w"&gt; &lt;/span&gt;customer3&lt;span class="w"&gt; &lt;/span&gt;dump&lt;span class="w"&gt; &lt;/span&gt;prd&lt;span class="w"&gt; &lt;/span&gt;instances&lt;span class="w"&gt; &lt;/span&gt;--output-format&lt;span class="w"&gt; &lt;/span&gt;json&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;~/customer3-prd-instances.json
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Checkout the &lt;a class="reference external" href="https://botoform.readthedocs.io/en/latest/guides/quickstart.html"&gt;Botoform Quickstart&lt;/a&gt;
for installation instructions.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Set DNS resolver options</title><link href="https://russell.ballestrini.net/set-dns-resolver-options/" rel="alternate"></link><published>2015-12-16T10:17:00-05:00</published><updated>2015-12-16T10:17:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-12-16:/set-dns-resolver-options/</id><summary type="html">&lt;p class="first last"&gt;The right way to persist resolver options across many Linux distributions.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I desire the following options in &lt;cite&gt;/etc/resolv.conf&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rotate&lt;span class="w"&gt; &lt;/span&gt;timeout:1&lt;span class="w"&gt; &lt;/span&gt;attempts:1
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="ubuntu-or-debian"&gt;
&lt;h2&gt;Ubuntu or Debian&lt;/h2&gt;
&lt;p&gt;For Ubuntu or Debian based systems, place the options in
&lt;cite&gt;/etc/resolvconf/resolv.conf.d/base&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;rotate&lt;span class="w"&gt; &lt;/span&gt;timeout:1&lt;span class="w"&gt; &lt;/span&gt;attempts:1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These options merge with the options from DHCP and end up in &lt;cite&gt;/etc/resolv.conf&lt;/cite&gt;.&lt;/p&gt;
&lt;p&gt;Then restart the host, or at least networking.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="redhat-or-centos-or-fedora"&gt;
&lt;h2&gt;Redhat or Centos or Fedora&lt;/h2&gt;
&lt;p&gt;For Redhat, Centos or Fedora, add the following to &lt;cite&gt;/etc/sysconfig/network&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;RES_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rotate timeout:1 attempts:1&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then restart the host, or at least networking.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Operations"></category><category term="Guide"></category></entry><entry><title>Bind9 on Joyent Triton</title><link href="https://russell.ballestrini.net/bind9-on-joyent-triton/" rel="alternate"></link><published>2015-11-29T18:47:00-05:00</published><updated>2015-11-29T18:47:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-11-29:/bind9-on-joyent-triton/</id><summary type="html">&lt;p class="first last"&gt;Tricks to reduce Bind9's large memory footprint on Joyent Triton.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have a single DNS server running on a KVM at DigitalOcean for $5/mo. I might move to Joyent and use 2 x 128M Ubuntu containers for $2.23/mo each.&lt;/p&gt;
&lt;p&gt;In my first test Bind9 (named RNDC) ended up using 111M of memory on a 256M Ubuntu Triton container.&lt;/p&gt;
&lt;p&gt;The large 111M memory footprint correlated with the amount of worker threads running. Bind9 determines the number of worker threads to manage by the number of CPUs detected by the OS.&lt;/p&gt;
&lt;p&gt;In my case, Ubuntu detected 48 CPUs because Joyent containers run on bare-metal.&lt;/p&gt;
&lt;p&gt;We can see this by running the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/proc/cpuinfo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;processor&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wc&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;span class="m"&gt;48&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rndc&lt;span class="w"&gt; &lt;/span&gt;status
version:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.9.5-3ubuntu0.5-Ubuntu&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;id:f9b8a50e&amp;gt;
CPUs&lt;span class="w"&gt; &lt;/span&gt;found:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;
worker&lt;span class="w"&gt; &lt;/span&gt;threads:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;48&lt;/span&gt;
UDP&lt;span class="w"&gt; &lt;/span&gt;listeners&lt;span class="w"&gt; &lt;/span&gt;per&lt;span class="w"&gt; &lt;/span&gt;interface:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
number&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;zones:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;216&lt;/span&gt;
debug&lt;span class="w"&gt; &lt;/span&gt;level:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
xfers&lt;span class="w"&gt; &lt;/span&gt;running:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
xfers&lt;span class="w"&gt; &lt;/span&gt;deferred:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
soa&lt;span class="w"&gt; &lt;/span&gt;queries&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;progress:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
query&lt;span class="w"&gt; &lt;/span&gt;logging&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;OFF
recursive&lt;span class="w"&gt; &lt;/span&gt;clients:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/0/1000
tcp&lt;span class="w"&gt; &lt;/span&gt;clients:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;/100
server&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;running
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To fix this, at least on Ubuntu, we need to pass &lt;cite&gt;-n 2&lt;/cite&gt; to limit the &lt;cite&gt;worker threads&lt;/cite&gt; to 2.&lt;/p&gt;
&lt;p&gt;For Ubuntu, edit &lt;cite&gt;/etc/default/bind9&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-u bind -n 2&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then restart the &lt;cite&gt;bind9&lt;/cite&gt; service and verify using &lt;cite&gt;sudo rndc status&lt;/cite&gt; and &lt;cite&gt;free -m&lt;/cite&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;bind9&lt;span class="w"&gt; &lt;/span&gt;restart
sudo&lt;span class="w"&gt; &lt;/span&gt;rndc&lt;span class="w"&gt; &lt;/span&gt;status
free&lt;span class="w"&gt; &lt;/span&gt;-m
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"></category><category term="Operations"></category></entry><entry><title>Migrating from WordPress to Pelican</title><link href="https://russell.ballestrini.net/migrating-from-wordpress-to-pelican/" rel="alternate"></link><published>2015-11-15T15:39:00-05:00</published><updated>2015-11-15T15:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-11-15:/migrating-from-wordpress-to-pelican/</id><summary type="html">&lt;p class="first last"&gt;Five hints to save time during your migration from WordPress.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Its finally happening. I'm moving this blog from WordPress to Pelican.
This task has persisted on my TODO list for over two years.&lt;/p&gt;
&lt;p&gt;During the process of the move, I'm going to use this post to dump hints:&lt;/p&gt;
&lt;div class="contents topic" id="hints"&gt;
&lt;p class="topic-title"&gt;Hints:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#wordpress-xml-to-json" id="toc-entry-1"&gt;WordPress XML to JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pelican-import" id="toc-entry-2"&gt;Pelican-import&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#add-date-to-post-filenames" id="toc-entry-3"&gt;Add date to post filenames&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#alter-category-to-tags" id="toc-entry-4"&gt;Alter category to tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#alter-attachment-and-image-paths" id="toc-entry-5"&gt;Alter attachment and image paths&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="wordpress-xml-to-json"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;WordPress XML to JSON&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wrote &lt;a class="reference external" href="https://github.com/russellballestrini/wordpress-xml-to-json"&gt;this tool to convert Wordpress XML dumps to JSON&lt;/a&gt;.
The tool is opinionated and removes lots of data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pelican-import"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Pelican-import&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A tool to convert WordPress .xml into .rst or .md files (ReStructuredText or MarkDown) is
&lt;a class="reference external" href="https://docs.getpelican.com/en/latest/importer.html"&gt;pelican-import&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I suggest checking it out, even if you do not plan to use Pelican as your static site generator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-date-to-post-filenames"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Add date to post filenames&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After using &lt;a class="reference external" href="https://docs.getpelican.com/en/latest/importer.html"&gt;pelican-import&lt;/a&gt; I had about 150 &lt;cite&gt;.rst&lt;/cite&gt; files and I decided to put the date in the filename, so I wrote this short bash script tool to do the renames:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;*.rst&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;the_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;:date:&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{ print $2; }&amp;#39;&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$the_date&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="alter-category-to-tags"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Alter category to tags&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;category and tags have different meanings and assumptions between wordpress and pelican.  As a result I decided to change all my categories to tags using this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/:category:/:tags:/g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*.rst
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="alter-attachment-and-image-paths"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Alter attachment and image paths&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;fix paths to images / uploads to remove wp-content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/\/wp-content//g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*.rst
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Boto3 get main route table</title><link href="https://russell.ballestrini.net/boto3-get-main-route-table/" rel="alternate"></link><published>2015-10-16T12:15:00-04:00</published><updated>2015-10-16T12:15:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-10-16:/boto3-get-main-route-table/</id><summary type="html">&lt;p class="first last"&gt;Library work around.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;While developing Botoform I ran into an issue with Boto3 where I
couldn't easily get the &amp;quot;main&amp;quot; route table of a VPC. I ended up adding a
&lt;a class="reference external" href="https://github.com/russellballestrini/botoform/blob/master/botoform/enriched/vpc.py"&gt;get_main_route_table&lt;/a&gt;
method to do the duty.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_main_route_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return the main (default) route table for VPC.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;main_route_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;route_table&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route_tables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;association&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route_table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;associations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;association&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;main_route_table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route_table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_route_table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cannot get main route table! &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_route_table&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;main_route_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That all for now!&lt;/p&gt;
&lt;p&gt;You should read my other Boto related posts for tricks to impress your friends.  : )&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="/setting-region-programmatically-in-boto3/"&gt;Setting region programmatically in Boto3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="/filtering-aws-resources-with-boto3/"&gt;Filtering AWS resources with Boto3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>List all installed package names in Python</title><link href="https://russell.ballestrini.net/list-all-installed-package-names-in-python/" rel="alternate"></link><published>2015-07-04T22:15:00-04:00</published><updated>2015-07-04T22:15:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-07-04:/list-all-installed-package-names-in-python/</id><summary type="html">&lt;p class="first last"&gt;You only need a two lines of code to access package names and versions.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Here we define a function called &lt;cite&gt;pkgs&lt;/cite&gt;.
This function returns a list of package resource objects.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;__import__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pkg_resources&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;working_set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We then define two more functions: &lt;cite&gt;pkg_names&lt;/cite&gt; &amp;amp; &lt;cite&gt;pkg_versions&lt;/cite&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pkg_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="n"&gt;pkg_versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;==&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Last we show how to use invoke these functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pkg_names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ansible&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pycrypto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;PyYAML&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Jinja2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;...truncated...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;virt-back&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Werkzeug&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xmltodict&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pkg_versions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ansible==1.7&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pycrypto==2.6.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;...truncated...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;virt-back==0.1.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xmltodict==0.9.2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Filtering AWS resources with Boto3</title><link href="https://russell.ballestrini.net/filtering-aws-resources-with-boto3/" rel="alternate"></link><published>2015-07-02T17:03:00-04:00</published><updated>2015-07-02T17:03:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-07-02:/filtering-aws-resources-with-boto3/</id><summary type="html">&lt;p class="first last"&gt;References to take you from filtering novice to expert.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This post will be updated frequently when as I learn more about how to
filter AWS resources using Boto3 library.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Filtering VPCs by tags&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this example we want to filter a particular VPC by the &amp;quot;Name&amp;quot; tag
with the value of 'webapp01'.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; import boto3
&amp;gt;&amp;gt;&amp;gt; boto3.setup_default_session(profile_name='project1')
&amp;gt;&amp;gt;&amp;gt; ec2 = boto3.resource('ec2', region_name='us-west-2')
&amp;gt;&amp;gt;&amp;gt; filters = [{'Name':'tag:Name', 'Values':['webapp01']}]
&amp;gt;&amp;gt;&amp;gt; webapp01 = list(ec2.vpcs.filter(Filters=filters))[0]
&amp;gt;&amp;gt;&amp;gt; webapp01.vpc_id
'vpc-11111111'
&lt;/pre&gt;
&lt;p&gt;You can also filter on the value of the 'tag-key' or the 'tag-value'
like so:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; taco_key_filter = [{'Name':'tag-key', 'Values':['taco']}]
&amp;gt;&amp;gt;&amp;gt; nacho_value_filter = [{'Name':'tag-value', 'Values':['nacho']}]
&lt;/pre&gt;
&lt;p&gt;You can also filter on multiple 'Values'. In this example want 2 VPCs
named 'webapp01' and 'webapp02':&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; filters = [{'Name':'tag:Name', 'Values':['webapp01','webapp02']}]
&amp;gt;&amp;gt;&amp;gt; list(ec2.vpcs.filter(Filters=filters))
[ec2.Vpc(id='vpc-11111111'), ec2.Vpc(id='vpc-22222222')]
&lt;/pre&gt;
&lt;p&gt;You can also use the '*' wildcard to glob up results in your filter. In
this example we want all 3 VPCs named 'webapp01', 'webapp02' and
'webapp03':&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; filters = [{'Name':'tag:Name', 'Values':['webapp*']}]
&amp;gt;&amp;gt;&amp;gt; list(ec2.vpcs.filter(Filters=filters))
[ec2.Vpc(id='vpc-11111111'), ec2.Vpc(id='vpc-22222222'), ec2.Vpc(id='vpc-33333333')]
&lt;/pre&gt;
&lt;p&gt;Thats all for now!&lt;/p&gt;
&lt;p&gt;You should read my other Boto related posts for tricks to impress your friends.  : )&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="/setting-region-programmatically-in-boto3/"&gt;Setting region programmatically in Boto3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="/working-with-botocores-awsconfig/"&gt;Working with botocores AWS config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Working with botocore's ~/.aws/config</title><link href="https://russell.ballestrini.net/working-with-botocores-awsconfig/" rel="alternate"></link><published>2015-07-01T18:14:00-04:00</published><updated>2015-07-01T18:14:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-07-01:/working-with-botocores-awsconfig/</id><summary type="html">&lt;p class="first last"&gt;Don't reinvent the wheel, use Botocores Config facilities for work with AWS.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I ran into a &lt;a class="reference external" href="https://github.com/boto/botocore/issues/435"&gt;bug&lt;/a&gt; in
botocore and this post will serve to document a work around as well as
show how to use botocore session object to work with the values stored
in ~/.aws/config.&lt;/p&gt;
&lt;p&gt;Pretend you have an aws config with two accounts for two separate
projects, like so:&lt;/p&gt;
&lt;p&gt;*~/.aws/config:*&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[profile project1]
account_id = 111111111111
aws_access_key_id=THISISNOTMYACCESSKEY1
aws_secret_access_key=THISISNOTMYSECRETKEY1
# Optional, to define default region for this profile.
region=us-west-1

[profile project2]
account_id = 222222222222
aws_access_key_id=THISISNOTMYACCESSKEY2
aws_secret_access_key=THISISNOTMYSECRETKEY2
# Optional, to define default region for this profile.
region=us-west-2
&lt;/pre&gt;
&lt;p&gt;Now instead of using a single object, we create multiple objects, one
for each profile we intend to use.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; import botocore.session
&amp;gt;&amp;gt;&amp;gt; session1 = botocore.session.Session(profile='project1')
&amp;gt;&amp;gt;&amp;gt; session2 = botocore.session.Session(profile='project2')
&amp;gt;&amp;gt;&amp;gt; session1.get_credentials().access_key
'THISISNOTMYACCESSKEY1'
&amp;gt;&amp;gt;&amp;gt; session2.get_credentials().access_key
'THISISNOTMYACCESSKEY2'
&lt;/pre&gt;
&lt;p&gt;Also figured out how to get at the `account_id` integer:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; session1.get_scoped_config()['account_id']
'111111111111'
&lt;/pre&gt;
&lt;p&gt;Here is another algorithm that returns a list of sessions objects, one
for each profile listed in the config.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; import botocore.session
&amp;gt;&amp;gt;&amp;gt; sessions = []
&amp;gt;&amp;gt;&amp;gt; aws_config = botocore.session.get_session().full_config
&amp;gt;&amp;gt;&amp;gt; for profile_name in aws_config['profiles']:
...     session = botocore.session.Session(profile=profile_name)
...     sessions.append(session)
&lt;/pre&gt;
&lt;p&gt;Thats all for now!&lt;/p&gt;
&lt;p&gt;You should read my other Boto related posts for tricks to impress your friends.  : )&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="/setting-region-programmatically-in-boto3/"&gt;Setting region programmatically in Boto3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="/filtering-aws-resources-with-boto3/"&gt;Filtering AWS resources with Boto3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>My Mentor</title><link href="https://russell.ballestrini.net/my-mentor/" rel="alternate"></link><published>2015-06-21T13:36:00-04:00</published><updated>2015-06-21T13:36:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-06-21:/my-mentor/</id><summary type="html"></summary><content type="html">&lt;p&gt;The original purpose of this post was to recognize the teachers and
mentors which have helped shape me. I planned to write about how Mr.
Cassidy, my high school drafting teacher, taught me the importance
precision. I prepared to explain how Mr. Mercuri, my college computer
science professor, ignited my desire to engineer software. But I could
not. I knew deep down that one person in particular affected me more
then the rest.&lt;/p&gt;
&lt;p&gt;This person could fix anything and could even talk through a broken
heart. He didn't know the internals of computers but the hacker spirit
sweats from his veins. He taught me how to problem solve, many times in
unconventional ways. I once watched him clear brush with an old snow
plow and cut our work day in half. If he didn't have access to a tool,
he would furnish one out of raw materials and spare parts, like a real
life MacGyver. With a little bit of thought and ingenuity he built
anything he imagined.&lt;/p&gt;
&lt;p&gt;Sometimes he would use tough love to prove a point. For example, when I
had a sun burn in middle school, he taught me to push through pain and
not let my team down even though my jersey was scratching my raw skin.
He showed me how to work hard and how to have great work ethic. He
taught me the importance of &amp;quot;saving for a rainy day&amp;quot; but also knew how
to have fun and loved parties. He taught me self control and how to make
sacrifices for payouts in the future.&lt;/p&gt;
&lt;p&gt;He taught me the fundamentals of kindness and guided me on the righteous
path. He gave great advice, and even though I didn't always listen, he
respected my thoughts and wishes. If he ever caused an accident he would
always admit fault. He emits honor, honesty, and courage and never lies
to or manipulates the people around him. Although we have had our
differences, I think about you each day.&lt;/p&gt;
&lt;p&gt;I love you. Thank you and happy father's day, dad.&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Change default gateway on all SmartOS Zones and KVM guests</title><link href="https://russell.ballestrini.net/change-default-gateway-on-all-smartos-zones-and-kvm-guests/" rel="alternate"></link><published>2015-06-01T20:34:00-04:00</published><updated>2015-06-01T20:34:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-06-01:/change-default-gateway-on-all-smartos-zones-and-kvm-guests/</id><summary type="html"></summary><content type="html">&lt;p&gt;Today I needed to change the default gateway on every Zone and KVM guest
on my SmartOS hypervisor because I switched my ISP and as a result my
gateway changed from 192.168.1.254 to 192.168.1.1. After changing one
guest I got lazy and put together this script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;update-all-gateways.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
for VM in `vmadm list -p -o alias,uuid`

  do
    # create an array called VM_PARTS splitting on ':'
    IFS=':' VM_PARTS=($VM)

    # create some helper varibles for alias and uuid
    alias=${VM_PARTS[0]}
    uuid=${VM_PARTS[1]}

    mac=`vmadm get $uuid | json nics | json -a mac`

    echo &amp;quot;
{
   \&amp;quot;update_nics\&amp;quot;: [
      {
        \&amp;quot;mac\&amp;quot;: \&amp;quot;$mac\&amp;quot;,
        \&amp;quot;gateway\&amp;quot;: \&amp;quot;192.168.1.1\&amp;quot;
      }
   ]
}
&amp;quot; | vmadm update $uuid
done
&lt;/pre&gt;
&lt;/p&gt;&lt;pre class="literal-block"&gt;
[root&amp;#64;hypervisor /opt/setup-jsons/updates]# bash update-all-gateways.sh
Successfully updated VM 211b992b-a448-40b4-94c9-xxxxxxxxxxxx
Successfully updated VM 31baa6a5-aa98-4750-80df-xxxxxxxxxxxx
Successfully updated VM 65d176b4-c36d-4cbf-b6ed-xxxxxxxxxxxx
Successfully updated VM aa0f603c-9572-4cb0-b96f-xxxxxxxxxxxx
Successfully updated VM ad928301-f3e1-4fe8-a1c1-xxxxxxxxxxxx
Successfully updated VM b82a257e-5628-46db-aee4-xxxxxxxxxxxx
Successfully updated VM da72b638-51de-4d7d-9853-xxxxxxxxxxxx
Successfully updated VM ee42bf30-51ce-4ae2-915b-xxxxxxxxxxxx
&lt;/pre&gt;
&lt;p&gt;You are welcome!&lt;/p&gt;
&lt;p&gt;Also to change the global zone (head node) default gateway, edit
/usbkey/config and then run the following commands:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;hypervisor /]# route delete default 192.168.1.254
delete net default: gateway 192.168.1.254

[root&amp;#64;hypervisor /]# route add default 192.168.1.1
add net default: gateway 192.168.1.1

[root&amp;#64;hypervisor /]# netstat -r
&lt;/pre&gt;
</content><category term="misc"></category><category term="Uncategorized"></category></entry><entry><title>SmartOS Ubuntu guest, apt-get not working because IPv6</title><link href="https://russell.ballestrini.net/smartos-ubuntu-guest-apt-get-not-working-because-ipv6/" rel="alternate"></link><published>2015-05-09T20:29:00-04:00</published><updated>2015-05-09T20:29:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-05-09:/smartos-ubuntu-guest-apt-get-not-working-because-ipv6/</id><summary type="html">&lt;p class="first last"&gt;My brutal yet simple work around.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Turns out I don't have IPv6 setup properly in my network so when apt
attempts to connect to the Internet it tries IPv6 and fails.&lt;/p&gt;
&lt;p&gt;To disable IPv6 on the ubuntu guest, add this to end of /etc/sysctl.conf
and restart the guest:&lt;/p&gt;
&lt;p&gt;sudo vim /etc/sysctl.conf:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;This was a hacky work around mostly because although I desire to have
IPv6 working, I desire to get this VM running more...&lt;/p&gt;
&lt;p&gt;Thanks for reading, leave comments please.&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>A Python script which searches for available interpreters</title><link href="https://russell.ballestrini.net/a-python-script-which-searches-for-available-interpreters/" rel="alternate"></link><published>2015-05-08T10:34:00-04:00</published><updated>2015-05-08T10:34:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-05-08:/a-python-script-which-searches-for-available-interpreters/</id><summary type="html"></summary><content type="html">&lt;p&gt;This post describes how to write a polyglot -- in this case a script
which runs as valid Bash or Python, to search for available Python
interpreters.&lt;/p&gt;
&lt;p&gt;The script initially runs as Bash but upon finding a first match, the
script will call itself again this time using the expected Python
interpreter in interactive mode!&lt;/p&gt;
&lt;p&gt;And now, for the polyglot code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/sh

# reinvoke this script with bpython -i, ipython -i, or python -i
# reference: https://unix.stackexchange.com/a/66242
''':'
if type bpython &amp;gt;/dev/null 2&amp;gt;/dev/null; then
  exec bpython -i &amp;quot;$0&amp;quot; &amp;quot;$&amp;#64;&amp;quot;
elif type ipython &amp;gt;/dev/null 2&amp;gt;/dev/null; then
  exec ipython -i &amp;quot;$0&amp;quot; &amp;quot;$&amp;#64;&amp;quot;
else
  exec python -i &amp;quot;$0&amp;quot; &amp;quot;$&amp;#64;&amp;quot;
fi
'''
from helpers import (
  base_parser,
  get_hvpc_from_args,
)
parser = base_parser('Interactive Python interpreter &amp;amp; connection to hvpc')
args = parser.parse_args()
hvpc = get_hvpc_from_args(args)
print('\nYou now have access to the hvpc object, for example: hvpc.roles\n')
&lt;/pre&gt;
&lt;p&gt;In this case you can see that we setup the interactive interpreter's
environment to create an hvpc (Husky VPC) object for exploration.&lt;/p&gt;
&lt;p&gt;If you want a pure python version that doesn't use a bash/python
polygot, checkout this code I wrote:
&lt;a class="reference external" href="https://github.com/russellballestrini/botoform/blob/master/botoform/plugins/repl.py"&gt;https://github.com/russellballestrini/botoform/blob/master/botoform/plugins/repl.py&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Migrating libvirt KVM guest to SmartOS KVM guest</title><link href="https://russell.ballestrini.net/migrating-libvirt-kvm-guest-to-smartos-kvm-guest/" rel="alternate"></link><published>2015-04-28T22:17:00-04:00</published><updated>2015-04-28T22:17:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-04-28:/migrating-libvirt-kvm-guest-to-smartos-kvm-guest/</id><summary type="html">&lt;p class="first last"&gt;Stop worrying and replace your Linux hypervisor with SmartOS.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The following tutorial documents how to migrate a libvirt/KVM guest from
Ubuntu to SmartOS.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;akuma:&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Ubuntu Hypervisor&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;guy:&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;SmartOS Hypervisor&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;sagat:&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;guest to migrate&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These commands were run on akuma:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
WORKDIR=/KVMROOT/migrate
sudo mkdir $WORKDIR
cd $WORKDIR

# this tool simply stops and tarballs up the qcow and xml for libvirt KVM guest.
sudo /usr/local/bin/virt-back --path=$WORKDIR --backup sagat

sudo tar -xzvf sagat.tar.gz

sudo qemu-img convert -O raw sagat.qcow2 sagat.raw
sudo qemu-img convert -O raw sagat-var.qcow2 sagat-var.raw

scp sagat*.raw root&amp;#64;guy:/opt/tmp
&lt;/pre&gt;
&lt;p&gt;These commands were run on guy:&lt;/p&gt;
&lt;p&gt;Create the following file on guy - setup-ubuntu-sagat.json:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{
  &amp;quot;brand&amp;quot;: &amp;quot;kvm&amp;quot;,
  &amp;quot;resolvers&amp;quot;: [
    &amp;quot;192.168.1.22&amp;quot;,
    &amp;quot;8.8.4.4&amp;quot;
  ],
  &amp;quot;ram&amp;quot;: &amp;quot;4096&amp;quot;,
  &amp;quot;vcpus&amp;quot;: &amp;quot;4&amp;quot;,
  &amp;quot;alias&amp;quot;: &amp;quot;sagat&amp;quot;,
  &amp;quot;hostname&amp;quot;: &amp;quot;sagat&amp;quot;,
  &amp;quot;nics&amp;quot;: [
    {
      &amp;quot;nic_tag&amp;quot;: &amp;quot;admin&amp;quot;,
      &amp;quot;ip&amp;quot;: &amp;quot;192.168.1.50&amp;quot;,
      &amp;quot;netmask&amp;quot;: &amp;quot;255.255.255.0&amp;quot;,
      &amp;quot;gateway&amp;quot;: &amp;quot;192.168.1.254&amp;quot;,
      &amp;quot;model&amp;quot;: &amp;quot;virtio&amp;quot;,
      &amp;quot;primary&amp;quot;: true
    }
  ],
  &amp;quot;disks&amp;quot;: [
    {
      &amp;quot;boot&amp;quot;: true,
      &amp;quot;model&amp;quot;: &amp;quot;virtio&amp;quot;,
      &amp;quot;size&amp;quot;: 10240
    },
    {
      &amp;quot;model&amp;quot;: &amp;quot;virtio&amp;quot;,
      &amp;quot;size&amp;quot;: 20480
    }
  ]
}
&lt;/pre&gt;
&lt;p&gt;We then create a new KVM guest on guy:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;guy /opt/setup-jsons]# vmadm create -f setup-ubuntu-sagat.json
Successfully created VM aa0f603c-9572-4cb0-b96f-4c79eb431223
&lt;/pre&gt;
&lt;p&gt;List out the virtual block devices on the new KVM guest:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;guy /opt/setup-jsons]# vmadm info aa0f603c-9572-4cb0-b96f-4c79eb431223 block
{
  &amp;quot;block&amp;quot;: [
    {
      &amp;quot;device&amp;quot;: &amp;quot;virtio0&amp;quot;,
      &amp;quot;locked&amp;quot;: false,
      &amp;quot;removable&amp;quot;: false,
      &amp;quot;inserted&amp;quot;: {
        &amp;quot;ro&amp;quot;: false,
        &amp;quot;drv&amp;quot;: &amp;quot;raw&amp;quot;,
        &amp;quot;encrypted&amp;quot;: false,
        &amp;quot;file&amp;quot;: &amp;quot;/dev/zvol/rdsk/zones/aa0f603c-9572-4cb0-b96f-4c79eb431223-disk0&amp;quot;
      },
      &amp;quot;type&amp;quot;: &amp;quot;hd&amp;quot;
    },
    {
      &amp;quot;device&amp;quot;: &amp;quot;virtio1&amp;quot;,
      &amp;quot;locked&amp;quot;: false,
      &amp;quot;removable&amp;quot;: false,
      &amp;quot;inserted&amp;quot;: {
        &amp;quot;ro&amp;quot;: false,
        &amp;quot;drv&amp;quot;: &amp;quot;raw&amp;quot;,
        &amp;quot;encrypted&amp;quot;: false,
        &amp;quot;file&amp;quot;: &amp;quot;/dev/zvol/rdsk/zones/aa0f603c-9572-4cb0-b96f-4c79eb431223-disk1&amp;quot;
      },
      &amp;quot;type&amp;quot;: &amp;quot;hd&amp;quot;
    }
  ]
}
&lt;/pre&gt;
&lt;p&gt;Stop the new guest, it needs to be off for the restore.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;guy /opt/setup-jsons]# vmadm stop aa0f603c-9572-4cb0-b96f-4c79eb431223
Successfully completed stop for VM aa0f603c-9572-4cb0-b96f-4c79eb431223
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Use dd to:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;write sagat.raw to disk0&lt;/li&gt;
&lt;li&gt;write sagat-var.raw to disk1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="literal-block"&gt;
dd if=sagat.raw of=/dev/zvol/rdsk/zones/aa0f603c-9572-4cb0-b96f-4c79eb431223-disk0 bs=1M
dd if=sagat-var.raw of=/dev/zvol/rdsk/zones/aa0f603c-9572-4cb0-b96f-4c79eb431223-disk1 bs=1M
&lt;/pre&gt;
&lt;p&gt;Lastly start the new guest up, and check it out:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
vmadm start aa0f603c-9572-4cb0-b96f-4c79eb431223
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Setting region programmatically in Boto3</title><link href="https://russell.ballestrini.net/setting-region-programmatically-in-boto3/" rel="alternate"></link><published>2015-04-24T14:07:00-04:00</published><updated>2015-04-24T14:07:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-04-24:/setting-region-programmatically-in-boto3/</id><summary type="html"></summary><content type="html">&lt;p&gt;At work I'm looking into the possibility of porting parts of our AWS
automation codebase from Boto2 to Boto3. We desire to perform this port
because Boto2's record and result pagination appears defective.&lt;/p&gt;
&lt;p&gt;I started to familiarize myself with Boto3 by using the Interactive Python interpreter.&lt;/p&gt;
&lt;p&gt;Here I show myself trying to connect to the RDS AWS endpoint following the docs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rds&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;NoRegionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;specify&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What, No region? OK - how do I set a region?&lt;/p&gt;
&lt;p&gt;Well it turns out the docs want you to configure a region in a config file.
This will not work for me, I need to set the region programatically...&lt;/p&gt;
&lt;p&gt;So after stumbling around in the botocore source code I found the
following solutions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution 1 - Set region_name when creating client:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rds&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-west-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Solution 2 - Set default region_name on the session:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup_default_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-west-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rds&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It seems Boto3 has two types of interfaces, clients and resources.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Clients:&lt;/dt&gt;
&lt;dd&gt;return description objects and appear lower level.
Description objects seem like AWS XML responses transformed into Python Dicts/Lists.&lt;/dd&gt;
&lt;dt&gt;Resources:&lt;/dt&gt;
&lt;dd&gt;return higher level Python objects and like Instances with stop/start methods.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;At a quick glance, both clients and resources seem to properly implement
pagination automatically!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# client interface.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ec2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-west-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;idesc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;describe_instances&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idesc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Reservations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;273&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;idesc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ResponseMetadata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTPStatusCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;RequestId&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;7e431ff7-xxxx-xxxx-xxxx-xxxxxxxxxxxxx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# resource interface.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ec2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;us-west-2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That all for now!&lt;/p&gt;
&lt;p&gt;You should read my other Boto related posts for tricks to impress your friends.  : )&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="/filtering-aws-resources-with-boto3/"&gt;Filtering AWS resources with Boto3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="/working-with-botocores-awsconfig/"&gt;Working with botocores AWS config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Securely publish Jenkins build artifacts on Salt Master</title><link href="https://russell.ballestrini.net/securely-publish-jenkins-build-artifacts-on-salt-master/" rel="alternate"></link><published>2015-02-22T12:39:00-05:00</published><updated>2015-02-22T12:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-02-22:/securely-publish-jenkins-build-artifacts-on-salt-master/</id><summary type="html">&lt;p class="first last"&gt;Your project deserves an asset pipeline.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Do you want a secure setup for publishing and staging build artifacts
from a Jenkins build server to a Salt Master? This guide describes my
fully automated pipeline to transport binaries using Salt's encrypted
&amp;quot;bus&amp;quot;.&lt;/p&gt;
&lt;p&gt;We start off with some Salt States to stand up a Jenkins build server
&amp;quot;client&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;jenkins/client.sls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# https://russell.ballestrini.net/securely-publish-jenkins-build-artifacts-on-salt-master/
# manage jenkins user, home dir, and Jenkins &amp;quot;master&amp;quot; public SSH key.
jenkins:
  user.present:
    - fullname: jenkins butler
    - shell: /bin/bash
    - home: /home/jenkins

  file.directory:
    - name: /home/jenkins
    - user: jenkins
    - group: jenkins
    - require:
      - user: jenkins

  ssh_auth.present:
    - user: jenkins
    - name: {{ pillar.get('jenkins-public-key') }}
    - require:
      - user: jenkins

# Manage a script to push artifacts to Salt Master.
# Note: jenkins user should _not_ have ability to change this file.
/opt/salt-call-put-artifacts-onto-salt-master.sh:
  file.managed:
    - user: root
    - group: jenkins
    - mode: 755
    - contents: |
        #!/bin/bash
        set -x
        salt-call cp.push_dir &amp;quot;$PWD&amp;quot; glob='*.tar.gz'
        salt-call cp.push &amp;quot;$PWD/commit-hash.txt&amp;quot;
    - require:
      - file: jenkins

# Allow jenkins to run push script as root via sudo.
jenkins-sudoers:
  file.append:
    - name: /etc/sudoers
    - text:
      - &amp;quot;jenkins    ALL = NOPASSWD: /opt/salt-call-put-artifacts-onto-salt-master.sh&amp;quot;
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;On the Salt Master we must enable MinionFS and restart Salt Master
process:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
fileserver_backend:
  - roots
  - minion

file_recv: True
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;We then use this command as the last build task in every Jenkins build
job:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo /home/jenkins/salt-call-put-artifacts-onto-salt-master.sh
&lt;/pre&gt;
&lt;p&gt;This causes the file to be staged on the Salt Master on a successful
build.&lt;/p&gt;
&lt;p&gt;The Salt States to deploy the software to production, look something
like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# extract tarball from Salt Master using MinionFS.
extract-tarball-for-mysite:
  archive.extracted:
    - name: /www/mysite
    - archive_format: tar
    - source: salt://ubuntu-jenkins.foxhop.net/home/jenkins/workspace/job-name/env.tar.gz
    - user: uwsgi
    - group: uwsgi
    # I want to always extract, not sure a better way.
    - if_missing: /dev/taco
    - require:
      - service: make-mysite-dead-for-release
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;I also have build triggers which monitor remote git/hg repos for
changes. Pushing code triggers a build which tests my code base and
securely publishes to my Salt Master. When the time comes to perform a
release, all I have to do is run highstate, because the pipeline did all
the other work for me!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Special thanks to an &lt;a class="reference external" href="https://my.remarkbox.com/r/efbc6dc7-02e3-11e9-b440-040140774501"&gt;anonymous commentor&lt;/a&gt; regarding how to increase security. I have updated the scripts accordingly.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Set postgres user password on PostgreSQL SmartOS Zone</title><link href="https://russell.ballestrini.net/set-postgres-user-password-on-postgresql-smartos-zone/" rel="alternate"></link><published>2015-01-21T22:28:00-05:00</published><updated>2015-01-21T22:28:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-01-21:/set-postgres-user-password-on-postgresql-smartos-zone/</id><summary type="html"></summary><content type="html">&lt;p&gt;Connect to zone and determine the auto generated password for postgres
user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat /var/svc/log/system-zoneinit\:default.log | grep PGSQL_PW
&lt;/pre&gt;
&lt;p&gt;document the result and log into postgres with the following command,
entering the password when prompted:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;psql ~]# psql --user postgres
&lt;/pre&gt;
&lt;p&gt;Alter the postgres role's password:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# ALTER ROLE postgres UNENCRYPTED PASSWORD 'new-password';
&lt;/pre&gt;
&lt;p&gt;Now exit (&lt;tt class="docutils literal"&gt;\q&lt;/tt&gt;) then try to log in with the new password.&lt;/p&gt;
&lt;p&gt;In my case I was setting up PostgreSQL for testing out Zabbix monitoring
system. So I did the following as the postgres user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
postgres=# CREATE USER zab UNENCRYPTED PASSWORD 'zabby'
postgres=# CREATE DATABASE zab OWNER zab;
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Risk, Process, and Balance</title><link href="https://russell.ballestrini.net/risk-process-and-balance/" rel="alternate"></link><published>2015-01-10T22:35:00-05:00</published><updated>2015-01-10T22:35:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2015-01-10:/risk-process-and-balance/</id><summary type="html">&lt;p&gt;The operations of a company will have intrinsic risk. Risk occurs each
time we decide to take an action or an inaction. This means that
anything we choose to do, or not do, has associated risk.&lt;/p&gt;
&lt;p&gt;An organization which has an unhealthy aversion to risk has a much
higher chance …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The operations of a company will have intrinsic risk. Risk occurs each
time we decide to take an action or an inaction. This means that
anything we choose to do, or not do, has associated risk.&lt;/p&gt;
&lt;p&gt;An organization which has an unhealthy aversion to risk has a much
higher chance of failure. As time goes on the intolerance of taking on
additional risk to accomplish goals, will render a company petrified.
Stuck in the fear of change.&lt;/p&gt;
&lt;p&gt;In order to become unstuck, a company and it's employees must become
great at calculating risk and assessment. In order to make a choice on
what to work on, and what to not work on, they will need to research and
collect data and continuously track progress and feedback. The company
must use this data and feedback to move uncalculated bad risk into
calculated good risk. Simply thinking through the problem and change
before hand can uncover ways to accomplish the end result in the least
risky way.&lt;/p&gt;
&lt;p&gt;The biggest objection to the risk assessment phase is typically time
constraints. A great company will learn how to rapidly assess and reduce
the risk of action. They reduce risk by: gathering requirements,
planning, building process, using process to drive automation, and
iterating on automation to speed up feedback loops. These fast feedback
loops will allow the company and it's employees to fail smaller and more
frequently and facilitate reflection to optimize the pipeline. This
means even more calculated and low risk actions!&lt;/p&gt;
&lt;blockquote&gt;
So, a company should add more process to reduce risk? Not quite.&lt;/blockquote&gt;
&lt;p&gt;A process, when left unchecked or added for the wrong reasons, will
become more detrimental then blindly performing an action without
calculating the risk. Below I outline the key differences between a good
process and a bad process.&lt;/p&gt;
&lt;p&gt;A good process will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;reduce risk but not at the expense of blocking or preventing progress&lt;/li&gt;
&lt;li&gt;be fast to facilitate quick feedback loops&lt;/li&gt;
&lt;li&gt;have potential for automation and must not be tedious or manual
(think approvals)&lt;/li&gt;
&lt;li&gt;promote small and frequent changes&lt;/li&gt;
&lt;li&gt;support both fail forward and fail backward&lt;/li&gt;
&lt;li&gt;only fail backward to reduce time-to-recover&lt;/li&gt;
&lt;li&gt;not be pushed down from upper management, rather upper management
should help set goals and requirements, while the team builds and
decides on process&lt;/li&gt;
&lt;li&gt;be questioned and reviewed often to promote iteration to find and fix
inefficiencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A bad process will:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;block real work from getting done&lt;/li&gt;
&lt;li&gt;prevent creative and innovative solutions&lt;/li&gt;
&lt;li&gt;have a higher chance of being ignored, skipped, or held in contempt&lt;/li&gt;
&lt;li&gt;punish people who take risks (make change) and promote people who do
nothing&lt;/li&gt;
&lt;li&gt;cause more issues then it solves&lt;/li&gt;
&lt;li&gt;become a crutch or a security blanket (a false sense of security)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I feel strongly that the essence behind the Agile and DevOps movements
comes from the constant battle of balancing risk with process. This
constant desire for balance in the organization's environment will lead
to innovative ideas, tools, and workflows. These ideas, tools and
workflows can not function properly if transplanted into a company who
does not have the desire to find the optimal balance. Put simply, a
company cannot buy Agile or DevOps, that culture needs to grow from with
in.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Opinion"></category></entry><entry><title>autofs /net automount stopped working</title><link href="https://russell.ballestrini.net/autofs-net-automount-stopped-working/" rel="alternate"></link><published>2014-12-21T22:38:00-05:00</published><updated>2014-12-21T22:38:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-12-21:/autofs-net-automount-stopped-working/</id><summary type="html">&lt;p&gt;So autofs randomly stopped working on one of my Ubuntu hosts (this issue
has been found on Arch as well so its most likely a change upstream). I
found this error in the logs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
attempting to mount entry /net/freenas.example.net
get_exports: lookup(hosts): exports lookup failed for freenas …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;So autofs randomly stopped working on one of my Ubuntu hosts (this issue
has been found on Arch as well so its most likely a change upstream). I
found this error in the logs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
attempting to mount entry /net/freenas.example.net
get_exports: lookup(hosts): exports lookup failed for freenas.example.net
key &amp;quot;freenas.example.net&amp;quot; not found in map source(s).
failed to mount /net/freenas.example.net
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;The fix was to replace the following line in /etc/auto.master from&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/net   -hosts
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;to this&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/net /etc/auto.net --timeout=60
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;and then restart autofs (sudo service autofs restart).&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category></entry><entry><title>Custom Rundeck HipChat notification templates</title><link href="https://russell.ballestrini.net/custom-rundeck-hipchat-notification-templates/" rel="alternate"></link><published>2014-12-03T01:37:00-05:00</published><updated>2014-12-03T01:37:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-12-03:/custom-rundeck-hipchat-notification-templates/</id><summary type="html">&lt;p&gt;Today I built a GUI and workflow around Ansible using Rundeck. Tonight I
started diving into sending HipChat notifications and after a bit of
research, I managed to create a custom notification template for each
Rundeck project.&lt;/p&gt;
&lt;p&gt;Modify your project's configuration file, on Ubuntu it was in
&lt;tt class="docutils literal"&gt;/var/rundeck/projects …&lt;/tt&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I built a GUI and workflow around Ansible using Rundeck. Tonight I
started diving into sending HipChat notifications and after a bit of
research, I managed to create a custom notification template for each
Rundeck project.&lt;/p&gt;
&lt;p&gt;Modify your project's configuration file, on Ubuntu it was in
&lt;tt class="docutils literal"&gt;/var/rundeck/projects/pname/etc/project.properties&lt;/tt&gt;, and add the
following line to the bottom:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
framework.plugin.Notification.HipChatNotification.messageTemplateLocation=/var/rundeck/projects/pname/etc/custom-hipchat-template.ftl
&lt;/pre&gt;
&lt;p&gt;Note: replace &lt;tt class="docutils literal"&gt;pname&lt;/tt&gt; with your project name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ended up producing a single line chat notification template.
I uploaded it here to act as an example:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://pad.yohdah.com/311/rundeck-hipchat-custom-notification-template"&gt;Custom Rundeck HipChat Notification Template&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The template syntax and renderer is called FreeMarker. Rundeck and the
HipChat plugin pass many different context Map hash objects to
FreeMarker for use in the templates. In this case I display &amp;quot;VPC name&amp;quot;
selected by the user when starting the job.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://rundeck.org/plugins/2013/06/24/hipchat-notification.html"&gt;Rundeck HipChat Notification
Plugin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://github.com/hbakkum/rundeck-hipchat-plugin"&gt;Rundeck HipChat Notification
Plugin&lt;/a&gt; (User
Guide and Default Template)&lt;/p&gt;
&lt;/li&gt;</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Build release pipelines on S3 with s3p</title><link href="https://russell.ballestrini.net/build-release-pipelines-on-s3-with-s3p/" rel="alternate"></link><published>2014-11-24T14:39:00-05:00</published><updated>2014-11-24T14:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-11-24:/build-release-pipelines-on-s3-with-s3p/</id><summary type="html">&lt;p&gt;This weekend I finished my first sprint on s3p which is a Python library
and CLI application that manages release pipelines on AWS S3. I put a
lot of effort into the
&lt;a class="reference external" href="https://github.com/russellballestrini/s3p/blob/master/readme.rst"&gt;readme.rst&lt;/a&gt;
file, so look there for usage and examples.&lt;/p&gt;
&lt;p&gt;The main purpose of s3p is to use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This weekend I finished my first sprint on s3p which is a Python library
and CLI application that manages release pipelines on AWS S3. I put a
lot of effort into the
&lt;a class="reference external" href="https://github.com/russellballestrini/s3p/blob/master/readme.rst"&gt;readme.rst&lt;/a&gt;
file, so look there for usage and examples.&lt;/p&gt;
&lt;p&gt;The main purpose of s3p is to use code to enforce process when promoting
releases in the pipeline. Another goal was to make the CLI tool dead
simple to use. As a side effect, I ended up using composition to extend
boto.s3's Key and Bucket classes to produce
&lt;a class="reference external" href="https://github.com/russellballestrini/s3p/blob/master/s3p/release.py"&gt;S3Release&lt;/a&gt;
and
&lt;a class="reference external" href="https://github.com/russellballestrini/s3p/blob/master/s3p/pipeline.py"&gt;S3Pipeline&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I plan to incorporate s3p into Teamcity build jobs. At work I would like
to use s3p to replace bash+s3cmd in pipeline management. Once s3p has a
bit more production use, I will likely sprint on it again.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Dealing with pagination in Python</title><link href="https://russell.ballestrini.net/dealing-with-pagination-in-python/" rel="alternate"></link><published>2014-11-13T21:06:00-05:00</published><updated>2014-11-13T21:06:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-11-13:/dealing-with-pagination-in-python/</id><summary type="html">&lt;p&gt;So I'm working with an API (AWS ElastiCache) that offers mandatory
pagination of results. I need to get all results, so I took some time to
work out this logic.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def combine_results(function, key, marker=0, **kwargs):
    &amp;quot;&amp;quot;&amp;quot;deal with manditory pagination of AWS result descriptions&amp;quot;&amp;quot;&amp;quot;
    results = []
    while marker != None:
        result …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;So I'm working with an API (AWS ElastiCache) that offers mandatory
pagination of results. I need to get all results, so I took some time to
work out this logic.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def combine_results(function, key, marker=0, **kwargs):
    &amp;quot;&amp;quot;&amp;quot;deal with manditory pagination of AWS result descriptions&amp;quot;&amp;quot;&amp;quot;
    results = []
    while marker != None:
        result = function(marker = marker, **kwargs)
        marker = nested_lookup('Marker', result)[0]
        results += nested_lookup(key, result)
    return results
&lt;/pre&gt;
&lt;/p&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Not only is the AWS ElastiCache API paginated but it also appears
deeply nested in lists and dicts.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;I use this to burn it with fire:&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
def nested_lookup(key, dictionary):
    &amp;quot;&amp;quot;&amp;quot;Lookup a key in a nested dictionary, return a list of values&amp;quot;&amp;quot;&amp;quot;
    return list(_nested_lookup(key, dictionary))

def _nested_lookup(key, dictionary):
    &amp;quot;&amp;quot;&amp;quot;
    Lookup a key in a nested dictionary, return value

    Authors: Dougles Miranda and Russell Ballestrini
    &amp;quot;&amp;quot;&amp;quot;
    if isinstance(dictionary, list):
        for d in dictionary:
            for result in _nested_lookup(key, d):
                yield result

    if isinstance(dictionary, dict):
        for k, v in dictionary.iteritems():
            if k == key:
                yield v
            elif isinstance(v, dict):
                for result in _nested_lookup(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in _nested_lookup(key, d):
                        yield result
&lt;/pre&gt;
&lt;p&gt;The end result is we have access to paginated and deeply nested data
with a simple to use function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; from lib import combine_results, nested_lookup
&amp;gt;&amp;gt;&amp;gt; d = elasticache_connection.describe_cache_clusters()
&amp;gt;&amp;gt;&amp;gt; nested_lookup('CacheClusterId', d)
[u'demo04-a-redis', u'demo04-b-redis', u'demo06-a-redis', u'demo06-b-redis', u'test-a-memcached', u'test-b-redis', u'ops01-redis', u'qa01-redis', u'ops02-redis', u'qa02-redis', u'int01-a-redis', u'int01-b-redis', u'ops03-redis', u'ops04-redis']
&lt;/pre&gt;
&lt;p&gt;Here are some unit tests to prove these functions work like expected:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from unittest import TestCase

from lib.util import (
  combine_results,
  nested_lookup,
  _nested_lookup,
)

def my_func_that_paginates(max_results=3, marker=0):
    &amp;quot;&amp;quot;&amp;quot;this function sort of mocks the paginated AWS description results&amp;quot;&amp;quot;&amp;quot;
    data = [
      {'desired_key' : 0},
      {'desired_key' : 1},
      {'desired_key' : 2},
      {'desired_key' : 3},
      {'desired_key' : 4},
      {'desired_key' : 5},
      {'desired_key' : 6},
      {'desired_key' : 7},
      {'desired_key' : 8},
      {'desired_key' : 9},
    ]
    new_marker = marker + max_results
    if new_marker &amp;gt; len(data):
        # last page!
        page = data[marker:]
        return {'results' : page, 'Marker' : None}
    page = data[marker:new_marker]
    return {'results' : page, 'Marker' : new_marker}

class TestCombineResults(TestCase):

    def test_combine_results_returns_all_results(self):
        expected_set = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        f = my_func_that_paginates
        result_set = set(combine_results(f, 'desired_key'))
        self.assertSetEqual(expected_set, result_set)

class TestNestedLookup(TestCase):

    def setUp(self):
        self.subject_dict = {'a':1,'b':{'d':100},'c':{'d':200}}

    def test_nested_lookup(self):
        results = nested_lookup('d', self.subject_dict)
        self.assertEqual(2, len(results))
        self.assertIn(100, results)
        self.assertIn(200, results)
        self.assertSetEqual({100,200}, set(results))

    def test_nested_lookup_wrapped_in_list(self):
        results = nested_lookup('d', [{}, self.subject_dict, {}])
        self.assertEqual(2, len(results))
        self.assertIn(100, results)
        self.assertIn(200, results)
        self.assertSetEqual({100,200}, set(results))

    def test_nested_lookup_wrapped_in_list_in_dict_in_list(self):
        results = nested_lookup('d', [{}, {'H' : [self.subject_dict]} ])
        self.assertEqual(2, len(results))
        self.assertIn(100, results)
        self.assertIn(200, results)
        self.assertSetEqual({100,200}, set(results))

    def test_nested_lookup_wrapped_in_list_in_list(self):
        results = nested_lookup('d', [ {}, [self.subject_dict, {}] ])
        self.assertEqual(2, len(results))
        self.assertIn(100, results)
        self.assertIn(200, results)
        self.assertSetEqual({100,200}, set(results))
&lt;/pre&gt;
&lt;p&gt;With this test, the steps of the algorithm looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{'Marker': 3, 'results': [{'desired_key': 0}, {'desired_key': 1}, {'desired_key': 2}]}
3
[0, 1, 2]
[0, 1, 2]
{'Marker': 6, 'results': [{'desired_key': 3}, {'desired_key': 4}, {'desired_key': 5}]}
6
[3, 4, 5]
[0, 1, 2, 3, 4, 5]
{'Marker': 9, 'results': [{'desired_key': 6}, {'desired_key': 7}, {'desired_key': 8}]}
9
[6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
{'Marker': None, 'results': [{'desired_key': 9}]}
None
[9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ok
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Turn python dict into a key=value string and back again</title><link href="https://russell.ballestrini.net/turn-python-dict-into-a-keyvalue-string/" rel="alternate"></link><published>2014-11-09T22:02:00-05:00</published><updated>2014-11-09T22:02:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-11-09:/turn-python-dict-into-a-keyvalue-string/</id><summary type="html">&lt;p&gt;I'm currently refactoring a script that tags AWS resources and I came up
with this one liner to generate pretty output. It basically turns
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;{'tag1':'value1','tag2':'value2'}&lt;/span&gt;&lt;/tt&gt; into &lt;tt class="docutils literal"&gt;tag1=value1, tag2=value2&lt;/tt&gt;.
Here is the code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
', '.join(['='.join(key_value) for key_value in {'a':'1','b':'2'}.items() ])
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Oh, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm currently refactoring a script that tags AWS resources and I came up
with this one liner to generate pretty output. It basically turns
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;{'tag1':'value1','tag2':'value2'}&lt;/span&gt;&lt;/tt&gt; into &lt;tt class="docutils literal"&gt;tag1=value1, tag2=value2&lt;/tt&gt;.
Here is the code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
', '.join(['='.join(key_value) for key_value in {'a':'1','b':'2'}.items() ])
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Oh, and if you like this, here is a function with additional functionality and protection:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def dict_to_key_value(data, sep='=', pair_sep=', '):
    &amp;quot;&amp;quot;&amp;quot;turns {'tag1':'value1','tag2':'value2'} into tag1=value1, tag2=value2&amp;quot;&amp;quot;&amp;quot;
    return pair_sep.join([sep.join((unicode(key), unicode(value))) for key, value in data.items()])
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Careful, this might blow up on dictionaries that nest other objects.
Also here is a test:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def test_dict_to_key_value():
    data = {'tag1':'value1','tag2':'value2'}
    pretty_str = dict_to_key_value(data)
    assert('tag1=value1' in pretty_str)
    assert('tag2=value2' in pretty_str)
    assert('tag1=value1, tag2=value2' in pretty_str)
    not_as_pretty = dict_to_key_value(data,'x','x')
    assert('tag1xvalue1xtag2xvalue2' in not_as_pretty)
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Here is the inverse, taking a list of key_value strings and returning a
dictionary of the data:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def key_value_to_dict(key_value_list, sep='=', pair_sep=',' ):
    &amp;quot;&amp;quot;&amp;quot;
    Accept a key_value_list, like::

      key_value_list = ['a=1,b=2', 'c=3, d=4', 'e=5']

    Return a dict, like::

      {'a':'1', 'b':'2', 'c':'3', 'd':'4', 'e':'5'}
    &amp;quot;&amp;quot;&amp;quot;
    d = {}
    for speclist in key_value_list:
        for spec in speclist.strip().split(','):
            key, value = spec.strip().split('=')
            d[key] = value
    return d
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;And of course a test to prove it works how we expect:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def test_key_value_to_dict():
    key_value_list = ['a=1,b=2', 'c=3, d=4', 'e=5']
    desired_result = {'a':'1', 'b':'2', 'c':'3', 'd':'4', 'e':'5'}
    assert(key_value_to_dict(key_value_list) == desired_result)
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Migrating MongoDB from Ubuntu to SmartOS</title><link href="https://russell.ballestrini.net/migrating-mongodb-from-ubuntu-to-smartos/" rel="alternate"></link><published>2014-10-11T20:52:00-04:00</published><updated>2014-10-11T20:52:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-10-11:/migrating-mongodb-from-ubuntu-to-smartos/</id><summary type="html">&lt;p&gt;First, I installed the mongodb 14.2.0
(&lt;tt class="docutils literal"&gt;uuid &lt;span class="pre"&gt;a5775e36-2a02-11e4-942a-67ae7a242985&lt;/span&gt;&lt;/tt&gt;) dataset:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
imgadm avail | grep mongo
imgadm import a5775e36-2a02-11e4-942a-67ae7a242985
&lt;/pre&gt;
&lt;p&gt;Next, I launched a new zone with this image.&lt;/p&gt;
&lt;p&gt;Then I grabbed the uuid of the zone (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;211b992b-a448-40b4-94c9-00fa82615cec&lt;/span&gt;&lt;/tt&gt;) and I connected into the zone&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zlogin 211b992b-a448-40b4-94c9-00fa82615cec
&lt;/pre&gt;
&lt;p&gt;The zone automatically creates a username …&lt;/p&gt;</summary><content type="html">&lt;p&gt;First, I installed the mongodb 14.2.0
(&lt;tt class="docutils literal"&gt;uuid &lt;span class="pre"&gt;a5775e36-2a02-11e4-942a-67ae7a242985&lt;/span&gt;&lt;/tt&gt;) dataset:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
imgadm avail | grep mongo
imgadm import a5775e36-2a02-11e4-942a-67ae7a242985
&lt;/pre&gt;
&lt;p&gt;Next, I launched a new zone with this image.&lt;/p&gt;
&lt;p&gt;Then I grabbed the uuid of the zone (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;211b992b-a448-40b4-94c9-00fa82615cec&lt;/span&gt;&lt;/tt&gt;) and I connected into the zone&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zlogin 211b992b-a448-40b4-94c9-00fa82615cec
&lt;/pre&gt;
&lt;p&gt;The zone automatically creates a username and password for
admin and &amp;quot;quickbackup&amp;quot;. You can find these passwords by running the
following command inside the zone:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat /var/svc/log/system-zoneinit\:default.log | grep -i mon
&lt;/pre&gt;
&lt;p&gt;First thing I did was disable authentication by modifying
&lt;tt class="docutils literal"&gt;/opt/local/etc/mongod.conf&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#auth = true
noauth = true
&lt;/pre&gt;
&lt;p&gt;Then I restarted MongoDB to re-read its configuration:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
svcadm restart mongodb
&lt;/pre&gt;
&lt;p&gt;Next I attempted to restore the database BSON files, with the following
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mongorestore --db=taco taco/taco.bson
&lt;/pre&gt;
&lt;p&gt;But I got the following error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
connected to: 127.0.0.1
terminate called after throwing an instance of 'std::runtime_error'
  what():  locale::facet::_S_create_c_locale name not valid
Abort (core dumped)
&lt;/pre&gt;
&lt;p&gt;After some research I learned that I needed to export the following
variable before running the restore:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
export LC_ALL=C
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Set Root Password SmartOS Percona MySQL Zone</title><link href="https://russell.ballestrini.net/set-root-password-smartos-percona-mysql-zone/" rel="alternate"></link><published>2014-10-11T00:12:00-04:00</published><updated>2014-10-11T00:12:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-10-11:/set-root-password-smartos-percona-mysql-zone/</id><summary type="html">&lt;p&gt;I used project-fifo to launch the &lt;tt class="docutils literal"&gt;percona (14.2.0)&lt;/tt&gt; MySQL dataset. I
couldn't get into the MySQL instance so I reached out on IRC.
Johngrasty, a friendly guy in the &lt;tt class="docutils literal"&gt;#smartos&lt;/tt&gt; IRC channel, provided a
command to display the randomly generated MySQL password emitted to the
zone-init log:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;I used project-fifo to launch the &lt;tt class="docutils literal"&gt;percona (14.2.0)&lt;/tt&gt; MySQL dataset. I
couldn't get into the MySQL instance so I reached out on IRC.
Johngrasty, a friendly guy in the &lt;tt class="docutils literal"&gt;#smartos&lt;/tt&gt; IRC channel, provided a
command to display the randomly generated MySQL password emitted to the
zone-init log:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat /var/svc/log/system-zoneinit\:default.log | grep MYSQL_PW
&lt;/pre&gt;
&lt;p&gt;I used this initial password to get into the &lt;tt class="docutils literal"&gt;mysql&amp;gt;&lt;/tt&gt; shell and
changed it with this SQL statement:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mysql&amp;gt; SET PASSWORD = PASSWORD('clear-text-password');
&lt;/pre&gt;
&lt;p&gt;Johngrasty also supplied a snippet of JSON which shows how to declare
root MySQL password:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{
  ... truncated ...
  &amp;quot;customer_metadata&amp;quot;: {
    &amp;quot;salt-master&amp;quot;: &amp;quot;10.0.0.101&amp;quot;,
    &amp;quot;salt-id&amp;quot;: &amp;quot;mysql1&amp;quot;
  },
  &amp;quot;internal_metadata&amp;quot;: {
    &amp;quot;mysql_pw&amp;quot;: &amp;quot;mypassword&amp;quot;
  }
}
&lt;/pre&gt;
&lt;p&gt;I looked for help in the #project-fifo channel as to why the GUI does
not work for assigning initial MySQL password. MerlinDMC, the author and
operator of &lt;a class="reference external" href="https://datasets.at"&gt;datasets.at&lt;/a&gt;, gave me the following
command to run in the Global Zone to look at a particular zone's
metadata:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat /zones/uuid-of-zone-goes-here/config/metadata.json
&lt;/pre&gt;
&lt;p&gt;Turns out the GUI is placing the &lt;tt class="docutils literal"&gt;&amp;quot;mysql_pw&amp;quot;&lt;/tt&gt; parameter into
&lt;tt class="docutils literal"&gt;&amp;quot;customer_metadata&amp;quot;&lt;/tt&gt; instead of &lt;tt class="docutils literal"&gt;&amp;quot;internal_metadata&amp;quot;&lt;/tt&gt; which is
invalid.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>rabbits</title><link href="https://russell.ballestrini.net/rabbits/" rel="alternate"></link><published>2014-09-18T21:11:00-04:00</published><updated>2014-09-18T21:11:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-09-18:/rabbits/</id><summary type="html">&lt;p class="first last"&gt;Digital Rabbit Drawing.&lt;/p&gt;
</summary><content type="html">&lt;img alt="original rabbits" src="/uploads/2014/09/2014-09-18-rabbits.png" /&gt;
</content><category term="misc"></category><category term="Art"></category></entry><entry><title>reality shattered</title><link href="https://russell.ballestrini.net/reality-shattered/" rel="alternate"></link><published>2014-09-18T21:11:00-04:00</published><updated>2014-09-18T21:11:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-09-18:/reality-shattered/</id><summary type="html">&lt;p class="first last"&gt;Emotional overload&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="image0" src="/uploads/2014/09/2014-09-17-reality-shattered.png" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Art"></category></entry><entry><title>Heka, World2!</title><link href="https://russell.ballestrini.net/heka-world2/" rel="alternate"></link><published>2014-08-23T22:54:00-04:00</published><updated>2014-08-23T22:54:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-08-23:/heka-world2/</id><summary type="html">&lt;p&gt;This article expands on my &lt;a class="reference external" href="/heka-world/"&gt;“Hello World” for Heka&lt;/a&gt; blog post.
Check that one out first if you are new to Heka.&lt;/p&gt;
&lt;p&gt;In this guide we introduce using Heka over the network by utilizing two
Hekad processes on localhost. For discussion purposes we name one of the
Hekad processes &amp;quot;sender …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This article expands on my &lt;a class="reference external" href="/heka-world/"&gt;“Hello World” for Heka&lt;/a&gt; blog post.
Check that one out first if you are new to Heka.&lt;/p&gt;
&lt;p&gt;In this guide we introduce using Heka over the network by utilizing two
Hekad processes on localhost. For discussion purposes we name one of the
Hekad processes &amp;quot;sender&amp;quot; and the other &amp;quot;receiver&amp;quot;.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &amp;quot;sender&amp;quot; will watch a log file and emit messages to localhost TCP
port 9612.&lt;/li&gt;
&lt;li&gt;The &amp;quot;receiver&amp;quot; will listen on localhost TCP port 9612 and emit
message payloads to file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;hello-heka-file-in-tcp-out.toml (sender):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# watch /tmp/input.log and output to TCP port 9612 on localhost
[hello_heka_input_log]
type = &amp;quot;LogstreamerInput&amp;quot;
log_directory = &amp;quot;/tmp&amp;quot;
file_match = 'input\.log'

[tcp_out:9612]
type = &amp;quot;TcpOutput&amp;quot;
message_matcher = &amp;quot;TRUE&amp;quot;
address = &amp;quot;127.0.0.1:9612&amp;quot;
#encoder = &amp;quot;hello_heka_output_encoder&amp;quot;
#
#[hello_heka_output_encoder]
#type = &amp;quot;PayloadEncoder&amp;quot;
#append_newlines = false
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;hello-heka-tcp-in-file-out.toml (receiver):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# listen to TCP port 9612 and emit to /tmp/output.log
[tcp_in:9612]
type = &amp;quot;TcpInput&amp;quot;
parser_type = &amp;quot;message.proto&amp;quot;
decoder = &amp;quot;ProtobufDecoder&amp;quot;
address = &amp;quot;:9612&amp;quot;

[hello_heka_output_log]
type = &amp;quot;FileOutput&amp;quot;
message_matcher = &amp;quot;TRUE&amp;quot;
path = &amp;quot;/tmp/output.log&amp;quot;
perm = &amp;quot;664&amp;quot;
encoder = &amp;quot;hello_heka_output_encoder&amp;quot;

[hello_heka_output_encoder]
type = &amp;quot;PayloadEncoder&amp;quot;
append_newlines = false
&lt;/pre&gt;
&lt;p&gt;Now that we have config files, let us start our hekad processes, Open
three terminals.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 1:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo hekad -config=hello-heka-file-in-tcp-out.toml
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 2:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo hekad -config=/tmp/hello-heka-tcp-in-file-out.toml
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 3:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
echo 'Heka, World2!' &amp;gt;&amp;gt; /tmp/input.log
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 3:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cat /tmp/output.log
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Again, like magic, the data echoed into input.log shows up in
output.log. This time the data traveled over TCP between to separate
Hekad processes. I leave changing the configuration to support separate
hosts to the reader.&lt;/p&gt;
&lt;p&gt;By default TCP sender encodes the message with Protobuf
(ProtobufEncoder) and the TCP reciever decodes the message with Protobuf
(ProtobufDecoder).&lt;/p&gt;
&lt;p&gt;In my testing I decided to make the TCP sender use the PayloadEncoder
and then instead of using a second hekad process, I used &lt;tt class="docutils literal"&gt;nc &lt;span class="pre"&gt;-l&lt;/span&gt; 9612&lt;/tt&gt;
to listen on the port. When data was added to &lt;tt class="docutils literal"&gt;/tmp/input.log&lt;/tt&gt; it
showed up in the netcat terminal because hekad was watching the file and
emiting just payload portion of the message to TCP 9612 which netcat was
listening on. I left this configuration in the examples above, simply
uncomment to reproduce.&lt;/p&gt;
&lt;p&gt;Read this for &lt;a class="reference external" href="https://www.foxhop.net/linux-nc-and-python-sockets"&gt;more fun with
netcat&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Mailpile Salt States for Ubuntu or Debian</title><link href="https://russell.ballestrini.net/mailpile-salt-states-for-ubuntu-or-debian/" rel="alternate"></link><published>2014-08-15T20:32:00-04:00</published><updated>2014-08-15T20:32:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-08-15:/mailpile-salt-states-for-ubuntu-or-debian/</id><summary type="html">&lt;p&gt;I wrote these Salt States to install Mailpile on an Ubuntu host. Fun
fact, it took me 20 minutes to write these states and they worked the
first time I ran them. Disclaimer - I used a throw away server and
wasn't concerned that buckets of packages were installed to the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I wrote these Salt States to install Mailpile on an Ubuntu host. Fun
fact, it took me 20 minutes to write these states and they worked the
first time I ran them. Disclaimer - I used a throw away server and
wasn't concerned that buckets of packages were installed to the system
instead of using a virtualenv.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cd /opt/Mailpile
./mp --set sys.http_host=0.0.0.0
./mp
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Then open a web browser the IP address of the host running the mp
command and follow the prompts to setup the server/client/app.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mailpile/init.sls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Clone the source repository
mailpile-git-latest:
  git.latest:
    - name: https://github.com/pagekite/Mailpile.git
    - target: /opt/Mailpile

# install the system requirements
mailpile-system-packages:
  pkg.installed:
    - names:
      - make
      - python-imaging
      - python-lxml
      - python-jinja2
      - pep8
      - ruby-dev
      - yui-compressor
      - python-nose
      - spambayes
      - phantomjs
      - python-pip
      - python-mock
      - python-pexpect
      {% if grains['lsb_distrib_release']|float &amp;gt;= 14.04 %}
      - rubygems-integration
      {% else %}
      - rubygems
      {% endif %}

# install some python requirements with pip
mailpile-pip-packages:
  pip.installed:
    - names:
      - pgpdump
      - selenium &amp;gt;= 2.40.0
    - require:
      - pkg: mailpile-system-packages

# install some ruby requirements with gem
mailpile-gem-packages:
  gem.installed:
    - names:
      - therubyracer
      - less
    - require:
      - pkg: mailpile-system-packages
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="DevOps"></category></entry><entry><title>You can hack on FreeNAS 9</title><link href="https://russell.ballestrini.net/you-can-hack-on-freenas-9/" rel="alternate"></link><published>2014-05-15T01:06:00-04:00</published><updated>2014-05-15T01:06:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-05-15:/you-can-hack-on-freenas-9/</id><summary type="html">&lt;p&gt;This post analyses the FreeNAS 9 code base and discusses the various
places users may feel confident to hack on.&lt;/p&gt;
&lt;p&gt;FreeNAS uses the following software stack:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Django&lt;/dt&gt;
&lt;dd&gt;A Python Web Application Framework which complies with WSGI&lt;/dd&gt;
&lt;dt&gt;Nginx&lt;/dt&gt;
&lt;dd&gt;A very fast web server which may act as a reverse proxy server …&lt;/dd&gt;&lt;/dl&gt;</summary><content type="html">&lt;p&gt;This post analyses the FreeNAS 9 code base and discusses the various
places users may feel confident to hack on.&lt;/p&gt;
&lt;p&gt;FreeNAS uses the following software stack:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Django&lt;/dt&gt;
&lt;dd&gt;A Python Web Application Framework which complies with WSGI&lt;/dd&gt;
&lt;dt&gt;Nginx&lt;/dt&gt;
&lt;dd&gt;A very fast web server which may act as a reverse proxy server for
HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load
balancer and HTTP cache.&lt;/dd&gt;
&lt;dt&gt;Dojo Toolkit&lt;/dt&gt;
&lt;dd&gt;The Javascript toolkit used to create widgets and handle client side
processing.&lt;/dd&gt;
&lt;dt&gt;FreeBSD&lt;/dt&gt;
&lt;dd&gt;FreeBSD is an advanced computer operating system used to power
modern servers.&lt;/dd&gt;
&lt;dt&gt;Want to hack on the frontend web application?&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;Start here if you enjoy Python, or if you really enjoy coding on
Django applications:&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external" href="https://github.com/freenas/freenas/tree/master/gui"&gt;https://github.com/freenas/freenas/tree/master/gui&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Want to hack on the GUI?&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;Start here if you are a front end developer and enjoy writing HTML,
CSS, and working with Javascript:&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external" href="https://github.com/freenas/freenas/tree/master/gui/templates"&gt;https://github.com/freenas/freenas/tree/master/gui/templates&lt;/a&gt;&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Want to change Nginx?&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;Take a look here if you would like to review, change, or tune Nginx
on FreeNAS:&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external" href="https://github.com/freenas/freenas/tree/master/nanobsd/Files/usr/local/etc/nginx"&gt;https://github.com/freenas/freenas/tree/master/nanobsd/Files/usr/local/etc/nginx&lt;/a&gt;
This directory holds the nginx &amp;quot;vhost&amp;quot; config files and CGI parameters.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Want to hack on the OS?&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;Start here, if you know about &lt;tt class="docutils literal"&gt;FreeBSD&lt;/tt&gt; or operating systems in general:&lt;/p&gt;
&lt;p class="last"&gt;&lt;a class="reference external" href="https://github.com/freenas/freenas/tree/master/nanobsd"&gt;https://github.com/freenas/freenas/tree/master/nanobsd&lt;/a&gt;
This directory seems like a customized and completely version controlled nanoBSD install!&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>Nginx with SSL and mixed content errors with upstream WSGI servers</title><link href="https://russell.ballestrini.net/nginx-with-ssl-and-mixed-content-errors-with-upstream-wsgi-servers/" rel="alternate"></link><published>2014-05-08T16:28:00-04:00</published><updated>2014-05-08T16:28:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-05-08:/nginx-with-ssl-and-mixed-content-errors-with-upstream-wsgi-servers/</id><summary type="html">&lt;p&gt;Mixed content errors occur because Nginx (the front-end server)
communicates to the upstream WSGI server using http. WSGI does not know
(or care) about the SSL session between Nginx and the user. The WSGI
server will naively generate URIs and serve assets as http.&lt;/p&gt;
&lt;p&gt;To fix mixed content errors, we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Mixed content errors occur because Nginx (the front-end server)
communicates to the upstream WSGI server using http. WSGI does not know
(or care) about the SSL session between Nginx and the user. The WSGI
server will naively generate URIs and serve assets as http.&lt;/p&gt;
&lt;p&gt;To fix mixed content errors, we need to communicate the inbound request
scheme or configure the WSGI server to always use https.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;waitress&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Waitress is meant to be a production-quality pure-Python WSGI server
with very acceptable performance.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;It commonly powers Pyramid and Substance D deployments.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To configure waitress to always use https in code:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from waitress import serve
serve(wsgiapp, host='0.0.0.0', port=8080, url_scheme='https')
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;configure waitress to always use https in paste deploy compatible
configuration file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[server:main]
host = 127.0.0.1
port = 6543
url_scheme = https
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category></entry><entry><title>How to patch Heartbleed OpenSSL defect (libssl) on Ubuntu</title><link href="https://russell.ballestrini.net/how-to-patch-heartbleed-openssl-defect-libssl-on-ubuntu/" rel="alternate"></link><published>2014-04-08T22:42:00-04:00</published><updated>2014-04-08T22:42:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-04-08:/how-to-patch-heartbleed-openssl-defect-libssl-on-ubuntu/</id><summary type="html">&lt;p&gt;Lots of people claim that you need to upgrade openssl package, but this
will not fix the issue.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The issue is not the openssl package, it is one of the libraries that
the package relies on (libssl).&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://www.ubuntu.com/usn/usn-2165-1/"&gt;https://www.ubuntu.com/usn/usn-2165-1/&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The output of &lt;tt class="docutils literal"&gt;openssl version &lt;span class="pre"&gt;-a&lt;/span&gt;&lt;/tt&gt; command …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lots of people claim that you need to upgrade openssl package, but this
will not fix the issue.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;The issue is not the openssl package, it is one of the libraries that
the package relies on (libssl).&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://www.ubuntu.com/usn/usn-2165-1/"&gt;https://www.ubuntu.com/usn/usn-2165-1/&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The output of &lt;tt class="docutils literal"&gt;openssl version &lt;span class="pre"&gt;-a&lt;/span&gt;&lt;/tt&gt; command should have a &lt;tt class="docutils literal"&gt;built on&lt;/tt&gt;
date older then &lt;tt class="docutils literal"&gt;Mon Apr&amp;nbsp; 7 20:33:29 UTC 2014&lt;/tt&gt;. After patching openssl
we still see the vulnerable date:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
openssl version -a | egrep &amp;quot;OpenSSL|built&amp;quot;
OpenSSL 1.0.1 14 Mar 2012
built on: Tue Aug 21 05:18:48 UTC 2012
&lt;/pre&gt;
&lt;p&gt;Now we patch &lt;tt class="docutils literal"&gt;libssl1.0.0&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo apt-get update
sudo apt-get install libssl1.0.0
&lt;/pre&gt;
&lt;p&gt;Notice the patched &lt;tt class="docutils literal"&gt;built on&lt;/tt&gt; date:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
openssl version -a | egrep &amp;quot;OpenSSL|built&amp;quot;
OpenSSL 1.0.1 14 Mar 2012
built on: Mon Apr  7 20:33:29 UTC 2014
&lt;/pre&gt;
&lt;p&gt;In my case I used a Salt remote execution to patch, verify, and restart
nginx on all of my 14 hosts:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo salt '*' cmd.run 'apt-get update'
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
sudo salt '*' cmd.run 'apt-get -y install libssl1.0.0'
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
sudo salt '*' cmd.run 'openssl version -a | egrep &amp;quot;OpenSSL|built&amp;quot;'
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
sudo salt '*' service.restart nginx
&lt;/pre&gt;
&lt;p&gt;
&lt;center&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;img alt="image0" src="https://imgs.xkcd.com/comics/heartbleed.png" /&gt;&lt;/div&gt;
&lt;div class="line"&gt;xkcd 1353&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/center&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Guide"></category><category term="Security"></category></entry><entry><title>IRC Bot (Foxbot) runs canned remote executions using Salt Stack</title><link href="https://russell.ballestrini.net/irc-bot-foxbot-runs-canned-remote-executions-using-salt-stack/" rel="alternate"></link><published>2014-03-15T16:33:00-04:00</published><updated>2014-03-15T16:33:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-03-15:/irc-bot-foxbot-runs-canned-remote-executions-using-salt-stack/</id><summary type="html">&lt;p&gt;I extended my IRC Bot Foxbot today to allow it to run canned remote
executions on behalf of users in an IRC channel. This is only a
prototype or proof-of-concept. Be very careful not to allow users to
inject their own commands. Foxbot must be running on the Salt Master …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I extended my IRC Bot Foxbot today to allow it to run canned remote
executions on behalf of users in an IRC channel. This is only a
prototype or proof-of-concept. Be very careful not to allow users to
inject their own commands. Foxbot must be running on the Salt Master and
must be running as the same user that runs the salt-master daemon.&lt;/p&gt;
&lt;p&gt;The code lives here:
&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/foxbot/src/tip/plugins/checks.py"&gt;foxbot/plugins/checks.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Example usage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;16:18:25            * | russell checks uptime minion2.foxhop.net
16:18:25       foxbot | minion2.foxhop.net:  16:18:25 up 496 days, 22:36, 17 users,  load average: 0.33, 0.70, 0.87
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;16:29:10            * | russell checks procs *
16:29:17       foxbot | minion2.foxhop.net: PROCS WARNING: 165 processes
16:29:17       foxbot | minion5.foxhop.net: PROCS CRITICAL: 309 processes
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Filter Salt Stack Return Data Output</title><link href="https://russell.ballestrini.net/filter-salt-stack-return-data-output/" rel="alternate"></link><published>2014-03-13T18:21:00-04:00</published><updated>2014-03-13T18:21:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-03-13:/filter-salt-stack-return-data-output/</id><summary type="html">&lt;p&gt;Sometimes you only want to see what has changed, and that is OK.&lt;/p&gt;
&lt;p&gt;Create a file like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;filter.py&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/python

from json import loads
from json import dumps

import fileinput

stdin_lines = [line for line in fileinput.input()]

ret = loads(''.join(stdin_lines))

for minion_id, data in ret.items …&lt;/pre&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;Sometimes you only want to see what has changed, and that is OK.&lt;/p&gt;
&lt;p&gt;Create a file like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;filter.py&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/python

from json import loads
from json import dumps

import fileinput

stdin_lines = [line for line in fileinput.input()]

ret = loads(''.join(stdin_lines))

for minion_id, data in ret.items():
    print(minion_id)
    print('='*len(minion_id))
    for key, value in ret[minion_id].items():
        if value['changes'] or value['result'] == False:
            print('')
            print(dumps(value, indent=4))
            print('')
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Make the file executable:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
chmod 755 filter.py
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Execute your remote execution like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
sudo salt-call --out=json state.highstate | ./filter.py
&lt;/pre&gt;
&lt;/p&gt;&lt;pre class="literal-block"&gt;
sudo salt '*' --out=json  --timeout=60 --static state.highstate | ./filter.py
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;The flags &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--timeout=60&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--static&lt;/span&gt;&lt;/tt&gt; will cause the Salt
command to block until the specified seconds for each minion to
return results. We then pipe the returned JSON into our
&lt;tt class="docutils literal"&gt;filter.py&lt;/tt&gt; script to filter out only the changes and failures!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Profit!&lt;/p&gt;
&lt;p&gt;Change the conditional depending on what you want. For example, for just
failures do this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
if value['result'] == False:
&lt;/pre&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
# sudo salt 'graphite.foxhop.net' --out=json --static --timeout=60 state.highstate | ./filter.py

graphite.foxhop.net
===================

{
    &amp;quot;comment&amp;quot;: &amp;quot;File /tmp/taco updated&amp;quot;,
    &amp;quot;__run_num__&amp;quot;: 15,
    &amp;quot;changes&amp;quot;: {
        &amp;quot;diff&amp;quot;: &amp;quot;New file&amp;quot;,
        &amp;quot;mode&amp;quot;: &amp;quot;0640&amp;quot;
    },
    &amp;quot;name&amp;quot;: &amp;quot;/tmp/taco&amp;quot;,
    &amp;quot;result&amp;quot;: true
}
&lt;/pre&gt;
&lt;/p&gt;&lt;/blockquote&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Replace the Nagios Scheduler and NRPE with Salt Stack</title><link href="https://russell.ballestrini.net/replace-the-nagios-scheduler-and-nrpe-with-salt-stack/" rel="alternate"></link><published>2014-03-08T15:53:00-05:00</published><updated>2014-03-08T15:53:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-03-08:/replace-the-nagios-scheduler-and-nrpe-with-salt-stack/</id><summary type="html">&lt;p&gt;Note: I will update this post as I progress.&lt;/p&gt;
&lt;p&gt;So the idea is to use Salt Stack's remote execution to communicate with
all nodes and run the Nagios checks and collect the return output
instead of using the NRPE client/service protocol. This reduces the
number of agents running on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Note: I will update this post as I progress.&lt;/p&gt;
&lt;p&gt;So the idea is to use Salt Stack's remote execution to communicate with
all nodes and run the Nagios checks and collect the return output
instead of using the NRPE client/service protocol. This reduces the
number of agents running on each host and appears significantly more
secure. Salt Stack uses public/private crypto on top of the ZMQ
publisher/subscriber model. This means the communication transport is
very fast, very secure, and all nodes will run checks in parallel!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;First, we need to install the Nagios checks and plugins on each host
or minion.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;I used the following State Formula on my Ubuntu and Debian hosts:&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;salt://nagios/plugins.sls&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
nagios-plugins:
  pkg:
    - installed

nagios-plugins-extra:
  pkg:
    - installed
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;salt://top.sls&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
base:
  '*'
    - nagios.plugins
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;I kicked off a highstate (&lt;tt class="docutils literal"&gt;salt '*' state.highstate&lt;/tt&gt;) on all minions
and eventually they all returned in the affirmative. We now have all the
Nagios plugins and checks installed on each host.&lt;/p&gt;
&lt;p&gt;This next part is FUN, We run a check on every host concurrently.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This particular check counts the number of processes running on each
host and warns at 150 and criticals at 200.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt '*' --out=json --static cmd.run_all '/usr/lib/nagios/plugins/check_procs -w 150 -c 200'
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Output static JSON - Wait for all minions to return and then
generate a single JSON object.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{
    &amp;quot;graphite.foxhop.net&amp;quot;: {
        &amp;quot;pid&amp;quot;: 24356,
        &amp;quot;retcode&amp;quot;: 0,
        &amp;quot;stderr&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stdout&amp;quot;: &amp;quot;PROCS OK: 78 processes&amp;quot;
    },
    &amp;quot;akuma.foxhop.net&amp;quot;: {
        &amp;quot;pid&amp;quot;: 4610,
        &amp;quot;retcode&amp;quot;: 1,
        &amp;quot;stderr&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stdout&amp;quot;: &amp;quot;PROCS WARNING: 158 processes&amp;quot;
    },
    &amp;quot;ken.foxhop.net&amp;quot;: {
        &amp;quot;pid&amp;quot;: 31254,
        &amp;quot;retcode&amp;quot;: 2,
        &amp;quot;stderr&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stdout&amp;quot;: &amp;quot;PROCS CRITICAL: 392 processes&amp;quot;
    }
}
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Review the &lt;a class="reference external" href="https://nagios.sourceforge.net/docs/3_0/pluginapi.html"&gt;Nagios Plugin
API&lt;/a&gt; for
more information about return codes and STDOUT formats.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Last, we take this JSON data perform further processing or display it in
a meaningful way.&lt;/p&gt;
&lt;p&gt;For example, we could generate an HTML/JavaScript dashboard from the raw
JSON objects. We could cut metrics out of the STDOUT and persist
historic values in a data store. We could push the data into another
system like Graphite or even send an email alerts.&lt;/p&gt;
&lt;p&gt;This solution has all the perks of Nagios without ANY of the cons!&lt;/p&gt;
&lt;p&gt;Returning the check output data to the CLI isn't super useful for
further processing, so I hacked in a way to return data back to the
master using the encrypted zeromq bus.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_returners/zeromq_return.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# -*- coding: utf-8 -*-
'''
The zeromq returner will send return data back to the Salt Master over the
Encrypted 0MQ event bus with a custom tag for filtering on the other end.

Basically after the remote execution finishes, the ret data is &amp;quot;packaged&amp;quot; into
a special &amp;quot;envelope&amp;quot; which triggers the local Salt Minion Daemon to
forward the ret data to the Salt Master's event bus.

The &amp;quot;package&amp;quot; basically wraps the ret data and uses the tag 'fire_master'.

For example, a ret data object from the execution of test.ping
would be &amp;quot;packaged&amp;quot; like this::

  ret = {
    'graphite.foxhop.net': true
  }

  ret['tag'] = 'third-party'

  package = {
    'events': [ ret ],
    'tag': None,
    'pretag': None,
    'data': None
  }

The Salt Minion Daemon will forward this package to the Salt Master
where a 3rd party script may be filtering on the specified internal event tag.

To use the zeromq returner, append '--return zeromq' to the salt command. ex::

  salt --return zeromq '*' test.ping

TODO:

 figure out a way for user to define custom tag for filtering ...
 Most returners use the Salt Minion config file to supply returner
 details... that is not optimal, it would be ideal if the custom tag
 could be supplied on the CLI when the remote execution is run, like::

   --return=zeromq --tag=mytag

'''

# needed to log to log file
import logging

# needed for config to opts processing
import os
import salt.syspaths as syspaths
from salt.config import minion_config

# needed to send events over ZMQ
import salt.utils.event

log = logging.getLogger(__name__)

# needed to define the module's virtual name
__virtualname__ = 'zeromq'

def __virtual__():
    return __virtualname__


def returner(ret):
    '''
    Send the return data to the Salt Master over the encrypted
    0MQ bus with custom tag for 3rd party script filtering.
    '''

    # get opts from minion config file, supports minion.d drop dir!
    opts = minion_config(os.path.join(syspaths.CONFIG_DIR, 'minion'))

    # TODO: this needs to be customizable!
    tag = 'third-party'

    # add custom tag to return data for filtering
    ret['tag'] = tag

    # multi event example, supports a list of event ret objects.
    # single event does not currently expand/filter properly on Master side.
    package = {
      #'id': opts['id'],
      'events': [ ret ],
      'tag': None,
      'pretag': None,
      'data': None
    }

    # opts must contain valid minion ID else it binds to invalid 0MQ socket.
    event = salt.utils.event.SaltEvent('minion', **opts)

    # Fire event payload with 'fire_master' tag which triggers the
    # salt-minion daemon to forward payload to the master event bus!
    event.fire_event(package, 'fire_master')
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Next we run a third party application on the Salt Master which
subscribes to our events by filtering on the special tag
('third-party').&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;listen_to_master_bus.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# event libary for events over ZMQ
import salt.utils.event

# create event object, attach to master socket ...
event = salt.utils.event.MasterEvent('/var/run/salt/master')

tag = 'third-party'

print('Listening for events tagged \'{}\' on Salt Master bus.'.format(tag))

# generator iterator yields events forever, we filter on tag
for data in event.iter_events(tag=tag):
    print(data)
&lt;/pre&gt;
&lt;p&gt;This small application just prints the incoming return data, but it
could easily be expanded to process the incoming return data and persist
it somewhere.&lt;/p&gt;
&lt;p&gt;Open two terminals on the Salt Master host.&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class="literal-block"&gt;
# on terminal 1 run:
python listen_to_master_bus.py
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
# on terminal 2 run:
salt '*' --return zeromq cmd.run_all '/usr/lib/nagios/plugins/check_procs -w 150 -c 200'
&lt;/pre&gt;
&lt;p&gt;Same remote execution check as before but now our new returner will
make data appear in terminal 1!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This zeromq returner is more of a proof-of-concept. I think the salt
remote execution command line tool should allow end-users to provide a
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--tag&lt;/span&gt;&lt;/tt&gt; so that data may be feed directly back to a third-party script
listening to the Salt Master's event bus which filters on the particular
tag. My next step is to look into what it would take to build in this
functionality.&lt;/p&gt;
&lt;p&gt;In the future I want to rig up the Salt scheduler to invoke these remote
execution checks on a steady and predictable cadence. Nagios
historically runs checks every 5 minutes. The Salt scheduler will allow
us schedule different checks with different frequencies. For example, I
might want my load checks and metrics to be collected every 10 secs, but
my disk capacity usage checked every 2 minutes. This fine-grain control
is super powerful!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Configuration Management and the Golden Image</title><link href="https://russell.ballestrini.net/configuration-management-and-the-golden-image/" rel="alternate"></link><published>2014-02-21T17:19:00-05:00</published><updated>2014-02-21T17:19:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-02-21:/configuration-management-and-the-golden-image/</id><summary type="html">&lt;p&gt;When operations first became a thing, system administrators stood up
servers using a base image from their favourite distribution. Things
were done manually. Some administrators created their own distros, some
wrote customised shell scripts to be run once-and-only-once to provision
software and settings. This method worked, but it was slow …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When operations first became a thing, system administrators stood up
servers using a base image from their favourite distribution. Things
were done manually. Some administrators created their own distros, some
wrote customised shell scripts to be run once-and-only-once to provision
software and settings. This method worked, but it was slow, manual, and
the human element caused defects. Then the request came in to stand up
100 servers the exact same way.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;System admins resolved this large request by coming up with a Standard
Operating Environment (SOE) image which would end up becoming the
&amp;quot;golden image&amp;quot;. This golden image was the source of truth, and we
built hundreds, thousands of machines this way. All systems were the
same, or rather started out the same, but it didn't take long for
deviations occur.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;(Norton Ghost, DD, ISOs)&lt;/div&gt;
&lt;div class="line"&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Standard_Operating_Environment"&gt;https://en.wikipedia.org/wiki/Standard_Operating_Environment&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The golden image by itself was a flawed idea. The task of creating a
golden image was difficult and required a lot of work. Not only was it
technical, but there was a lot of politics involved in what was worthy
of inclusion. We didn't want to add cruft to every machine. Also the
golden image was only updated every couple of years, so it would quickly
become outdated and it still required provisioning scripts. Systems
already in production didn't get configuration updates that were
recently added to the golden image. There had to be a better way, and
there was...&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Some novel and smart system administrators who also knew how to
program decided that they didn't want to maintain a golden image and
deal with all the headaches involved and opted to use the light weight
distribution image and then build sophisticated remote execution
software to manage and maintain each server's configuration. Later on
they built configuration management systems on top of this remote
execution layer and were finally able to keep each system up-to-date,
regardless of when it was deployed. They were geniuses and everyone
who knew anything quickly rushed to implement remote execution and
configuration management. It was the right way to manage servers,
until the cloud drifted in.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;(SaltStack, Ansible, Puppet, Chef, CFEngine)&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the day of cloud computing, we needed to scale up and down servers in
seconds. A complex configuration manifest could take hours to run from
start to finish. Configuration management was too slow. Each server
needed to download, install, and configure the software stack, in
real-time. Sure we could spin up multiple machines in parallel, but it
was still slow. We started to look back at the golden age of the golden
image, when a server was built and booted in moments. How could we pair
the speed of the golden image with the flexibility of configuration
management?&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;In the future could we use configuration management manifests and
revision control to document how to build the golden image and then
take a snapshot? Could we overlay multiple layers or dataset of images
on top of each other? Perhaps we take a step way back and create
golden images with a combination of a highly customizable distribution
like Gentoo and configuration management.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;(Joyant SmartOS zone datasets, Docker container images, Vagrant box
files, AWS AMI, Digital Ocean Snapshots, etc)&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>Automatic Backups</title><link href="https://russell.ballestrini.net/automatic-backups/" rel="alternate"></link><published>2014-02-07T17:14:00-05:00</published><updated>2014-02-07T17:14:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-02-07:/automatic-backups/</id><summary type="html">&lt;p class="first last"&gt;My tools for creating automatic backups for various systems&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I maintain many tools for creating automatic backups for various systems.
This page will act as a hub to each of those solutions.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;a class="reference external" href="/tar-back"&gt;tar-back&lt;/a&gt;:&lt;/dt&gt;
&lt;dd&gt;tar-back is a backup utility to tar and gzip target filesystems. It
supports a custom retention, filter exclusions, and backup
directory.&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="/virt-back-a-python-libvirt-backup-utility-for-kvm-xen-virtualbox/"&gt;virt-back&lt;/a&gt;:&lt;/dt&gt;
&lt;dd&gt;virt-back virt-back is a python application that uses the libvirt
API to safely shutdown, gzip, and restart guests. Supports KVM,
QEMU, XEN, Virtualbox, and more.&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="/mysql-back"&gt;mysql-back&lt;/a&gt;:&lt;/dt&gt;
&lt;dd&gt;mysql-back is a backup utility script to dump (backup) and gzip
every MySQL database on a host.&lt;/dd&gt;
&lt;dt&gt;&lt;a class="reference external" href="/backup-all-virtual-machines-on-a-smartos-hypervisor-with-smart-back-sh/"&gt;smart-back&lt;/a&gt;:&lt;/dt&gt;
&lt;dd&gt;smart-back is a utility used to backup of every virtual machine on a
SmartOS hypervisor.&lt;/dd&gt;
&lt;/dl&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category><category term="Greatest Hits"></category></entry><entry><title>tar-back</title><link href="https://russell.ballestrini.net/tar-back/" rel="alternate"></link><published>2014-02-07T16:49:00-05:00</published><updated>2014-02-07T16:49:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-02-07:/tar-back/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tar-back&lt;/span&gt;&lt;/tt&gt; is a backup utility to tar and gzip target filesystems.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;It supports a custom retention, filter exclusions, and backup
directory.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tar-back&lt;/span&gt;&lt;/tt&gt; in combination with cron to perform regular backups
of all localhost filesystems into &lt;tt class="docutils literal"&gt;/archive/fs&lt;/tt&gt;. I then have a central
long term storage server that collects …&lt;/p&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tar-back&lt;/span&gt;&lt;/tt&gt; is a backup utility to tar and gzip target filesystems.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;It supports a custom retention, filter exclusions, and backup
directory.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;tar-back&lt;/span&gt;&lt;/tt&gt; in combination with cron to perform regular backups
of all localhost filesystems into &lt;tt class="docutils literal"&gt;/archive/fs&lt;/tt&gt;. I then have a central
long term storage server that collects the &lt;tt class="docutils literal"&gt;/archive&lt;/tt&gt; partition from
every host.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tar-back:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/env python

DESCRIPTION = &amp;quot;&amp;quot;&amp;quot;A backup utility to tar and gzip
target filesystems. Custom retention, filter exclusions and
backup directory.
&amp;quot;&amp;quot;&amp;quot;

from os import path
from sys import exit
from shutil import move
from optparse import OptionParser
import tarfile

def filter_exclusions( tarinfo ):
    &amp;quot;&amp;quot;&amp;quot;Accept tarinfo, return tarinfo or None&amp;quot;&amp;quot;&amp;quot;
    if o.filters:
        for filtr in o.filters:
            if filtr in tarinfo.name:
                return None
    return tarinfo

def exclude_exclusions( filename ):
    &amp;quot;&amp;quot;&amp;quot;Accept filename, return True or False&amp;quot;&amp;quot;&amp;quot;
    &amp;quot;&amp;quot;&amp;quot;support for python 2.6 or lower&amp;quot;&amp;quot;&amp;quot;
    if o.filters:
        for filtr in o.filters:
            if filtr in filename:
                return True
    return False

def file_rotate( target, retention = 3 ):
    &amp;quot;&amp;quot;&amp;quot;file rotation routine&amp;quot;&amp;quot;&amp;quot;
    for i in range( retention-2, 0, -1 ): # count backwards
        old_name = &amp;quot;%s.%s&amp;quot; % ( target, i )
        new_name = &amp;quot;%s.%s&amp;quot; % ( target, i + 1 )
        try: move( old_name, new_name  )
        except IOError: pass
    move( target, target + '.1' )

if __name__ == '__main__':

    p = OptionParser()  # create an option parser object

    p.set_description( DESCRIPTION )

    p.add_option( '-t', '--targets',
      help='list of target filesystems to backup (coma delimited)',
      default=None, dest='targets', metavar='&amp;quot;/home&amp;quot;',
    )

    p.add_option( '-b', '--backup-dir',
      help='path to backup directory',
      dest='backdir', metavar='&amp;quot;PATH&amp;quot;',
    )

    p.add_option( '-f', '--filters',
      help='list of filter patterns to exclude (coma delimited)',
      default=None, dest='filters', metavar='&amp;quot;.mp3&amp;quot;',
    )

    p.add_option( '-r', '--retention',
      help='backups to retain [default: 3]',
      default=3, type='int', dest='retention', metavar='amount',
    )

    o, args = p.parse_args()  # parse options and args

    extension = '.tar.gz'

    if not o.targets:
        exit( &amp;quot;missing filesystem target, run --help&amp;quot; )
    if not o.backdir:
        exit( &amp;quot;missing backup directory, run --help&amp;quot; )
    if not path.isdir( o.backdir ):
        exit( &amp;quot;backup-dir %s does not exist&amp;quot; % o.backdir )

    if o.filters:
        o.filters = o.filters.split(',')

    o.targets = o.targets.split(',')

    for target in o.targets:
        slug = target.replace( '/', '-' ).lstrip( '-' ) # change / to -

        if slug == '': slug = 'r' # / slug is empty (root)

        tarpath = path.join( o.backdir, slug + extension )

        if path.isfile( tarpath ):
            file_rotate( tarpath, o.retention )

        tar = tarfile.open( tarpath, 'w:gz' )
        try:
            tar.add( target, filter=filter_exclusions )
        except TypeError:
            tar.add( target, exclude=exclude_exclusions )
        tar.close()
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>mysql-back</title><link href="https://russell.ballestrini.net/mysql-back/" rel="alternate"></link><published>2014-02-07T16:39:00-05:00</published><updated>2014-02-07T16:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-02-07:/mysql-back/</id><summary type="html">&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;mysql-back&lt;/span&gt;&lt;/tt&gt;is a backup utility script to dump (backup) and gzip
every MySQL database on a host.&lt;/p&gt;
&lt;p&gt;I use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;mysql-back&lt;/span&gt;&lt;/tt&gt; in combination with cron to perform regular
database dumps of MySQL servers to the &lt;tt class="docutils literal"&gt;/archive/db&lt;/tt&gt; partition on
localhost. I then have a central long term storage server that collects …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;mysql-back&lt;/span&gt;&lt;/tt&gt;is a backup utility script to dump (backup) and gzip
every MySQL database on a host.&lt;/p&gt;
&lt;p&gt;I use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;mysql-back&lt;/span&gt;&lt;/tt&gt; in combination with cron to perform regular
database dumps of MySQL servers to the &lt;tt class="docutils literal"&gt;/archive/db&lt;/tt&gt; partition on
localhost. I then have a central long term storage server that collects
the &lt;tt class="docutils literal"&gt;/archive&lt;/tt&gt; partition from every host.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mysql-back:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/bash

USER=&amp;quot;root&amp;quot;
PASSWORD=&amp;quot;&amp;quot;
TODAY=`date +%Y-%m-%d`
OUTPUTDIR=&amp;quot;/archive/db/$TODAY&amp;quot;
MYSQLDUMP=`which mysqldump`
MYSQL=`which mysql`
GZIP=`which gzip`

mkdir $OUTPUTDIR

# get a list of databases
databases=`$MYSQL --user=$USER --password=$PASSWORD \
 -e &amp;quot;SHOW DATABASES;&amp;quot; | tr -d &amp;quot;| &amp;quot; | grep -v Database`

# dump each database in turn
for db in $databases; do
    $MYSQLDUMP --force --opt --user=$USER --password=$PASSWORD \
    --databases $db &amp;gt; &amp;quot;$OUTPUTDIR/$db.sql&amp;quot;
done

# compress all of the files
$GZIP $OUTPUTDIR/*

# clean up all dumps 30 days old
find /archive/db -ctime +30 -type d | xargs rm -rf
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;If you ever need to restore, like in my case when I moved all my
databases over to a brand new Percona MySQL SmartOS zone, I used the
following command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zcat *sql.gz | mysql -u root -p
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>The Three Deployment Management Strategies</title><link href="https://russell.ballestrini.net/the-three-deployment-management-strategies/" rel="alternate"></link><published>2014-02-03T15:06:00-05:00</published><updated>2014-02-03T15:06:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-02-03:/the-three-deployment-management-strategies/</id><summary type="html">&lt;p&gt;There are three deployment management strategies that could be used to
maintain a system. Each has pros and cons which I outline in this
document.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;run once&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;A proceedure that is run once and only once to setup a system's
configuration values and settings. A semaphore or flag generally
blocks …&lt;/p&gt;&lt;/dd&gt;&lt;/dl&gt;</summary><content type="html">&lt;p&gt;There are three deployment management strategies that could be used to
maintain a system. Each has pros and cons which I outline in this
document.&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;run once&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;A proceedure that is run once and only once to setup a system's
configuration values and settings. A semaphore or flag generally
blocks repeated executions to prevent an undesirable outcome.&lt;/p&gt;
&lt;p class="last"&gt;Development Difficulty: LOW - binary state aware (has run/has not
run)&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;run always&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;A proceedure that is safe to run multiple times but uses a brute
force method to setup a system's configuration and settings. It will
self-heal at the expense of clobbering existing state.&lt;/p&gt;
&lt;p class="last"&gt;Development Difficulty: MEDIUM - not state aware - attention must be
spent to allow multiple executions&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;strong&gt;run as needed&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;A proceedure that is safe to run multiple times and checks a
system's configuration and settings before it sets a system's
configuration or settings. &amp;quot;checks before it steps&amp;quot;. It will
self-heal only the values it needs to.&lt;/p&gt;
&lt;p class="last"&gt;Development Difficulty: HIGH - state aware - must know how to both
get and set values and make desicions based on what it finds.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Opinion"></category></entry><entry><title>How to reset HP iLO Lights-Out User and Password Settings with IPMItool</title><link href="https://russell.ballestrini.net/how-to-reset-hp-ilo-lights-out-user-and-password-settings-with-ipmitools/" rel="alternate"></link><published>2014-01-25T09:18:00-05:00</published><updated>2014-01-25T09:18:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-01-25:/how-to-reset-hp-ilo-lights-out-user-and-password-settings-with-ipmitools/</id><summary type="html">&lt;dl class="docutils"&gt;
&lt;dt&gt;Warning:&lt;/dt&gt;
&lt;dd&gt;Do NOT follow guides that suggest to make a DOS boot disk, this is over complicated.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Use the &lt;tt class="docutils literal"&gt;ipmitool&lt;/tt&gt; which ships with most Unix based operating systems.
I tested on SmartOS and Ubuntu Linux. Use a live boot disk if you must.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Run &lt;tt class="docutils literal"&gt;ipmitool user list&lt;/tt&gt; to list all …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;dl class="docutils"&gt;
&lt;dt&gt;Warning:&lt;/dt&gt;
&lt;dd&gt;Do NOT follow guides that suggest to make a DOS boot disk, this is over complicated.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Use the &lt;tt class="docutils literal"&gt;ipmitool&lt;/tt&gt; which ships with most Unix based operating systems.
I tested on SmartOS and Ubuntu Linux. Use a live boot disk if you must.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Run &lt;tt class="docutils literal"&gt;ipmitool user list&lt;/tt&gt; to list all users installed on the iLO.&lt;/li&gt;
&lt;li&gt;Choose a user ID from the list and run &lt;tt class="docutils literal"&gt;ipmitool user set password [ID]&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;Last, make sure the user is enabled with &lt;tt class="docutils literal"&gt;ipmitool user enable [ID]&lt;/tt&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Here is a complete set of commands I used to reset user ID 6:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[root&amp;#64;1c-c1-de-f0-ad-36 /opt]# ipmitool user list
ID  Name             Callin  Link Auth  IPMI Msg   Channel Priv Limit
2   ROUSER           true    false      false      Unknown (0x00)
3   USERID           true    false      false      Unknown (0x00)
4   OEM              true    false      false      Unknown (0x00)
5   Operator         true    false      false      Unknown (0x00)
6   admin            true    false      false      Unknown (0x00)
... truncated ...
15  admin            true    false      false      Unknown (0x00)
16  OEM              true    false      false      Unknown (0x00)

[root&amp;#64;1c-c1-de-f0-ad-36 /opt]# ipmitool user set password 6 mynew-password

[root&amp;#64;1c-c1-de-f0-ad-36 /opt]# ipmitool user enable 6
&lt;/pre&gt;
&lt;p&gt;Next I logged into the web GUI and cleaned up this crazy list of users.
You may also need to change the privileges on a user ID to grant admin
level access. Run &lt;tt class="docutils literal"&gt;ipmitool user&lt;/tt&gt; for a list of possible sub-commands.&lt;/p&gt;
&lt;p&gt;After I finished poking around with the web GUI, I decided to learn a
bit more about IPMI and the &lt;tt class="docutils literal"&gt;ipmitool&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;I figured out how to get sensor information over the LAN using this
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin sensor
&lt;/pre&gt;
&lt;p&gt;I was even able to log into the server OS via Serial-over-LAN (SOL)
using this command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo ipmitool -I lanplus -H guy-ilo.foxhop.net -U admin sol activate
&lt;/pre&gt;
&lt;p&gt;Serial-over-LAN completely resolves the need for a complicated JAVA/KVM
(keyboard video mouse) setup, I was able to reboot the server and watch
the machine POST over the serial connection! Now you don't need to shell
out $229+ on an Advanced 1 yr single server Licence for HP Lights-Out 100i (LO100i)!&lt;/p&gt;
&lt;p&gt;I uploaded a video showing
&lt;a class="reference external" href="https://www.youtube.com/watch?v=xAFjbKAzB4s"&gt;Serial-Over-LAN with IPMItools to an HP Proliant DL160 G6 running
SmartOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Documenting a few other useful commands for myself here.&lt;/p&gt;
&lt;p&gt;Power on and off the server chassis:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin chassis power status
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin chassis power on
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin chassis power soft
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin chassis power off
sudo ipmitool -I lan -H guy-ilo.foxhop.net -U admin chassis power cycle
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Apparently after you know the ILO username and password you may
also use SSH to connect and manage the server:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ssh admin&amp;#64;guy-ilo.foxhop.net
admin&amp;#64;guile-ilo.foxhop.net's password:

Lights-Out 100 Management
Copyright 2005-2007 ServerEngines Corporation
Copyright 2006-2007 Hewlett-Packard Development Company, L.P.

/./-&amp;gt; help
Root Directory

/./-&amp;gt; show
    /./
    Targets
        system1
        map1

    Properties

    Verbs
        cd
        version
        exit
        show
        help

/./-&amp;gt; cd system1
/./system1/-&amp;gt; show
    /./system1/
    Targets
        oemhp_sensors
        oemhp_frus
        console1
        led1

    Properties
        name=DL180(Aspen)    _R
        enabledstate=enabled

    Verbs
        cd
        version
        exit
        show
        reset
        start
        stop
        help
&lt;/pre&gt;
&lt;p&gt;You can even trigger the server OS to stop change run levels or mess
with chassis power for more extreme measures.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/./system1/-&amp;gt; stop
System1 stopped.
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I run FreeNAS on an HP DL180 G6 and just replaced my p410 controller with an LSI SAS9220-8i (IBM M1015) flashed to IT mode. The stock cables are long enough. I did not have issues with fans running at high RPM.&lt;/p&gt;
&lt;p&gt;(I did the same replacement on an &lt;a class="reference external" href="/how-i-added-two-seagate-240g-ssds-as-smartos-l2arc/"&gt;HP DL160 G6 running SmartOS&lt;/a&gt; and it didn't have a high fan RPM issues either)&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>How I added two Seagate 240G SSDs as SmartOS L2ARC</title><link href="https://russell.ballestrini.net/how-i-added-two-seagate-240g-ssds-as-smartos-l2arc/" rel="alternate"></link><published>2014-01-05T14:56:00-05:00</published><updated>2014-01-05T14:56:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-01-05:/how-i-added-two-seagate-240g-ssds-as-smartos-l2arc/</id><summary type="html">&lt;p&gt;&lt;strong&gt;How I added two Seagate 240G SSDs as SmartOS L2ARC&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;removed icepacks from two western digital velociraptors&lt;/li&gt;
&lt;li&gt;installed ssds into icepacks&lt;/li&gt;
&lt;li&gt;installed icepacks into HP hotswap trays&lt;/li&gt;
&lt;li&gt;installed trays into HP prolaient g6 server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How to list all drive installed in Solaris, Open Solaris, or SmartOS&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
iostat -eE
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
format
&lt;/pre&gt;
&lt;/p&gt;&lt;pre class="literal-block"&gt;
AVAILABLE …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;How I added two Seagate 240G SSDs as SmartOS L2ARC&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;removed icepacks from two western digital velociraptors&lt;/li&gt;
&lt;li&gt;installed ssds into icepacks&lt;/li&gt;
&lt;li&gt;installed icepacks into HP hotswap trays&lt;/li&gt;
&lt;li&gt;installed trays into HP prolaient g6 server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;How to list all drive installed in Solaris, Open Solaris, or SmartOS&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
iostat -eE
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
format
&lt;/pre&gt;
&lt;/p&gt;&lt;pre class="literal-block"&gt;
AVAILABLE DISK SELECTIONS:
       0. c1t0d0
          /pci&amp;#64;0,0/pci103c,330b&amp;#64;1f,2/disk&amp;#64;0,0
       1. c1t1d0
          /pci&amp;#64;0,0/pci103c,330b&amp;#64;1f,2/disk&amp;#64;1,0
       2. c1t2d0
          /pci&amp;#64;0,0/pci103c,330b&amp;#64;1f,2/disk&amp;#64;2,0
       3. c1t3d0
          /pci&amp;#64;0,0/pci103c,330b&amp;#64;1f,2/disk&amp;#64;3,0
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;list zpools:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zpool list
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
NAME    SIZE  ALLOC   FREE  EXPANDSZ    CAP  DEDUP  HEALTH  ALTROOT
zones  1.81T  41.4G  1.77T         -     2%  1.00x  ONLINE  -
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Add the two 240G SSDs as L2ARC devices:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zpool add zones cache c1t2d0 c1t3d0
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Look at the iostats of the drives in the zpool&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
zpool iostat -v
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
               capacity     operations    bandwidth
pool        alloc   free   read  write   read  write
----------  -----  -----  -----  -----  -----  -----
zones       41.4G  1.77T      5     46  78.7K   730K
  mirror    41.4G  1.77T      5     46  78.7K   730K
    c1t0d0      -      -      0     15  41.5K   733K
    c1t1d0      -      -      0     15  41.6K   733K
cache           -      -      -      -      -      -
  c1t2d0    14.6M   224G      0      3  2.48K   273K
  c1t3d0    45.5M   224G      0      6  2.48K   852K
----------  -----  -----  -----  -----  -----  -----
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;This machine is a test hypervisor and after reviewing the output from
&lt;tt class="docutils literal"&gt;zpool iostat &lt;span class="pre"&gt;-v&lt;/span&gt;&lt;/tt&gt; over the last couple days, I'm pretty sure adding
L2ARC to this box was not needed, but it was a great learning
experience.&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>Test Game Engine with Python and SFML</title><link href="https://russell.ballestrini.net/test-game-engine-with-python-and-sfml/" rel="alternate"></link><published>2014-01-02T00:30:00-05:00</published><updated>2014-01-02T00:30:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2014-01-02:/test-game-engine-with-python-and-sfml/</id><summary type="html">&lt;p&gt;Over this holiday season, Christmas and New Years, I took the time to
mess with some Game Development. I wrote a demo game engine using Python
and SFML. I plan to use this post to track my progress.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Evolution Playlist&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ytEG21b2es4&amp;amp;list=PLPqlh_ebFYDFPv_tIdUb26Bit2Q2nIVrm"&gt;All
videos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2014-01-01&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ytEG21b2es4"&gt;User Input, Simple world,
Scrolling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=LFxfepBSgEc"&gt;Simple …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Over this holiday season, Christmas and New Years, I took the time to
mess with some Game Development. I wrote a demo game engine using Python
and SFML. I plan to use this post to track my progress.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Evolution Playlist&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ytEG21b2es4&amp;amp;list=PLPqlh_ebFYDFPv_tIdUb26Bit2Q2nIVrm"&gt;All
videos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2014-01-01&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ytEG21b2es4"&gt;User Input, Simple world,
Scrolling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=LFxfepBSgEc"&gt;Simple Collision
detection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2014-01-02&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=SsAi00wv0vU"&gt;Simple Collision bump vs
push&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=2-FFMP67atU"&gt;Power ups and grow and
shrink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Art"></category><category term="Code"></category></entry><entry><title>Heka, World!</title><link href="https://russell.ballestrini.net/heka-world/" rel="alternate"></link><published>2013-12-20T18:25:00-05:00</published><updated>2013-12-20T18:25:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-12-20:/heka-world/</id><summary type="html">&lt;p&gt;This post serves as a &amp;quot;Hello World&amp;quot; for the &lt;a class="reference external" href="https://github.com/mozilla-services/heka"&gt;data collection and
processing software called
Heka&lt;/a&gt;. Heka is written in
Go and was open sourced by Mozilla, the same fabulous group that brings
us Firefox!&lt;/p&gt;
&lt;p&gt;I intend to use Heka to replace Logstash agents by sending logs directly
to ElasticSearch …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post serves as a &amp;quot;Hello World&amp;quot; for the &lt;a class="reference external" href="https://github.com/mozilla-services/heka"&gt;data collection and
processing software called
Heka&lt;/a&gt;. Heka is written in
Go and was open sourced by Mozilla, the same fabulous group that brings
us Firefox!&lt;/p&gt;
&lt;p&gt;I intend to use Heka to replace Logstash agents by sending logs directly
to ElasticSearch and continuing to use Kibana3 for visualizations. Also
I aim to start collecting metrics and sending to a central Whisper
back-end to fuel Graphite charts. All that we need to make Heka take on
these responsibilities is one binary and some custom configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heka:&lt;/strong&gt; Hello, World!&lt;/p&gt;
&lt;p&gt;This mostly contrived example will show how to use Heka to watch
&lt;tt class="docutils literal"&gt;/tmp/input.log&lt;/tt&gt; and write to &lt;tt class="docutils literal"&gt;/tmp/output.log&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; install Heka&lt;/p&gt;
&lt;p&gt;Compile from source or install the &lt;a class="reference external" href="https://github.com/mozilla-services/heka/releases"&gt;Heka
package&lt;/a&gt; for your
operating system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; create a Heka TOML configuration file&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/tmp/hello-heka.conf:&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[hello_heka_input_log]
type = &amp;quot;LogstreamerInput&amp;quot;
log_directory = &amp;quot;/tmp&amp;quot;
file_match = 'input\.log'

[hello_heka_output_log]
type = &amp;quot;FileOutput&amp;quot;
message_matcher = &amp;quot;TRUE&amp;quot;
path = &amp;quot;/tmp/output.log&amp;quot;
perm = &amp;quot;664&amp;quot;
encoder = &amp;quot;hello_heka_output_encoder&amp;quot;

[hello_heka_output_encoder]
type = &amp;quot;PayloadEncoder&amp;quot;
append_newlines = false
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; start Hekad and test!&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 1:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo hekad -config=/tmp/hello-heka.conf
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 2:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
tail -f /tmp/output.log
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;in terminal 3:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
echo 'Heka, World!' &amp;gt;&amp;gt; /tmp/input.log
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Like magic the data appended to &lt;tt class="docutils literal"&gt;input.log&lt;/tt&gt; will appear in
&lt;tt class="docutils literal"&gt;output.log&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Heka also has great docs and a number of input and output plugins, but
don't take my word for it, try it yourself!&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>sensu-salt</title><link href="https://russell.ballestrini.net/sensu-salt/" rel="alternate"></link><published>2013-12-17T11:35:00-05:00</published><updated>2013-12-17T11:35:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-12-17:/sensu-salt/</id><summary type="html">&lt;p&gt;A while back I explained how to &lt;a class="reference external" href="/create-your-own-fleet-of-servers-with-digital-ocean-and-salt-cloud/"&gt;Create your own fleet of servers with
Digital Ocean and
salt-cloud&lt;/a&gt;.
Today I will extend that post and show how I deployed a test environment
for &lt;a class="reference external" href="https://sensuapp.org/"&gt;Sensu, an open source monitoring
framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before I test out new infrastructure software, I always attempt to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back I explained how to &lt;a class="reference external" href="/create-your-own-fleet-of-servers-with-digital-ocean-and-salt-cloud/"&gt;Create your own fleet of servers with
Digital Ocean and
salt-cloud&lt;/a&gt;.
Today I will extend that post and show how I deployed a test environment
for &lt;a class="reference external" href="https://sensuapp.org/"&gt;Sensu, an open source monitoring
framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before I test out new infrastructure software, I always attempt to write
deployment manifests. I subject myself to this exercise for the
following reasons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;to get better at configuration management (self growth)&lt;/li&gt;
&lt;li&gt;to enable quick setup and tear down of test environments (save money)&lt;/li&gt;
&lt;li&gt;to make sure the new software may be deployed and maintained in
configuration management (a must)&lt;/li&gt;
&lt;li&gt;to document the install process and leave comments (my notes double
as automation scripts!)&lt;/li&gt;
&lt;li&gt;to allow knowledge transfer (sharing is caring, you're welcome!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The state formulas in this post were tested on Ubuntu 12.04.3 x64bit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clone or fork the Salt-State tree&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/russellballestrini/sensu-salt"&gt;https://github.com/russellballestrini/sensu-salt&lt;/a&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
git clone https://github.com/russellballestrini/sensu-salt.git
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Declare deployment targets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before deployment, we must declare some targets in the top.sls file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
'*':
  - git

'sensu-client*':
  - sensu.client

'sensu-server*':
  - rabbitmq.server
  - redis.server
  - sensu.server
  - sensu.client
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Stand up Sensu test environment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used the following &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;salt-cloud&lt;/span&gt;&lt;/tt&gt; command to create the sensu monitor
server:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt-cloud --profile ubuntu_do sensu-server &amp;amp;&amp;amp; salt 'sensu-server' state.highstate
&lt;/pre&gt;
&lt;p&gt;Once the sensu-server was live, I altered the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;client-config.json&lt;/span&gt;&lt;/tt&gt; and
modified the RabbitMQ host with the new &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;sensu-server&lt;/span&gt;&lt;/tt&gt;'s IP or DNS
record.&lt;/p&gt;
&lt;p&gt;I then spun up 4 sensu-clients using the following command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt-cloud -P --profile ubuntu_do sensu-client1 sensu-client2 sensu-client3 sensu-client4 &amp;amp;&amp;amp; salt 'sensu-client*' state.highstate
&lt;/pre&gt;
&lt;p&gt;This caused 4 cloud servers to be spawned in parallel, and when the
provisioning finished, they instantly appeared in the
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;sensu-dashboard&lt;/span&gt;&lt;/tt&gt; which runs on the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;sensu-server&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tear down Sensu test environment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Later when I was done messing with writing checks, I used the following
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;salt-cloud&lt;/span&gt;&lt;/tt&gt; commands to destroy the sensu server and clients:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt-cloud --destroy sensu-server
salt-cloud --destroy sensu-client1 sensu-client2 sensu-client3 sensu-client4
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Hackathon 2013 Virtualization</title><link href="https://russell.ballestrini.net/hackathon-2013-virtualization/" rel="alternate"></link><published>2013-12-13T20:56:00-05:00</published><updated>2013-12-13T20:56:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-12-13:/hackathon-2013-virtualization/</id><summary type="html">&lt;p&gt;As a warning before we dive into things, this post is less of a formal
publication and more of a stream of conscience.&lt;/p&gt;
&lt;p&gt;My employer &lt;a class="reference external" href="https://newcars.com/jobs/"&gt;newcars.com&lt;/a&gt; has allowed the
technical staff to host hackathon! Over the past couple weeks I have had
quite a few ideas tumbling around in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As a warning before we dive into things, this post is less of a formal
publication and more of a stream of conscience.&lt;/p&gt;
&lt;p&gt;My employer &lt;a class="reference external" href="https://newcars.com/jobs/"&gt;newcars.com&lt;/a&gt; has allowed the
technical staff to host hackathon! Over the past couple weeks I have had
quite a few ideas tumbling around in my head:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Standup a central logging server&lt;/li&gt;
&lt;li&gt;Standup Sensu for monitoring&lt;/li&gt;
&lt;li&gt;Bake-off and document some KVM virtualization hypervisors (Ubuntu or
SmartOS)&lt;/li&gt;
&lt;li&gt;Test Docker and document findings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately I have chosen to dedicate my time to testing out
virtualization on the new Cisco UCS Blade servers. I plan to test Ubuntu
KVM first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KVM (Ubuntu 12.04)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I decided to use a Salt-stack configuration management formulas to
document how I transformed &lt;tt class="docutils literal"&gt;kvmtest02&lt;/tt&gt; (a regular Ubuntu 12.04 server)
into a KVM hypervisor. I use &lt;tt class="docutils literal"&gt;kvmtest0a&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;kvmtest02b&lt;/tt&gt; when
refering to the virtual machines living on the hypervisor. Here is the
formula:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;kvm/init.sls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# https://help.ubuntu.com/community/KVM
# Ubuntu server a KVM - Kernel Virtual Machine hypervisor

# the official ubuntu document suggests we install the following
kvm-hypervisor:
  pkg.installed:
    - names:
      - qemu-kvm
      - libvirt-bin
      - ubuntu-vm-builder
      - bridge-utils

# we need to make all of our ops people part of the libvirtd group
# so that they may create and manipulate guests. we skip this for now
# and assume all virtual machines will be owned by root.

# these are optional packages which gives us a GUI for the hypervisor
virt-manager-and-viewer:
  pkg.installed:
    - names:
      - virt-manager
      - virt-viewer
    - require:
      - pkg: kvm-hypervisor

# create a directory to hold virtual machine image files
kvm-image-dir:
  file.directory:
    - name: /cars/vms
    - user: root
    - group: root
    - mode: 775
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;I used the following to install the formula to the test hypervisor:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt 'kvmtest02.example.com' state.highstate
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;The salt highstate reported everything was good (green), so I moved on
to setting up the bridge networking interface. At this point I don't
want to figure out the logistics setting up the network bridge in
configuration management, so I simply manually edited
&lt;tt class="docutils literal"&gt;/etc/network/interfaces&lt;/tt&gt; to look like this (substitute your own
network values):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
#auto eth0
#iface eth0 inet manual

# https://help.ubuntu.com/community/KVM/Networking
# create a bridge so guest VMs may have their own identities
auto br0
iface br0 inet static
    address XXX.XX.89.42
    netmask 255.255.255.0
    network XXX.XX.89.0
    broadcast XXX.XX.89.255
    gateway XXX.XX.89.1

    # dns-* options are implemented by the resolvconf package
    dns-nameservers XXX.XX.254.225 XXX.XX.254.225
    dns-search example.com

    # bridge_* options are implemented by bridge-utils package
    bridge_ports eth0
    bridge_stp off
    bridge_fd 0
    bridge_maxwait 0
&lt;/pre&gt;
&lt;p&gt;Then, I crossed my fingers and reloaded the network stack using this
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/etc/init.d/networking restart
&lt;/pre&gt;
&lt;p&gt;I also used this to &amp;quot;bounce&amp;quot; the bridge network interface:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ifdown br0 &amp;amp;&amp;amp; ifup br0
&lt;/pre&gt;
&lt;p&gt;I verified with &lt;tt class="docutils literal"&gt;ifconfig&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;I'm ready to create my first VM. There are many different ways to boot
the VM and install the operating system. KVM is fully virtualized so
nearly any operating system may be install on the VM.&lt;/p&gt;
&lt;p&gt;If you have not already, please get familiarized with the following two
commands:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;virsh&lt;/li&gt;
&lt;li&gt;virt-install&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;virsh&lt;/tt&gt; command is an unified tool / API for working with
hypervisors that support the libvirt library. Currently &lt;tt class="docutils literal"&gt;virsh&lt;/tt&gt;
supports Xen, QEmu, KVM, LXC, OpenVZ, VirtualBox and VMware ESX. For
more information run &lt;tt class="docutils literal"&gt;virsh help&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-install&lt;/span&gt;&lt;/tt&gt; command line tool is used to create new KVM, Xen,
or Linux container guests using the &amp;quot;libvirt&amp;quot; hypervisor management
library. For more information run &lt;tt class="docutils literal"&gt;man &lt;span class="pre"&gt;virt-install&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;blockquote&gt;
Woah, virsh and virt-install both support LXC?&lt;/blockquote&gt;
&lt;p&gt;We have decided to only support Ubuntu 12.04 at this time, so obviously
we will choose that for our guest's OS. Now we need to decide on an
installation strategy. We may use the following techniques to perform an
install:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;boot from local CD-rom&lt;/li&gt;
&lt;li&gt;boot from local ISO&lt;/li&gt;
&lt;li&gt;boot from PXE server on our local vLAN&lt;/li&gt;
&lt;li&gt;boot from netboot image from anywhere in the world&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will choose the PXE boot strategy because our vLAN environment
already uses that for physical hosts.&lt;/p&gt;
&lt;p&gt;We will use the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-install&lt;/span&gt;&lt;/tt&gt; helper tool to create the virtual
machine's &amp;quot;hardware&amp;quot; with various flags. Lets document the creation of
this guest in a simple bash script so we may reference it again in the
future.&lt;/p&gt;
&lt;p&gt;/tmp/create-kvmtest02-a.sh:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
HOSTNAME=kvmtest02-a
DOMAIN=example.com

sudo virt-install \
  --connect qemu:///system \
  --virt-type kvm \
  --name $HOSTNAME \
  --vcpu 2 \
  --ram 4096 \
  --disk /cars/vms/$HOSTNAME.qcow2,size=20 \
  --os-type linux \
  --graphics vnc \
  --network bridge=br0,mac=RANDOM \
  --autostart \
  --pxe
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;This was not used, but shows the flags to perform a netboot from
Internet:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
--location=https://archive.ubuntu.com/ubuntu/dists/raring/main/installer-amd64/ \
--extra-args=&amp;quot;auto=true priority=critical keymap=us locale=en_US hostname=$HOSTNAME domain=$DOMAIN url=http://192.168.1.22/my-debconf-preseed.txt&amp;quot;
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;I created the vm:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
bash /tmp/create-kvmtest02-a.sh
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-install&lt;/span&gt;&lt;/tt&gt; drops you into the &amp;quot;console&amp;quot; of the VM, but this will
not work yet, so we use ctrl+] to break out and get back to our
hypervisor. Use &lt;tt class="docutils literal"&gt;virsh list&lt;/tt&gt; to list all the currently running VMs.&lt;/p&gt;
&lt;p&gt;Lets use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-viewer&lt;/span&gt;&lt;/tt&gt; to view the VMs display. For this we need to
SSH to the hypervisor and forward our display to our workstation, we do
this with the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-X&lt;/span&gt;&lt;/tt&gt; flag. For example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ssh -X kvmtest02
&lt;/pre&gt;
&lt;p&gt;Now we can launch &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-viewer&lt;/span&gt;&lt;/tt&gt; on the remote hypervisor, and the GUI
will be drawn on our local X display!&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-viewer kvmtest02-a
&lt;/pre&gt;
&lt;p&gt;Once I got that to work, I also tested &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-manager&lt;/span&gt;&lt;/tt&gt; which gives a
GUI to control all guests on the remote hypervisor.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-manager
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Now we need to determine the auto-generated MAC Address of the new
virtual machine.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virsh dumpxml kvmtest02-a | grep -i &amp;quot;mac &amp;quot;
  mac address='52:54:00:47:86:8e'
&lt;/pre&gt;
&lt;p&gt;We need to add this MAC address to our PXE server's DHCP configuration
to allocate the IP and tell it where to PXE-boot from.&lt;/p&gt;
&lt;p&gt;During a real deployment we would get an IP address allocated and an A
record and PTR setup for new servers. This is a test and I will be
destroying all traces of this virtual machine after presenting during
the hackathon, so for now I'm going to skip the DNS entries and &amp;quot;steal&amp;quot;
an IP address. I must be VERY careful not to use an IP address already
in production. First use dig to find an IP without a record, then
attempt to ping and use NMAP on the IP.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
dig -x XXX.XX.89.240 +short
ping XXX.XX.89.240
nmap XXX.XX.89.240 -PN
&lt;/pre&gt;
&lt;p&gt;The IP address checked out, it didn't have a PTR, it didn't respond to
pings, and using nmap proved there were no open ports. I'm very
confident this IP address is not in use.&lt;/p&gt;
&lt;p&gt;I added a record to our DHCP / PXE server for this Virtual Machine. I
attempted multiple times to pxe boot the VM, but the network stack was
never automatically configured... The DHCP server was discovering the
new VMs MAC and offering the proper IP address, as noted by these log
lines:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Dec 13 07:57:43 pxeserver60 dhcpd: DHCPDISCOVER from 52:54:00:47:86:8e via eth0
Dec 13 07:57:43 pxeserver60 dhcpd: DHCPOFFER on xxx.xx.89.240 to 52:54:00:47:86:8e via eth0
Dec 13 07:57:44 pxeserver60 dhcpd: DHCPDISCOVER from 52:54:00:47:86:8e via eth0
Dec 13 07:57:44 pxeserver60 dhcpd: DHCPOFFER on xxx.xx.89.240 to 52:54:00:47:86:8e via eth0
Dec 13 07:57:48 pxeserver60 dhcpd: DHCPDISCOVER from 52:54:00:47:86:8e via eth0
Dec 13 07:57:48 pxeserver60 dhcpd: DHCPOFFER on xxx.xx.89.240 to 52:54:00:47:86:8e via eth0
&lt;/pre&gt;
&lt;p&gt;I wasted about 4 hours attempting to troubleshoot and diagnose why the
VM wouldn't work with DHCP. I ended the night without any guests
online...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Next DAY!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So today I decided to stop trying to get DHCP and PXE working. I
downloaded an Ubuntu server ISO to the hypervisor, and used
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;virt-manager&lt;/span&gt;&lt;/tt&gt; to mount the ISO on the guest and booted for a manual
operating system install.&lt;/p&gt;
&lt;p&gt;This did two things, it proved that the hypervisor's network bridge
&lt;tt class="docutils literal"&gt;br0&lt;/tt&gt; worked for static network assigned settings and that something
between the DHCP server and the hypervisor was preventing the
&lt;tt class="docutils literal"&gt;DHCPOFFER&lt;/tt&gt; answer from getting back to the VM. I looked into iptables
firewall, removed apparmor, removed SELINUX and reviewed countless logs
looking for hints... then moved on...&lt;/p&gt;
&lt;p&gt;I was able to get salt-minion installed on the vm using our
post-install-salt-minion.sh script, which I manually downloaded from the
salt master. But to keep this test self contained, I pointed the VM's
salt-minion to &lt;tt class="docutils literal"&gt;kvmtest02&lt;/tt&gt; which we already had setup as a test
salt-master.&lt;/p&gt;
&lt;p&gt;The salt-master saw the salt-minion's key right away, so I decided to
target an install. This what I applied to the VM:&lt;/p&gt;
&lt;p&gt;salt/top.sls:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
'kvmtest02.example.com':
  - kvm

'kvmtest02b.example.com':
  - virtualenv
  - python-ldap
  - nginx
  - the-gateway
&lt;/pre&gt;
&lt;p&gt;salt-pillar/top.sls&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# kvmtest02b gateway in a VM experiment
'kvmtest02b.example.com':
  - nginx
  - the-gateway.alpha
  - deployment-keys.the-gateway-alpha
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;The stack was successfully deployed to the VM and proved that virtual
machines are a viable solution for stage or production. It also gave me
the change to test out this particular deployment again and found a few
gotchas we need to create maintenance tickets for.&lt;/p&gt;
&lt;p&gt;Without configuration management, it would have taken weeks to deploy
this custom application stack. The install with configuration management
took less then 10 minutes!&lt;/p&gt;
&lt;p&gt;One of the KVM related snags I ran into was that Nginx does some fun
calculations with cpu cache to determine hash table sizes. As a
temporary work around, until I can devote more research time, I raised
up the following three hash table directives in the http section of
nginx.conf:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
server_names_hash_bucket_size 512;
types_hash_bucket_size 512;
types_hash_max_size 4096;
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;SmartOS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;snippet from /etc/dhcp/dhcpd.conf&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# SmartOS hypervisor group to boot image
group &amp;quot;smartos-hypervisors&amp;quot; {
  next-server xxx.xx.89.71;

   host smrtest01-eth0 {
        hardware ethernet 00:25:B5:02:07:DF;
        option host-name &amp;quot;ncstest01&amp;quot;;
        fixed-address smrtest01.example.com;

        if exists user-class and option user-class = &amp;quot;iPXE&amp;quot; {
           filename = &amp;quot;smartos/menu.ipxe&amp;quot;;
        } else {
           filename = &amp;quot;smartos/undionly.kpxe&amp;quot;;
        }
    }

}
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
mkdir /cars/tftp/smartos
cd /cars/tftp/smartos
wget http://boot.ipxe.org/undionly.kpxe
wget https://download.joyent.com/pub/iso/platform-latest.tgz
tar -xzvf platform-latest.tgz
mv platform-20130629T040542Z 20130629T040542Z
mkdir platform
mv i86pc/ platform/
&lt;/pre&gt;
&lt;p&gt;create boot menu that we referenced,
&lt;tt class="docutils literal"&gt;vim /cars/tftp/smartos/menu.ipxe&lt;/tt&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!ipxe

kernel /smartos/20130629T040542Z/platform/i86pc/kernel/amd64/unix
initrd /smartos/20130629T040542Z/platform/i86pc/amd64/boot_archive
boot
&lt;/pre&gt;
&lt;p&gt;Make sure to replace platform version with current.&lt;/p&gt;
&lt;p&gt;I was able to get the blade server to PXE boot the image, but it seems
SmartOS doesn't really support the SANs. SmartOS really expects to see
local disks, and to build a ZFS pool on top of that. Basically SmartOS
could be used to build a SAN, so they didn't put much effort in
supporting SANs. After I figured this out I abandoned this test. We
could revist this again, using one of the Dell servers, or use it to
stand up a really powerful Alpha server environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LXC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Run /usr/bin/httpd in a linux container guest (LXC). Resource usage is
capped at 512 MB of ram and 2 host cpus:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-install \
--connect lxc:/// \
--name lxctest02-a \
--ram 512 \
--vcpus 2 \
--init /usr/bin/httpd
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Discussion points&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Why doesn't DHCP work on bridge?&lt;/li&gt;
&lt;li&gt;If we use virtualization, we need to come up with a plan for IP
addresses, like possibly allocate ~5 IP addresses to a hypervisor
host&lt;/li&gt;
&lt;li&gt;We need to come up with a naming convention for guests, in testing I
appended a letter to the hypervisor name &lt;tt class="docutils literal"&gt;kvmtest02&lt;/tt&gt; so the guests
names were &lt;tt class="docutils literal"&gt;kvmtest02a&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;kvmtest02b&lt;/tt&gt;, is this plausible going
forward?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;If I had more time ...&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I would have liked to test out LXC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would have liked to test out Docker&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I would have liked to test out physical to virtual migrations&lt;/p&gt;
&lt;/li&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Project"></category></entry><entry><title>Backup all virtual machines on a SmartOS hypervisor with smart-back.sh</title><link href="https://russell.ballestrini.net/backup-all-virtual-machines-on-a-smartos-hypervisor-with-smart-back-sh/" rel="alternate"></link><published>2013-10-27T20:45:00-04:00</published><updated>2013-10-27T20:45:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-10-27:/backup-all-virtual-machines-on-a-smartos-hypervisor-with-smart-back-sh/</id><summary type="html">&lt;p&gt;This post will explain how to create a cronjob to backup of every
virtual machine on a SmartOS hypervisor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create&lt;/strong&gt; the following bash script in /opt/smart-back.sh:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/bash

# Backup all virtual machines on a SmartOS hypervisor
# Author:  russell&amp;#64;ballestrini.net
# Website: https://russell.ballestrini.net/

# Backup directory …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;This post will explain how to create a cronjob to backup of every
virtual machine on a SmartOS hypervisor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create&lt;/strong&gt; the following bash script in /opt/smart-back.sh:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/bash

# Backup all virtual machines on a SmartOS hypervisor
# Author:  russell&amp;#64;ballestrini.net
# Website: https://russell.ballestrini.net/

# Backup directory without trailing slash
backupdir=/opt/backups

# temp dir where we ZFS send and gzip before moving to backupdir
tmpdir=/opt

svcadm enable autofs

for VM in `vmadm list -p -o alias,uuid`
  do
    # create an array called VM_PARTS splitting on ':'
    IFS=':' VM_PARTS=($VM)

    # create some helper varibles for alias and uuid
    alias=${VM_PARTS[0]}
    uuid=${VM_PARTS[1]}

    # echo &amp;quot;Backup started for $VM&amp;quot;
    vmadm send $uuid &amp;gt; $tmpdir/$alias

    # echo &amp;quot;Starting $VM&amp;quot;
    vmadm start $uuid

    # disk space is cheap, uncomment if you disagree.
    #pbzip2 $tmpdir/$alias

  done

mv $tmpdir/*.bz2 $backupdir
&lt;/pre&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Create&lt;/strong&gt; a cronjob entry to schedule the backups:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
crontab -e
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
2 6 * * 0 /usr/bin/bash /opt/smart-back.sh
&lt;/pre&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If I expand on this script much more, I plan to stick it into revision
control.&lt;/p&gt;
&lt;p&gt;If you look closely, I have also added a hack to enable autofs
(&lt;tt class="docutils literal"&gt;svcadm enable autofs&lt;/tt&gt;) which allows me to automount an NFS
share on my remote FreeNAS by setting
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;backupdir=/net/[ip-or-fqdn-of-freenas]/mnt/zfs-mirror/backup/vms&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;We have scheduled a backup of each virtual machine on your SmartOS
hypervisor!&lt;/p&gt;
&lt;p&gt;If or when the time comes to restore a VM from a backup, use the
following:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# decompress the backup file.
pbzip2 -d backup-file.bz2

# ingest the backup file into the hypervisor.
vmadm receive -f /path/to/backup-file
&lt;/pre&gt;
&lt;p&gt;Just make sure the VM doesn't currently exist on the hypervisor.&lt;/p&gt;
&lt;p&gt;This strategy is great for complete backups of machines which could be
used during a manual migration, or if corruption happened to the VM and
we wanted to restore to a previous version.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Simplify deployments with Upstart and uWSGI</title><link href="https://russell.ballestrini.net/simplify-deployments-with-upstart-and-uwsgi/" rel="alternate"></link><published>2013-10-19T22:35:00-04:00</published><updated>2013-10-19T22:35:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-10-19:/simplify-deployments-with-upstart-and-uwsgi/</id><summary type="html">&lt;p&gt;As you know from my previous post, I recently &lt;a class="reference external" href="https://russell.ballestrini.net/honey-i-just-deleted-linkpeek-com/"&gt;deleted
LinkPeek.com&lt;/a&gt;
and after struggling to get it back online, I vowed to start utilizing
configuration management. During this exercise, I noticed that the
architecture I use in production seems overly complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The current production deployment stack:&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nginx listen on …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;As you know from my previous post, I recently &lt;a class="reference external" href="https://russell.ballestrini.net/honey-i-just-deleted-linkpeek-com/"&gt;deleted
LinkPeek.com&lt;/a&gt;
and after struggling to get it back online, I vowed to start utilizing
configuration management. During this exercise, I noticed that the
architecture I use in production seems overly complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The current production deployment stack:&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nginx listen on 80/443 proxy upstream 9901/9902&lt;/li&gt;
&lt;li&gt;Upstart =&amp;gt; Supervisord =&amp;gt; Cherrypy/Paste listen on 9901/9902&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Each of these services and processes have their own configuration files
which must work together. Upstart needs to know the location of
Supervisord's configuration files. Supervisord needs to know the
location of Cherrypy/PasteDeploy's configuration files. Supervisord also
must bring up a specified number of worker processes who listen on a
pool of ports. Nginx needs to proxy upstream to that pool of worker
ports.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This architecture seems difficult to automate because of the numerous
places errors may sneak in. I started researching an alternative
architecture and stumbled upon Nicholas Piel's &lt;a class="reference external" href="https://nichol.as/benchmark-of-python-web-servers"&gt;Benchmark of Python Web
Servers&lt;/a&gt;. The
graphs Nicholas compiled allowed me to narrow down a list of potential
replacements.&lt;/p&gt;
&lt;p&gt;I chose to review uWSGI first because it was a top contender on every
chart. After briefly testing uWSGI, I halted my explorations and
selected it as the winner. I know you probably feel that was a premature
decision but uWSGI fit what I was looking for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The new simplified stack:&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nginx listen on 80/443 proxy upstream 5200&lt;/li&gt;
&lt;li&gt;Upstart =&amp;gt; uWSGI listen on 5200&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The uWSGI server possesses great performance out-of-the-box but also
presents many options for fine tuning. These options may be specified
either directly in the Upstart script using flags or in a configuration
file (like a Pyramid .ini). Either way, the stack uses less files, less
processes, and less complexity then the original architecture. For
&lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek&lt;/a&gt; deployments I decided to place all
uWSGI related configuration directly in the Upstart script which I
embedded below:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;linkpeek/weblinkpeek.conf | Upstart for starting uWSGI&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
description &amp;quot;Start the uWSGI master for the LinkPeek WEB&amp;quot;

start on runlevel [2345]
stop on runlevel [!2345]

respawn

# run service as linkpeek user
setuid linkpeek
setgid linkpeek

# uWSGI command to execute and treat as a daemon
exec /path/to/linkpeek/web/env/bin/uwsgi --master --processes=4 --http=127.0.0.1:5200 --die-on-term --logto2=/path/to/linkpeek/web/uwsgi.log --virtualenv=/path/to/linkpeek/web/env --ini-paste=/path/to/linkpeek/web/linkpeek/production.ini
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;The web application is now daemonized and listening on port 5200 with 4
workers. I leave implementing the Nginx front end to the reader.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Opinion"></category></entry><entry><title>Postfix Salt State Formula</title><link href="https://russell.ballestrini.net/postfix-salt-state-formula/" rel="alternate"></link><published>2013-10-17T21:28:00-04:00</published><updated>2013-10-17T21:28:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-10-17:/postfix-salt-state-formula/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;img alt="postfix-config-management" src="/uploads/2013/10/mysza.gif" /&gt;&lt;/p&gt;
&lt;p&gt;The following formula was tested on Ubuntu and Debian however it would
not take much work to test on other operating systems.&lt;/p&gt;
&lt;p&gt;This state formula will install postfix and mutt. The postfix service
will watch various configuration files for changes and restart
accordingly.&lt;/p&gt;
&lt;p&gt;This formula will also manage and watch the /etc/aliases file and invoke
the &lt;em&gt;newaliases&lt;/em&gt; command to initialize or re-initialize the alias
database. This formula will also manage and watch the
/etc/postfix/virtual file and invoke the &lt;em&gt;postmap&lt;/em&gt; command to create or
update a managed Postfix lookup table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;postfix/init.sls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Install mutt and postfix mutt packages.
#
# This formula supports setting an optional:
#
#  * 'aliases' file
#  * 'virtual' map file
#
# Both aliases and virtual use a pillar data schema
# which takes the following form:
#
# postfix:
#   aliases: |
#       postmaster: root
#       root: testuser
#       testuser: russell&amp;#64;example.com
#   virtual: |
#       example.com             this is a comment
#       test1&amp;#64;example.com       me&amp;#64;example.com
#       test2&amp;#64;example.com       me&amp;#64;example.com
#

# install mutt
mutt:
  pkg:
    - installed

# install postfix have service watch main.cf
postfix:
  pkg:
    - installed
  service:
    - running
    - enable: True
    - watch:
      - pkg: postfix
      - file: /etc/postfix/main.cf

# postfix main configuration file
/etc/postfix/main.cf:
  file.managed:
    - source: salt://postfix/main.cf
    - user: root
    - group: root
    - mode: 644
    - template: jinja
    - require:
      - pkg: postfix

# manage /etc/aliases if data found in pillar
{% if 'aliases' in pillar.get('postfix', '') %}
/etc/aliases:
  file.managed:
    - source: salt://postfix/aliases
    - user: root
    - group: root
    - mode: 644
    - template: jinja
    - require:
      - pkg: postfix

run-newaliases:
  cmd.wait:
    - name: newaliases
    - cwd: /
    - watch:
      - file: /etc/aliases
{% endif %}

# manage /etc/postfix/virtual if data found in pillar
{% if 'virtual' in pillar.get('postfix', '') %}
/etc/postfix/virtual:
  file.managed:
    - source: salt://postfix/virtual
    - user: root
    - group: root
    - mode: 644
    - template: jinja
    - require:
      - pkg: postfix

run-postmap:
  cmd.wait:
    - name: /usr/sbin/postmap /etc/postfix/virtual
    - cwd: /
    - watch:
      - file: /etc/postfix/virtual
{% endif %}
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;postfix/aliases:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Managed by config management
# See man 5 aliases for format
{{pillar['postfix']['aliases']}}
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;postfix/virtual:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Managed by config management
{{pillar['postfix']['virtual']}}
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;postfix/main.cf:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Managed by config management
# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate &amp;quot;delayed mail&amp;quot; warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

myhostname = {{ grains['fqdn'] }}
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = {{ grains['fqdn'] }}, localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all

{% if 'virtual' in pillar.get('postfix','') %}
virtual_alias_maps = hash:/etc/postfix/virtual
{% endif %}
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Control a MongoDB collection in configuration management</title><link href="https://russell.ballestrini.net/control-a-mongodb-collection-in-configuration-management/" rel="alternate"></link><published>2013-10-14T21:39:00-04:00</published><updated>2013-10-14T21:39:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-10-14:/control-a-mongodb-collection-in-configuration-management/</id><summary type="html">&lt;p&gt;This post explains how to use configuration management (Salt Stack) to
completely control a MongoDB collection. In our example we want to
control a store's collection of plans.&lt;/p&gt;
&lt;p&gt;First we create a JSON representation of the collection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mongodb/plan.json:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{
  &amp;quot;_id&amp;quot; : { &amp;quot;$oid&amp;quot; : &amp;quot;4ef8b9e2be329f491d98f74b&amp;quot; },
  &amp;quot;cost&amp;quot; : 20, &amp;quot;description&amp;quot; : &amp;quot;development&amp;quot;,
  &amp;quot;name&amp;quot; : &amp;quot;good&amp;quot;, &amp;quot;count …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;This post explains how to use configuration management (Salt Stack) to
completely control a MongoDB collection. In our example we want to
control a store's collection of plans.&lt;/p&gt;
&lt;p&gt;First we create a JSON representation of the collection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mongodb/plan.json:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{
  &amp;quot;_id&amp;quot; : { &amp;quot;$oid&amp;quot; : &amp;quot;4ef8b9e2be329f491d98f74b&amp;quot; },
  &amp;quot;cost&amp;quot; : 20, &amp;quot;description&amp;quot; : &amp;quot;development&amp;quot;,
  &amp;quot;name&amp;quot; : &amp;quot;good&amp;quot;, &amp;quot;count&amp;quot; : 6000
}
{
  &amp;quot;_id&amp;quot; : { &amp;quot;$oid&amp;quot; : &amp;quot;4ef8b9e8be329f491d98f74c&amp;quot; },
  &amp;quot;cost&amp;quot; : 60, &amp;quot;description&amp;quot; : &amp;quot;freelancers&amp;quot;,
  &amp;quot;name&amp;quot; : &amp;quot;better&amp;quot;, &amp;quot;count&amp;quot; : 36000
}
{
  &amp;quot;_id&amp;quot; : { &amp;quot;$oid&amp;quot; : &amp;quot;4ef8b9f0be329f491d98f74d&amp;quot; },
  &amp;quot;cost&amp;quot; : 180, &amp;quot;description&amp;quot; : &amp;quot;production&amp;quot;,
  &amp;quot;name&amp;quot; : &amp;quot;best&amp;quot;, &amp;quot;count&amp;quot; : 162000
}
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Next we configure a salt state formula to manage the JSON file and watch
it for changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mongodb/init.sls:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# install mongodb server
mongodb-server:
  pkg:
    - installed

# manage the store's plan.json
/tmp/plan.json:
  file.managed:
    - source: salt://mongodb/plan.json
    - user: root
    - group: root
    - mode: 644

# import the plan collection if it changes
import-plan-collection:
    cmd.wait:
      - name: mongoimport --db=store --collection=plan --upsert /tmp/plan.json
      - require:
        - pkg: mongodb-server
      - watch:
        - file: /tmp/plan.json
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;em&gt;Now whenever plan.json is altered in configuration management, the file
on the minion will update which will trigger a mongoimport with upsert
to occur.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Optionally, we could replace &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--upsert&lt;/span&gt;&lt;/tt&gt; with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--drop&lt;/span&gt;&lt;/tt&gt; which will
drop the collection before re-importing thus removing stale records.&lt;/p&gt;
&lt;p&gt;We now have a version controlled JSON file in configuration management
and the power of MongoDB Document Objects in our application code!&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Configuration Management vs Remote Execution</title><link href="https://russell.ballestrini.net/configuration-management-vs-remote-execution/" rel="alternate"></link><published>2013-07-11T23:15:00-04:00</published><updated>2013-07-11T23:15:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-07-11:/configuration-management-vs-remote-execution/</id><summary type="html">&lt;p class="first last"&gt;It's about time to learn the difference.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="config-mangement-vs-remote-execution" src="/uploads/2013/07/config-mangement-vs-remote-execution.png" /&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What is configuration management?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In a perfect world configuration management provides a centralized,
revision controlled, self-documented, change management location for
manifests and formulas which both define how to build a complete system
and organize a means of knowledge transfer. An infrastructure perfectly
described in configuration management allows any single part of the
system to be created, reproduced, multiplied, self-healed or even
re-purposed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When should I use configuration management?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use configuration management whenever you intend to permanently alter
system state or infrastructure data.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;System state - the current state of the system:&lt;/em&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;operating system (distro,kernel,patches,hot-fixes)&lt;/li&gt;
&lt;li&gt;software installed (base,role-specific)&lt;/li&gt;
&lt;li&gt;services running (base,role-specific)&lt;/li&gt;
&lt;li&gt;user access&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Infrastructure data - the information that describes the
infrastructure:&lt;/em&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;asset information (models,specs,IP,DNS,rack-elevation,etc)&lt;/li&gt;
&lt;li&gt;roles (app,web,db,proxy,load-balancer,etc)&lt;/li&gt;
&lt;li&gt;current allocations (allocated,unallocated,number of servers in each
role,etc)&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What is remote execution?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remote execution is the act of issuing commands to one or more remote
systems. The most popular remote execution system in use today is SSH.
SSH is great for maintaining a small group of dissimilar systems. Once
the system count grows and similar roles present themselves, start
looking at something like Fabric. Fabric is a python framework which
builds on top of the SSH protocol and makes it possible to invoke the
same command on hundreds of servers sequentially. Once the fleet count
reaches thousands of servers and commands must run in parallel, look at
Salt-stack's remote execution layer written with python and ZeroMQ.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When should I use remote execution?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Only use remote execution for ad hoc reports, data collection, or for
temporary tests which have no expectation of persistence after research
is complete. Frequent data collection jobs work best when implemented in
a metrics collection or monitoring system. (I'll save that for another
post)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When should I not use remote execution?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Do not use remote execution to change the state of the system or the
data which describes the infrastructure! If a remote execution job
changes either state or data it should be placed into the configuration
management for the reasons mentioned above.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>Understanding Salt Stack user and group management</title><link href="https://russell.ballestrini.net/understanding-salt-stack-user-and-group-management/" rel="alternate"></link><published>2013-07-08T13:51:00-04:00</published><updated>2013-07-08T13:51:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-07-08:/understanding-salt-stack-user-and-group-management/</id><summary type="html">&lt;p&gt;This state will create a user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
russell:
  user:
    - present
&lt;/pre&gt;
&lt;p&gt;This state will create a user and a group. This also makes the user part
of the group, and handles creating the group first:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
russell:
  group:
    - present
  user:
    - present
    - groups:
      - russell
    - require:
      - group: russell
&lt;/pre&gt;
&lt;p&gt;This state handles user and group generation …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This state will create a user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
russell:
  user:
    - present
&lt;/pre&gt;
&lt;p&gt;This state will create a user and a group. This also makes the user part
of the group, and handles creating the group first:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
russell:
  group:
    - present
  user:
    - present
    - groups:
      - russell
    - require:
      - group: russell
&lt;/pre&gt;
&lt;p&gt;This state handles user and group generation along with password and
ssh-key maintenance. This is all done securely using pillar to
parameterize arguments:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# This state will create users accounts
#
# This state requires a pillar named 'users' with data formatted like:
#
# users:
#
#  tusername:
#    fullname: Test Username
#    uid: 1007
#    gid: 1007
#    groups:
#      - sudo
#      - ops
#    crypt: $password-hash-sha512-prefered
#    pub_ssh_keys:
#      - ssh-rsa list-of-public-keys tusername-sm
#
#  anotheruser: ... snipped ...

# loop over all users presented by pillar:
# create user's group, create user, then add pub keys
{% for username, details in pillar.get('users', {}).items() %}
{{ username }}:

  group:
    - present
    - name: {{ username }}
    - gid: {{ details.get('gid', '') }}

  user:
    - present
    - fullname: {{ details.get('fullname','') }}
    - name: {{ username }}
    - shell: /bin/bash
    - home: /home/{{ username }}
    - uid: {{ details.get('uid', '') }}
    - gid: {{ details.get('gid', '') }}
    - crypt: {{ details.get('crypt','') }}
    {% if 'groups' in details %}
    - groups:
      {% for group in details.get('groups', []) %}
      - {{ group }}
      {% endfor %}
    - require:
      {% for group in details.get('groups', []) %}
      - group: {{ group }}
      {% endfor %}
    {% endif %}

  {% if 'pub_ssh_keys' in details %}
  ssh_auth:
    - present
    - user: {{ username }}
    - names:
    {% for pub_ssh_key in details.get('pub_ssh_keys', []) %}
      - {{ pub_ssh_key }}
    {% endfor %}
    - require:
      - user: {{ username }}
  {% endif %}

{% endfor %}
&lt;/pre&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Create your own fleet of servers with Digital Ocean and salt-cloud</title><link href="https://russell.ballestrini.net/create-your-own-fleet-of-servers-with-digital-ocean-and-salt-cloud/" rel="alternate"></link><published>2013-07-02T22:20:00-04:00</published><updated>2013-07-02T22:20:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-07-02:/create-your-own-fleet-of-servers-with-digital-ocean-and-salt-cloud/</id><summary type="html">&lt;p&gt;Have you heard about Digital Ocean?
They offer a polished user interface, KVM guests with SSD storage, and an API to interact with a cloud of hypervisors.
API integration got you down?
Don't worry, salt-cloud has already integrated Digital Ocean among it's list of providers!
The rest of this post …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Have you heard about Digital Ocean?
They offer a polished user interface, KVM guests with SSD storage, and an API to interact with a cloud of hypervisors.
API integration got you down?
Don't worry, salt-cloud has already integrated Digital Ocean among it's list of providers!
The rest of this post illustrates the steps I took to configure salt-cloud to work with Digital Ocean.&lt;/p&gt;
&lt;p&gt;This guide assumes you already have a:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;salt-master&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.digitalocean.com/?refcode=27e015299dc7%20"&gt;Digital Ocean&lt;/a&gt; account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Step one,&lt;/strong&gt; install the most recent version of salt-cloud.&lt;/p&gt;
&lt;p&gt;On the salt-master:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo apt-get install salt-cloud

# or if you prefer ...
pip install salt-cloud==2015.5.0

# last verify it was successfully installed
salt-cloud --version
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step two,&lt;/strong&gt; configure salt-cloud.&lt;/p&gt;
&lt;p&gt;Salt-cloud uses the following files YAML files for configuration:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;/etc/salt/cloud.conf.d/main.conf:&lt;/dt&gt;
&lt;dd&gt;This is the main configuration file. I have the following statements:&lt;/dd&gt;
&lt;/dl&gt;
&lt;pre class="literal-block"&gt;
minion:
    master: master.foxhop.net
    append_domain: foxhop.net
&lt;/pre&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;/etc/salt/cloud.providers/do.conf:&lt;/dt&gt;
&lt;dd&gt;This is a provider configuration file for Digital Ocean (do).
Collect your client_key and personal_access_token (api_key) from the Digital Ocean user dashboard.
Also create an SSH key and add the public key using the dashboard:&lt;/dd&gt;
&lt;/dl&gt;
&lt;pre class="literal-block"&gt;
# For Digital Ocean
do:
  provider: digital_ocean
  client_key: MyClientKeyLiftedFromDashboard
  personal_access_token: MyAPIKeyLiftedFromDashboard
  ssh_key_file: /keys/digital-ocean-salt-cloud
  ssh_key_name: digital-ocean-salt-cloud.pub
&lt;/pre&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;/etc/salt/cloud.profiles/do.conf:&lt;/dt&gt;
&lt;dd&gt;This is the Digital Ocean profiles configuration file.
We will create just two profiles for now, but you can create unlimited named combinations.&lt;/dd&gt;
&lt;/dl&gt;
&lt;pre class="literal-block"&gt;
ubuntu-12-04-do-512:
  provider: do
  image: ubuntu-12-04-x64
  size: 512mb
  location: nyc1

ubuntu-14-04-do-512:
  provider: do
  image: ubuntu-14-04-x64
  size: 512mb
  location: nyc1
&lt;/pre&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;ssh_key_file:&lt;/dt&gt;
&lt;dd&gt;This is your private SSH key located on your salt-master&lt;/dd&gt;
&lt;dt&gt;ssh_key_name:&lt;/dt&gt;
&lt;dd&gt;This is the name of the public key you added in your Digital Ocean dashboard&lt;/dd&gt;
&lt;dt&gt;size:&lt;/dt&gt;
&lt;dd&gt;The size or plan you would like to provision, 512mb is the smallest plan&lt;/dd&gt;
&lt;dt&gt;location:&lt;/dt&gt;
&lt;dd&gt;The geographical region, location, and/or data center&lt;/dd&gt;
&lt;dt&gt;image:&lt;/dt&gt;
&lt;dd&gt;The operating system image&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;After you configure the do provider in /etc/salt/cloud.providers you
gain access to the following commands:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt-cloud --list-sizes do
salt-cloud --list-locations do
salt-cloud --list-images do
salt-cloud --help
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Lets provision a new cloud server!&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
salt-cloud --profile ubuntu-14-04-do-512 deejay
&lt;/pre&gt;
&lt;p&gt;If all goes well you should have a newly provisioned server bootstrapped with salt-minion.
The new minion's keys are already added to the salt-master.
Now you just need to run highstate!&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Add a custom header to all Salt managed files using pillar and jinja templates</title><link href="https://russell.ballestrini.net/add-a-custom-header-to-all-salt-managed-files-using-pillar-and-jinja-templates/" rel="alternate"></link><published>2013-07-01T14:11:00-04:00</published><updated>2013-07-01T14:11:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-07-01:/add-a-custom-header-to-all-salt-managed-files-using-pillar-and-jinja-templates/</id><summary type="html">&lt;p&gt;Salt-stack (salt) provides a solution for centralized configuration
management and remote execution. One of the most basic things Salt
provides is the ability to manage the contents of a file or a directory
of files. Using Salt we can dictate the state of our minions and as a
result we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Salt-stack (salt) provides a solution for centralized configuration
management and remote execution. One of the most basic things Salt
provides is the ability to manage the contents of a file or a directory
of files. Using Salt we can dictate the state of our minions and as a
result we also gain auto-healing of configuration files.&lt;/p&gt;
&lt;p&gt;Salt will clobber local changes to managed files and force the state to
reflect the version in configuration management. In an effort to avoid
confusing the uninformed, I place a header on each managed file which
announces &amp;quot;THIS FILE IS MANAGED BY SALT&amp;quot;.&lt;/p&gt;
&lt;p&gt;To avoid repeating myself in each managed file, I came up with the
following centralized solution -&lt;/p&gt;
&lt;p&gt;First, I give all minions access to the headers pillar tree in top.sls:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
base:
  '*':
    - headers
&lt;/pre&gt;
&lt;p&gt;Next, I create a couple headers in in headers/init.sls:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
headers:
  salt:
    file: |
        ################################
        # THIS FILE IS MANAGED BY SALT #
        ################################
    directory: |
        #####################################
        # THIS DIRECTORY IS MANAGED BY SALT #
        #####################################
&lt;/pre&gt;
&lt;p&gt;Then, I parse each managed file in the state tree with jinja, for
example hosts/init.sls:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/etc/hosts:
  file.managed:
    source: salt://hosts/hosts
    user: root
    group: group
    mode: 644
    template: jinja
&lt;/pre&gt;
&lt;p&gt;Finally, in each file served by salt I add the jinja header
substitution, for example hosts/hosts:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
{{pillar['headers']['salt']['file']}}
127.0.0.1       localhost.localdomain localhost
::1     localhost6.localdomain6 localhost6
&lt;/pre&gt;
&lt;p&gt;I use this same technique to declare most configuration options as
parameters, like hostnames, IP addresses, ports, and more.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>High load and CPU usage craftbukkit compared to vanilla minecraft</title><link href="https://russell.ballestrini.net/high-load-and-cpu-usage-craftbukkit-compared-to-vanilla-minecraft/" rel="alternate"></link><published>2013-06-20T23:35:00-04:00</published><updated>2013-06-20T23:35:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-06-20:/high-load-and-cpu-usage-craftbukkit-compared-to-vanilla-minecraft/</id><summary type="html">&lt;p&gt;I started researching the best ways to use &lt;a class="reference external" href="https://bobbylikeslinux.net/post/2013/2013-salt-minecraft-fun/"&gt;salt to provision minecraft
servers&lt;/a&gt;. I wrote
a salt state formula for the vanilla minecraft server deployment. The
deployment worked out great so I decided to try my luck with plugins.&lt;/p&gt;
&lt;p&gt;In order to use plugins and mods we need to use …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I started researching the best ways to use &lt;a class="reference external" href="https://bobbylikeslinux.net/post/2013/2013-salt-minecraft-fun/"&gt;salt to provision minecraft
servers&lt;/a&gt;. I wrote
a salt state formula for the vanilla minecraft server deployment. The
deployment worked out great so I decided to try my luck with plugins.&lt;/p&gt;
&lt;p&gt;In order to use plugins and mods we need to use a customized server
package. I decided to try provisioning a craftbukkit server and quickly
noticed something was very wrong. Vanilla Craftbukkit (with no plugins)
produces very high load and CPU usage.&lt;/p&gt;
&lt;p&gt;Here is a chart for proof. The spike is when I stopped the vanilla
minecraft server and started the craftbukkit minecraft server:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2013/06/minecraft-vs-craftbukkit.png"&gt;&lt;img alt="minecraft-vs-craftbukkit" src="/uploads/2013/06/minecraft-vs-craftbukkit.png" /&gt;&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category><category term="Project"></category></entry><entry><title>Honey! I just DELETED LinkPeek.com</title><link href="https://russell.ballestrini.net/honey-i-just-deleted-linkpeek-com/" rel="alternate"></link><published>2013-05-26T13:19:00-04:00</published><updated>2013-05-26T13:19:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-05-26:/honey-i-just-deleted-linkpeek-com/</id><summary type="html">&lt;p&gt;During the day I am an ops sys-admin. During the night I am a husband,
father of two, and a CEO of a bootstrapped start-up. After launch, my
first project was to schedule regular backups of user data and archive
off-site. My goal was to &lt;a class="reference external" href="https://russell.ballestrini.net/virt-back-restoring-from-backups/"&gt;create backups but never need …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;During the day I am an ops sys-admin. During the night I am a husband,
father of two, and a CEO of a bootstrapped start-up. After launch, my
first project was to schedule regular backups of user data and archive
off-site. My goal was to &lt;a class="reference external" href="https://russell.ballestrini.net/virt-back-restoring-from-backups/"&gt;create backups but never need
them&lt;/a&gt;.
Boy was I lucky ...&lt;/p&gt;
&lt;p&gt;Yes, leave it to me to inadvertently delete the VPS root disk. One of
the major cloud providers places the &amp;quot;rename&amp;quot; and &amp;quot;remove&amp;quot; disk buttons
right next to each other and I learned a nasty habit of clearing pop-ups
without reading them (thanks Windows).&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;Honey! I just deleted LinkPeek.com&amp;quot;&lt;/blockquote&gt;
&lt;p&gt;The horror... My stomach felt like I took a tumble in a roller-coaster.
Instantly I tossed off my developer hat and put on my operations hat. I
checked the off-site backups. I had nightly dumps of MongoDB and weekly
tar backups of the /etc partition. The user data was in MongoDB and most
of the system configuration information was in the tar. I used the tar
to recover 2 upstart scripts, 2 supervisord scripts, 2 complex nginx
confs, an ssl cert, and the pyramid production.ini.&lt;/p&gt;
&lt;p&gt;I set out to stand up a new server, re-install the needed packages,
recover the user data, and restore the service. After 1.5 hours of
feverish typing, &lt;a class="reference external" href="https://linkpeek.com/"&gt;https://linkpeek.com/&lt;/a&gt; was back online.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What I learned and my plan going forward&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are a small team or a start-up, you must have somebody dedicated
to operations. Without backups I would not have been able to gracefully
recover. Most likely I would have reimbursed the existing members and
shuttered the doors.&lt;/p&gt;
&lt;p&gt;This experience was eye-opening. In my next couple of posts I will
explain how I &lt;a class="reference external" href="/automatic-backups/"&gt;create and maintain backups&lt;/a&gt; and
my next project will implement a configuration management and
provisioning system.&lt;/p&gt;
&lt;p&gt;This system will allow me to:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;take out the human element of recovery&lt;/li&gt;
&lt;li&gt;significantly reduce the time-to-recover from a catastrophic failure&lt;/li&gt;
&lt;li&gt;test disaster recovery procedures before needing them&lt;/li&gt;
&lt;li&gt;provision development and production environments without effort&lt;/li&gt;
&lt;li&gt;have a reproducible blueprint of &amp;quot;how to build a LinkPeek server&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>Survey Baby Monkey</title><link href="https://russell.ballestrini.net/survey-baby-monkey/" rel="alternate"></link><published>2013-04-26T13:47:00-04:00</published><updated>2013-04-26T13:47:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-04-26:/survey-baby-monkey/</id><summary type="html">&lt;p class="first last"&gt;baby monkey fxhp cartoon&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="baby-monkey-fxhp" src="/uploads/2013/04/baby-monkey-fxhp.jpg" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Art"></category></entry><entry><title>Automatic event hangout with cron</title><link href="https://russell.ballestrini.net/automatic-event-hangout-with-cron/" rel="alternate"></link><published>2013-04-09T09:17:00-04:00</published><updated>2013-04-09T09:17:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-04-09:/automatic-event-hangout-with-cron/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Create an online only, hangout event&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a new event with a date far into the future, like the year 2015.
Go to the event's options &amp;gt; advanced and enable 'this event is online
only' which will create a unique Hangout URI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a cronjob&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a cronjob on each device …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Create an online only, hangout event&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a new event with a date far into the future, like the year 2015.
Go to the event's options &amp;gt; advanced and enable 'this event is online
only' which will create a unique Hangout URI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a cronjob&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a cronjob on each device to open the web browser Monday-Friday at
12:49pm and open the unique Hangout URI.&lt;/p&gt;
&lt;p&gt;Firefox cron example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Run Firefox at 12:49 EST each weekday and open hangout URI
49 12 * * 1-5 export DISPLAY=:0 &amp;amp;&amp;amp; /usr/bin/firefox 'hangout-uri'
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Chromium-browser cron example:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
# Run Chromium-browser at 12:49 EST each weekday and open hangout URI
49 12 * * 1-5 export DISPLAY=:0 &amp;amp;&amp;amp; /usr/bin/chromium-browser 'hangout-uri'
&lt;/pre&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>Guido name dropped tornado python tulip and pep-3156</title><link href="https://russell.ballestrini.net/guido-name-dropped-tornado-python-tulip-and-pep-3156/" rel="alternate"></link><published>2013-03-22T22:07:00-04:00</published><updated>2013-03-22T22:07:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-03-22:/guido-name-dropped-tornado-python-tulip-and-pep-3156/</id><summary type="html">&lt;p&gt;Pycon 2013 was excellent, in fact it was my first one I have attended.&lt;/p&gt;
&lt;p&gt;I found it odd that django and Pyramid had plenty of talks but nobody
mentioned tornado.&lt;/p&gt;
&lt;p&gt;The only person that brought up tornado was Guido himself, who has been
researching and developing async python since December …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Pycon 2013 was excellent, in fact it was my first one I have attended.&lt;/p&gt;
&lt;p&gt;I found it odd that django and Pyramid had plenty of talks but nobody
mentioned tornado.&lt;/p&gt;
&lt;p&gt;The only person that brought up tornado was Guido himself, who has been
researching and developing async python since December 12th, 2012. Guido
wants to add async API libraries to python core, and has been comparing
his work to twisted and more importantly tornado. He is leveraging the
existing solutions to stay on the correct track.&lt;/p&gt;
&lt;p&gt;Async API's in Python core! This news was extra exciting for me, because
I have already learned the power of async by messing around with
tornado. Since the talk many people have approached me and asked for
more information about async API's.&lt;/p&gt;
&lt;p&gt;My favorite use for async is long polling for web applications. Here is
a diagram that shows how great tornado is:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2013/03/2013-03-22-tornado-callback-long-polling-event-loop-scaled.png"&gt;&lt;img alt="2013-03-22-tornado-callback-long-polling-event-loop-scaled" src="/uploads/2013/03/2013-03-22-tornado-callback-long-polling-event-loop-scaled.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This type of polling can support thousands of concurrent users, all
connected and waiting for a callback event to occur. I used tornado to
build &lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go&lt;/a&gt;, a real-time and
multi-player browser-based-game.&lt;/p&gt;
</content><category term="misc"></category><category term="Uncategorized"></category></entry><entry><title>virt-back's Domfetcher class returns doms from libvirt API</title><link href="https://russell.ballestrini.net/virt-backs-domfetcher-class-returns-doms-from-libvirt-api/" rel="alternate"></link><published>2013-03-02T15:04:00-05:00</published><updated>2013-03-02T15:04:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-03-02:/virt-backs-domfetcher-class-returns-doms-from-libvirt-api/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2013/03/2013-03-02-scale-virt-back-domfetcher-scaled.png"&gt;&lt;img alt="2013-03-02-scale-virt-back-domfetcher-scaled" src="/uploads/2013/03/2013-03-02-scale-virt-back-domfetcher-scaled.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'm hooked...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I attended SCaLE 11x, my first technical conference, and had an amazing
time. My favorite talk was Michael Day's &amp;quot;&lt;a class="reference external" href="https://code.ncultra.org/2013/02/scale-11x-open-virtualization/"&gt;Advancements with Open
Virtualization &amp;amp;
KVM&lt;/a&gt;&amp;quot;
(link to slides). Michael's presentation inspired me to continue my work
on virt-back.&lt;/p&gt;
&lt;p&gt;During my trip home I used the in-flight wifi to push this
&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/virt-back/commits/d6dff27323650bf784cc284f676299ffe07953cb"&gt;commit&lt;/a&gt;
into the cloud from the clouds! This particular commit re-factored the
dom object list generation into a simple-to-use class called Domfetcher.
Domfetcher abstracts the libvirt API and grants access to the following
helper methods:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;get_all_doms( )&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Return a list of all dom objects&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;get_doms_by_names( guest_names=[] )&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Accept a list of guest_names, return a list of related dom objects&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;get_running_doms( )&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Return a list of running dom objects&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;get_shutoff_doms( )&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Return a list of shutoff but defined dom objects&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;This is an example of how to use Domfetcher:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import virtback

# optionally supply hypervisor uri
domfetcher = virtback.Domfetcher()

doms = domfetcher.get_running_doms()

for dom in doms:
    print dom.name()

for dom in doms:
    print dom.info()

for dom in doms:
    print dom.shutdown()
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;As always thanks for reading!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>How to overload default function arguments in python using lambda</title><link href="https://russell.ballestrini.net/how-to-overload-default-function-arguments-in-python-using-lambda/" rel="alternate"></link><published>2013-01-12T11:22:00-05:00</published><updated>2013-01-12T11:22:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-01-12:/how-to-overload-default-function-arguments-in-python-using-lambda/</id><summary type="html">&lt;p&gt;Python Lambda functions are very powerful but I often forget how they
work or the fun things they do. This post will document how to use a
lambda to provide different default arguments to a function.&lt;/p&gt;
&lt;p&gt;We will use the &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/ago/overview"&gt;human function found in
ago.py&lt;/a&gt; as an
example - because …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python Lambda functions are very powerful but I often forget how they
work or the fun things they do. This post will document how to use a
lambda to provide different default arguments to a function.&lt;/p&gt;
&lt;p&gt;We will use the &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/ago/overview"&gt;human function found in
ago.py&lt;/a&gt; as an
example - because I'm the module author and I really like it. Lets use
the interactive python interpreter to run help on the human function.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; from ago import human
&amp;gt;&amp;gt;&amp;gt; help( human )

Help on function human in module ago:

human(dt, precision=2, past_tense='{} ago', future_tense='in {}')
    Accept a datetime or timedelta, return a human readable delta string
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Shown above the human function accepts 1 argument and 3 named keyword
arguments. The dt argument must be a datetime or timedelta object, the
precision must be an integer, and the other two must be strings. If we
didn't like the default arguments, we would need to specify (or pass in)
new values each time we invoked the function.&lt;/p&gt;
&lt;p&gt;Example: &lt;tt class="docutils literal"&gt;human(dt, 3, 'this happened {} &lt;span class="pre"&gt;ago!',&lt;/span&gt; 'in {} from &lt;span class="pre"&gt;now!')&lt;/span&gt;&lt;/tt&gt;.
If we know we will always want different default arguments we can create
a lambda function to shorten the invocation length.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; h = lambda dt : human(dt, 3, 'this happened {} ago!', 'in {} from now!')
&amp;gt;&amp;gt;&amp;gt; print h( dt ) # h is much shorter then human, and still reusable!
&lt;/pre&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Above creates a new function h who only accepts one argument dt. This
function calls human with our default arguments.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;This lambda is equivalent to this regular python function:&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; def h( dt ):
...    return human(d, 3, 'this happened {} ago!', 'in {} from now!')
&amp;gt;&amp;gt;&amp;gt; print h( dt ) # h is much shorter then human, and still reusable!
&lt;/pre&gt;
&lt;p&gt;Here is a working example to show the new lambda function h in action:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; from datetime import datetime
&amp;gt;&amp;gt;&amp;gt; from datetime import timedelta
&amp;gt;&amp;gt;&amp;gt; from ago import human
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; h = lambda dt : human(dt, 3, 'this happened {} ago!', 'in {} from now!')
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; present = datetime.now()
&amp;gt;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; for i in range( 1, 15 ):
...     if i % 2 == 0:
...         new_date = present - timedelta( i, i * i, i * i * i )
...     else:
...         new_date = present + timedelta( i, i * i, i * i * i )
...     h( new_date )
...
'in 22 hours, 9 minutes, 30 seconds from now!'
'this happened 2 days, 1 hour, 50 minutes ago!'
'in 2 days, 22 hours, 9 minutes from now!'
'this happened 4 days, 1 hour, 50 minutes ago!'
'in 4 days, 22 hours, 9 minutes from now!'
'this happened 6 days, 1 hour, 51 minutes ago!'
'in 6 days, 22 hours, 10 minutes from now!'
'this happened 8 days, 1 hour, 51 minutes ago!'
'in 8 days, 22 hours, 10 minutes from now!'
'this happened 10 days, 1 hour, 52 minutes ago!'
'in 10 days, 22 hours, 11 minutes from now!'
'this happened 12 days, 1 hour, 52 minutes ago!'
'in 12 days, 22 hours, 12 minutes from now!'
'this happened 14 days, 1 hour, 53 minutes ago!'
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Greatest Hits"></category><category term="Guide"></category></entry><entry><title>ago.py human readable timedelta 0.0.4 release</title><link href="https://russell.ballestrini.net/ago-py-human-readable-timedelta0-0-4-release/" rel="alternate"></link><published>2013-01-09T00:04:00-05:00</published><updated>2013-01-09T00:04:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2013-01-09:/ago-py-human-readable-timedelta0-0-4-release/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;We have released ago.py 0.0.4&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;pypi: &lt;a class="reference external" href="https://pypi.python.org/pypi/ago"&gt;https://pypi.python.org/pypi/ago&lt;/a&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;repo: &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/ago"&gt;https://bitbucket.org/russellballestrini/ago&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Special thanks to David Beitey for supplying &lt;a class="reference external" href="https://davidjb.com/"&gt;ideas and python
code&lt;/a&gt; for this update!&lt;/p&gt;
&lt;p&gt;All changes are backward compatible.&lt;/p&gt;
&lt;p&gt;Change log:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;added support for future dates&lt;/li&gt;
&lt;li&gt;added optional past_tense …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;We have released ago.py 0.0.4&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;pypi: &lt;a class="reference external" href="https://pypi.python.org/pypi/ago"&gt;https://pypi.python.org/pypi/ago&lt;/a&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;repo: &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/ago"&gt;https://bitbucket.org/russellballestrini/ago&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Special thanks to David Beitey for supplying &lt;a class="reference external" href="https://davidjb.com/"&gt;ideas and python
code&lt;/a&gt; for this update!&lt;/p&gt;
&lt;p&gt;All changes are backward compatible.&lt;/p&gt;
&lt;p&gt;Change log:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;added support for future dates&lt;/li&gt;
&lt;li&gt;added optional past_tense keyword argument string for custom output&lt;/li&gt;
&lt;li&gt;added optional future_tense keyword argument string for custom
output&lt;/li&gt;
&lt;li&gt;added 13 tests to help prevent regressions&lt;/li&gt;
&lt;li&gt;Updated the README.rst documentation for better coverage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We were just shy of 800 downloads on pypi for ago-0.0.3, I hope
ago-0.0.4 will perform even better!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category></entry><entry><title>Tips for getting pull requests approved</title><link href="https://russell.ballestrini.net/tips-for-getting-pull-requests-approved/" rel="alternate"></link><published>2012-12-12T12:50:00-05:00</published><updated>2012-12-12T12:50:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-12-12:/tips-for-getting-pull-requests-approved/</id><summary type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pull-rejection-sucks" id="toc-entry-1"&gt;Pull rejection sucks!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#start-small" id="toc-entry-2"&gt;Start small&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#don-t-add-leading-or-trailing-white-space" id="toc-entry-3"&gt;Don't add leading or trailing white space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#less-is-more" id="toc-entry-4"&gt;Less is more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#only-commit-working-code" id="toc-entry-5"&gt;Only commit working code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#follow-the-leader" id="toc-entry-6"&gt;Follow the leader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#comments-docs-and-tests-oh-my" id="toc-entry-7"&gt;Comments, docs, and tests oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#blog-about-the-change" id="toc-entry-8"&gt;Blog about the change&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pull-requests-are-like-paragraphs" id="toc-entry-9"&gt;Pull requests are like paragraphs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="pull-rejection-sucks"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Pull rejection sucks!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You have just coded, implemented, and submitted a pull …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pull-rejection-sucks" id="toc-entry-1"&gt;Pull rejection sucks!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#start-small" id="toc-entry-2"&gt;Start small&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#don-t-add-leading-or-trailing-white-space" id="toc-entry-3"&gt;Don't add leading or trailing white space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#less-is-more" id="toc-entry-4"&gt;Less is more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#only-commit-working-code" id="toc-entry-5"&gt;Only commit working code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#follow-the-leader" id="toc-entry-6"&gt;Follow the leader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#comments-docs-and-tests-oh-my" id="toc-entry-7"&gt;Comments, docs, and tests oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#blog-about-the-change" id="toc-entry-8"&gt;Blog about the change&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pull-requests-are-like-paragraphs" id="toc-entry-9"&gt;Pull requests are like paragraphs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="pull-rejection-sucks"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Pull rejection sucks!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You have just coded, implemented, and submitted a pull request. A short
while later the request is declined by an upstream maintainer and you
feel crushed. We have all been there. Today I'm going to show you a
better way. This article will teach you how to create pull requests that
get approved.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="start-small"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Start small&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You need to earn trust with the maintainers. Your first commit should be
a small change which they &lt;em&gt;cannot&lt;/em&gt; reject. Try to write a missing test
or re-factor duplicate code. Correct a comment's accuracy or rename a
variable to better reflect its purpose. Your first pull request should
not alter how the program works.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/start-small.xcf_.png"&gt;&lt;img alt="start-small.xcf" src="/uploads/2012/12/start-small.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="don-t-add-leading-or-trailing-white-space"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Don't add leading or trailing white space&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Additional whitespace will alter the diff output. This causes version
control systems to flag lines as changed which is irritating and
sometimes misleading.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/spot-the-diff.xcf_.png"&gt;&lt;img alt="spot-the-diff.xcf" src="/uploads/2012/12/spot-the-diff.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="less-is-more"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Less is more&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Minimize the number of changes to accomplish your goal. People are busy
and at times lazy. Reduce the work the maintainers must do to perform a
merge. Lowering the amount of lines to review should increase the chance
of approval.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/less-is-more.xcf_.png"&gt;&lt;img alt="less-is-more.xcf" src="/uploads/2012/12/less-is-more.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="only-commit-working-code"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Only commit working code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Do not break the program, only commit working code. If the project has
tests make sure they work before you commit.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/commit-working-code.xcf_.png"&gt;&lt;img alt="commit-working-code.xcf" src="/uploads/2012/12/commit-working-code.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="follow-the-leader"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Follow the leader&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try to mimic the maintainers. Follow the coding style and project layout
even if it seems wrong. This is not your playground... yet. Before you
commit, review the VCS logs to learn how verbose or terse your commit
messages should be. When in Rome do as the romans do. This silly game of
follow the leader reduces friction of an outsider committing to the
project.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/follow-the-leader.xcf_.png"&gt;&lt;img alt="follow-the-leader.xcf" src="/uploads/2012/12/follow-the-leader.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comments-docs-and-tests-oh-my"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Comments, docs, and tests oh my!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your first pull request should clarify or add to the existing
documentation. Fix the README, adjust a comment, or write a test. These
tasks might appear small but they serve to prove that you possess
comprehension of the source code. They also do not alter the program's
functionality.&lt;/p&gt;
&lt;p&gt;Having a few of these pull requests under-your-belt will
earn you trust which will eventuality translate to more responsibility in
the future. You will also differentiate yourself from the rest because
most people do not enjoy working on documentation.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/comments-docs-tests-oh-my.xcf_.png"&gt;&lt;img alt="comments-docs-tests-oh-my.xcf" src="/uploads/2012/12/comments-docs-tests-oh-my.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="blog-about-the-change"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Blog about the change&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Write about the change, give reasons and examples. Include a link to
your blog post in the pull request.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/ideas-blog-get-heard.xcf_.png"&gt;&lt;img alt="ideas-blog-get-heard.xcf" src="/uploads/2012/12/ideas-blog-get-heard.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pull-requests-are-like-paragraphs"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Pull requests are like paragraphs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you were writing an essay, you would split up your ideas into
separate paragraphs. A pull request has many qualities similar to a
paragraph. Each commit should be related to the pull request's main
objective. Commits of a pull request should stay focused and on topic.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;A pull request is to a program as a paragraph is to an essay.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;A commit is to a pull request as a sentence is to a paragraph.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;In an essay, if you have more then one topic you should have more then
one paragraph. Likewise when coding, only one idea or change per pull
request.&lt;/p&gt;
&lt;p&gt;Separating your ideas into different pull requests will grant the
maintainers greater flexibility when they begin to integrate. They will
have the ability to pick-and-choose which requests to merge and
everybody wins!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/12/pull-request-stay-focused.xcf_.png"&gt;&lt;img alt="pull-request-stay-focused.xcf" src="/uploads/2012/12/pull-request-stay-focused.xcf_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>how to drain an iPhone battery without needing passcode</title><link href="https://russell.ballestrini.net/how-to-drain-an-iphone-battery-without-needing-passcode/" rel="alternate"></link><published>2012-11-03T19:27:00-04:00</published><updated>2012-11-03T19:27:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-11-03:/how-to-drain-an-iphone-battery-without-needing-passcode/</id><content type="html">&lt;ol class="arabic simple"&gt;
&lt;li&gt;Press home button [ ]&lt;/li&gt;
&lt;li&gt;Slide camera button up&lt;/li&gt;
&lt;li&gt;Slide mode to video&lt;/li&gt;
&lt;li&gt;Turn on flash&lt;/li&gt;
&lt;li&gt;Put iPhone on table with light pointed down&lt;/li&gt;
&lt;li&gt;Walk away inconspicuously&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Extra points if you set the camera to record (just don't record yourself
or sounds) which will fill up the iPhone capacity.&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category><category term="Security"></category></entry><entry><title>Explaining cache with python</title><link href="https://russell.ballestrini.net/explaining-cache-with-python/" rel="alternate"></link><published>2012-10-02T15:22:00-04:00</published><updated>2012-10-02T15:22:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-10-02:/explaining-cache-with-python/</id><summary type="html">&lt;p&gt;&lt;strong&gt;What is cache?&lt;/strong&gt; I define cache as &amp;quot;a saved answer to a question&amp;quot;.
Caching can speed up an application if a computationally complex
question is asked frequently. Instead of the computing the answer over
and over, we can use the previously cached answer. This post will
present one method of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;What is cache?&lt;/strong&gt; I define cache as &amp;quot;a saved answer to a question&amp;quot;.
Caching can speed up an application if a computationally complex
question is asked frequently. Instead of the computing the answer over
and over, we can use the previously cached answer. This post will
present one method of adding cache to a python program. Specifically we
will write a program that computes prime numbers and saves the answers
into cache for quick retrieval.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; The kind people of the Internet have expressed concern with my
loose use of the term cache; the techniques that follow are most
accurately described as memoization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One algorithm for determining if a number is prime follows:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
prime_flag = True # default state
x = 5 # number to test
if x == 1:
    prime_flag = False
else:
    for i in range( 2, x ):
        if x % i == 0:
            prime_flag = False
            break
print prime_flag
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Create a prime_flag variable to hold the answer and default it to
true. Let x be the number being tested and if x is equal to 1, the x is
not prime. Otherwise iterate over each number in the range of 2 to x.
Let i be the current number to be tested. if x is divided by i without
any remainder, x is not prime. Set the prime_flag to False and break
out of the loop. Print the result.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next we will move the algorithm into a function which will allow for
code reuse:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def is_prime( x ):
    &amp;quot;&amp;quot;&amp;quot;Determine if a number is prime, return Boolean&amp;quot;&amp;quot;&amp;quot;
    prime_flag = True
    if x == 1:
        prime_flag = False
    else:
        for i in range( 2, x ):
            if x % i == 0:
                prime_flag = False
                break
    return prime_flag

# invoke function:
print is_prime( 5 ) # True
print is_prime( 4 ) # False
&lt;/pre&gt;
&lt;p&gt;This function saves us a lot of typing and enables the ability to
quickly determine if a given number is prime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next we will use a python dictionary to implement a result cache.&lt;/strong&gt;
Also by circumstance we introduce objects and classes.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
class Primer( object ):
    def __init__( self ):
        &amp;quot;&amp;quot;&amp;quot;create a cache dictionary&amp;quot;&amp;quot;&amp;quot;
        self.cache = {}

    def is_prime( self, x ):
        &amp;quot;&amp;quot;&amp;quot;Determine if x is prime, cache and return result&amp;quot;&amp;quot;&amp;quot;
        if x in self.cache:
            return self.cache[x] # lookup result

        prime_flag = True

        if x == 1:
            prime_flag = False
        else:
            for i in range( 2, x ):
                if x % i == 0:
                    prime_flag = False
                    break

        self.cache[x] = prime_flag # cache result
        return prime_flag

p = Primer() # create a new primer object
p.is_prime( 5 ) # True
p.is_prime( 4 ) # False
p.is_prime( 5 ) # True and fetched from cache
&lt;/pre&gt;
&lt;p&gt;What is great about this solution is that we can avoid looping and
computation if an answer is already in cache. Looking up a cached result
is much more efficient and will ultimately make a program feel more
responsive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Determining if 97352613 is prime takes my laptop nearly 18 seconds.&lt;/strong&gt;
Fetching the cached result seems to happen instantly.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;gt;&amp;gt;&amp;gt; s1 = time();p.is_prime( 97352613 );e1 = time()
False # not prime
&amp;gt;&amp;gt;&amp;gt; s2 = time();p.is_prime( 97352613 );e2 = time()
False # not prime from cache
&amp;gt;&amp;gt;&amp;gt; e1 - s1
17.970067977905273 # seconds
&amp;gt;&amp;gt;&amp;gt; e2 - s2
2.5987625122070312e-05 # or approx .000026 seconds
&lt;/pre&gt;
&lt;p&gt;A look-up will always beat a computation. Anything that can be cached,
should be cached. I hope this helps clear things up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="2013-03-03-explaining-cache-scaled" src="/uploads/2013/03/2013-03-03-explaining-cache-scaled.png" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Greatest Hits"></category></entry><entry><title>The Pyramid community taught me the importance of test driven development</title><link href="https://russell.ballestrini.net/the-pyramid-community-taught-me-the-importance-of-test-driven-development/" rel="alternate"></link><published>2012-09-22T14:03:00-04:00</published><updated>2012-09-22T14:03:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-09-22:/the-pyramid-community-taught-me-the-importance-of-test-driven-development/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/Pylons/pyramid/commit/72561a213ccc456738582551e85fab0f0c8d09ab"&gt;Sontek's patch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I greeted the UPS man in the middle of the street to sign for my new
Lenovo ThinkPad T430. Because this was My first &lt;em&gt;brand-new&lt;/em&gt; laptop
purchase I rationalized the time I spent tracking the package from the
factory in China to my hands in Connecticut. Once inside …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/Pylons/pyramid/commit/72561a213ccc456738582551e85fab0f0c8d09ab"&gt;Sontek's patch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I greeted the UPS man in the middle of the street to sign for my new
Lenovo ThinkPad T430. Because this was My first &lt;em&gt;brand-new&lt;/em&gt; laptop
purchase I rationalized the time I spent tracking the package from the
factory in China to my hands in Connecticut. Once inside, I opened the
box and started installing Fedora 17. I couldn't help but to take in the
new-electronics smell.&lt;/p&gt;
&lt;p&gt;I've been without a laptop for more than a month so I was eager to get
my development environment configured. Most of my tools ship with
vanilla Linux, vim, python, hg mercurial, chromium browser, etc. My
first goal was to get a development copy of linkpeek.com running
locally. This took about 5 minutes and it seemed to be working fine
until I noticed a few pages had errors. The errors seemed to be caused
by a difference between Pyramid 1.3 and 1.4a1. But what was failing?&lt;/p&gt;
&lt;p&gt;I posted a short message in #pyramid about the bug and minutes later I
had multiple developers prodding for hints. &amp;quot;Could you post the whole
traceback?&amp;quot;, &amp;quot;What does your view look like?&amp;quot;. I answered quickly and
attempted to explain what I thought was going on. Turns out I was close
but before I could finish explaining the problem, sontek had a working
one-character-fix and was in the process finishing the tests to prove
the patch. He also explained what I should do in the interim to patch
locally.&lt;/p&gt;
&lt;p&gt;In the next 4 hours a pull request was submitted to the upstream master
and the patch was peer reviewed, accepted, and integrate by mcdonc. That
impressed me, a lot. All Pylon Projects have strict policies about test
coverage and now I understand why. Tests not only help produce better
bug-free software but also act as a powerful tool when proving the
validity of a patch. I plan to devote the next couple weeks to making
test-driven-development a habit.&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>miniuri parser and ago human timedelta</title><link href="https://russell.ballestrini.net/miniuri-parser-and-ago-human-timedelta/" rel="alternate"></link><published>2012-06-29T21:30:00-04:00</published><updated>2012-06-29T21:30:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-06-29:/miniuri-parser-and-ago-human-timedelta/</id><content type="html">&lt;p&gt;I just packaged and published a couple of python modules to pypi:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://pypi.python.org/pypi/ago"&gt;ago&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://pypi.python.org/pypi/miniuri"&gt;miniuri&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To install them, run:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Install:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--upgrade&lt;span class="w"&gt; &lt;/span&gt;ago&lt;span class="w"&gt; &lt;/span&gt;miniuri
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you want to view their source code, look here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/miniuri"&gt;miniuri&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/ago"&gt;ago&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope you enjoy them.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Project"></category></entry><entry><title>My top five suggestions for an independent developer creating a new product or service</title><link href="https://russell.ballestrini.net/my-top-five-suggestions-for-an-independent-developer-creating-a-new-product-or-service/" rel="alternate"></link><published>2012-06-25T19:50:00-04:00</published><updated>2012-06-25T19:50:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-06-25:/my-top-five-suggestions-for-an-independent-developer-creating-a-new-product-or-service/</id><summary type="html">&lt;p&gt;&lt;strong&gt;1. Write everyday.&lt;/strong&gt; Build a blog for the project and write about
milestones, progress, and hurdles. Also keep a personal blog and write
about hobbies. Read some theory about &amp;quot;copy writing&amp;quot; and search engine
optimization. Write personalized email responses to customers. Great
communication skills will have the most impact on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;1. Write everyday.&lt;/strong&gt; Build a blog for the project and write about
milestones, progress, and hurdles. Also keep a personal blog and write
about hobbies. Read some theory about &amp;quot;copy writing&amp;quot; and search engine
optimization. Write personalized email responses to customers. Great
communication skills will have the most impact on the success of your
company. The best way to increase communication skills is to do it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Spend at least 15 minutes on the company everyday.&lt;/strong&gt; Even small or
petty tasks will lift your company to the next goal. Over time the
company will grow strong and people will give you attention. Building a
company seems similar to building a character in an RPG. Instead however
you will progress your company to its next level. Also, always look
ahead when working, don't look back at prior achievements. Keep your
eyes on the next milestone and keep moving forward. This type of
behavior will promote growth and prevent getting stuck in the daily
grind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Do not fear manual processes.&lt;/strong&gt; In the early stages of a company
you should only build automation which will enhance customer experience.
For everything else, build a repeatable manual process.
Coding automation will typically cost more time to build than it will save.
For example on &lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek&lt;/a&gt; I have not automated customer account de-activation because fortunately I don't have to do many of them.
This manual process only takes a couple minutes to complete and gives me a chance to write a &amp;quot;thank you, and sorry to see you go&amp;quot; email to the fleeting customer.
Manually de-activating (loosing) a customer is also a humbling experience.
Keep a list of nice-to-have automations and review them in the far future.
How far in the future? Far, like when your idea is validated and your company appears successful.
Don't waste valuable time building automation for a service nobody will pay for or use.
Time is your most scarce resource.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Always attempt to increase your luck.&lt;/strong&gt; This might sound funny but
the the following suggestions should reduce risk and also increase luck:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Meet fellow lucky people&lt;/li&gt;
&lt;li&gt;Write on a blog&lt;/li&gt;
&lt;li&gt;Advertise your company on side-projects that lack monetization&lt;/li&gt;
&lt;li&gt;Think before wasting money using adsense or similar networks&lt;/li&gt;
&lt;li&gt;Do market research before committing to an idea&lt;/li&gt;
&lt;li&gt;Figure out how to make money before building product&lt;/li&gt;
&lt;li&gt;Build the smallest version of the product that could still be sold&lt;/li&gt;
&lt;li&gt;Try to stay focused on the things your customers will see&lt;/li&gt;
&lt;li&gt;Don't build an admin dashboard until successful (profitable?)&lt;/li&gt;
&lt;li&gt;Choose tasks that require the least effort but have the biggest
impact&lt;/li&gt;
&lt;li&gt;Don't be afraid to do things in the early stages that won't scale in
the long run (example: personal email responses)&lt;/li&gt;
&lt;li&gt;Listen to how early adopters describe your product; then on your
website, marketing and emails reuse their words.&lt;/li&gt;
&lt;li&gt;Attempt to keep marketing, newsletter, and support emails only few
sentences long.&lt;/li&gt;
&lt;li&gt;Start maintaining an opt-in email list&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might have the best mouse-trap but without luck it will never gain
traction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Have an unhealthy passion to support your customers and build your
company.&lt;/strong&gt; Six days ago I suffered a major chemical burn to my right
eye. With my wife's help I continued to respond to support emails. Since
I launched &lt;a class="reference external" href="https://linkpeek.com"&gt;LinkPeek&lt;/a&gt; my
focus and attention to my customers and company have never faltered.
Dedication plays a key role to success.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2012/06/dedication-eye-chemical-burn.jpg"&gt;&lt;img alt="image0" src="/uploads/2012/06/dedication-eye-chemical-burn.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>Prevent a certain program from running too long in bash</title><link href="https://russell.ballestrini.net/prevent-a-certain-program-from-running-to-long-in-bash/" rel="alternate"></link><published>2012-06-13T09:45:00-04:00</published><updated>2012-06-13T09:45:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-06-13:/prevent-a-certain-program-from-running-to-long-in-bash/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; - I opensourced this script here: &lt;a class="reference external" href="https://github.com/russellballestrini/bash-kira"&gt;bash kira&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I came up this this script to kill certain programs after they run for
too long. This works like similar to a timeout. Warning this script is
pretty harsh and kills the program.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/bash
PROGRAM=replace-with-program-name
PIDSFILE=/tmp/kill-these.pids

for …&lt;/pre&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; - I opensourced this script here: &lt;a class="reference external" href="https://github.com/russellballestrini/bash-kira"&gt;bash kira&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I came up this this script to kill certain programs after they run for
too long. This works like similar to a timeout. Warning this script is
pretty harsh and kills the program.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/bin/bash
PROGRAM=replace-with-program-name
PIDSFILE=/tmp/kill-these.pids

for pid in `pidof $PROGRAM`
  do
    if grep -q $pid $PIDSFILE
      then
        kill $pid
    fi
  done

&amp;gt; $PIDSFILE

for pid in `pidof $PROGRAM`
  do
    echo $pid &amp;gt;&amp;gt; $PIDSFILE
  done
&lt;/pre&gt;
&lt;p&gt;Then I wrote a cronjob to kill hung programs:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
* * * * * /usr/local/sbin/killprogs.sh
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Always attempt to scale vertically first</title><link href="https://russell.ballestrini.net/always-attempt-to-scale-vertically-first/" rel="alternate"></link><published>2012-06-10T21:52:00-04:00</published><updated>2012-06-10T21:52:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-06-10:/always-attempt-to-scale-vertically-first/</id><summary type="html">&lt;p&gt;I spent the weekend fretting because one of my servers was basically
being DOS'd by paying customers. During the outage I started thinking
about the best way to scale and how I could make the code-base more
efficient.&lt;/p&gt;
&lt;p&gt;Linux top reported high load, in the 20's. Eventually I figured out …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I spent the weekend fretting because one of my servers was basically
being DOS'd by paying customers. During the outage I started thinking
about the best way to scale and how I could make the code-base more
efficient.&lt;/p&gt;
&lt;p&gt;Linux top reported high load, in the 20's. Eventually I figured out that
the server was having IO performance issues.&lt;/p&gt;
&lt;p&gt;I wasted a bunch of time attempting to fight fires. After about an hour
of that I decided to scale my VPS vertically by giving it an extra 256mb
of memory and a larger swap file (256mb to 1024mb).&lt;/p&gt;
&lt;p&gt;These two changes were surprisingly effective and the IO issues
resolved. Apparently the server was starving for memory which caused the
host to swap which brought things to a crawl waiting for IO.&lt;/p&gt;
&lt;p&gt;Crisis averted for the moment. Now I am free to think clearly and
engineer a proper solution instead of attempting to put out fires.&lt;/p&gt;
&lt;p&gt;If you ever encounter a similar situation, attempt the simplest fix.
There is no shame in throwing more money at a problem if it will buy you
time. In this case, an extra $10.00 a month relieved the performance
issues and bought myself some time, for the moment.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/always-attempt-to-scale-vertically-first/vertical-scale-marked/"&gt;&lt;img alt="image0" src="/uploads/2012/06/vertical-scale-marked.png" /&gt;&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="LinkPeek"></category></entry><entry><title>High load on web server after updating from Ubuntu 10.04 to Ubuntu 12.04 LTS</title><link href="https://russell.ballestrini.net/high-load-on-web-server-after-updating-from-ubuntu-10-04-to-ubuntu-12-04-lts/" rel="alternate"></link><published>2012-05-19T18:40:00-04:00</published><updated>2012-05-19T18:40:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-05-19:/high-load-on-web-server-after-updating-from-ubuntu-10-04-to-ubuntu-12-04-lts/</id><summary type="html">&lt;p class="first last"&gt;Charts that show the load difference.&lt;/p&gt;
</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;High load on web server after updating from Ubuntu 10.04 to Ubuntu
12.04 LTS&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line"&gt;Check out charts which lineup to when I upgraded:&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/high-load-on-web-server-after-updating-from-ubuntu-10-04-to-ubuntu-12-04-lts/high-load-after-updating-ubuntu-from-10-04-lts-to-12-04-lts/"&gt;&lt;img alt="image0" src="/uploads/2012/05/high-load-after-updating-ubuntu-from-10.04-LTS-to-12.04-LTS.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;I couldn't determine the cause of the load average increase...&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; The issue might be memory bound. Check out this graph that
show much higher swap.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/high-load-on-web-server-after-updating-from-ubuntu-10-04-to-ubuntu-12-04-lts/ubuntu-12-04-swap-year/"&gt;&lt;img alt="image1" src="/uploads/2012/05/ubuntu.12.04.swap_.year_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After much research this appears to be a load calculation and display
problem with the newer Linux kernels. The community has found Commit-ID:
c308b56b5398779cd3da0f62ab26b0453494c3d4 to be the problem. The commit
causes incorrect high reported load averages can be reported under
conditions of light load and high enter/exit idle frequency conditions
(greater then 25 hertz).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A nice fellow named Doug from smythies.com researched
the topic between &lt;cite&gt;tick and tickless linux kernels and the effect they
have on load averages &amp;lt;http://www.smythies.com/~doug/network/load_average/new.html&amp;gt;&lt;/cite&gt;. You should check it out.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category></entry><entry><title>How to rescue logs and config from a failed Citrix NetScaler App Gateway</title><link href="https://russell.ballestrini.net/how-to-rescue-logs-and-config-from-a-failed-citrix-netscaler-app-gateway/" rel="alternate"></link><published>2012-05-11T00:05:00-04:00</published><updated>2012-05-11T00:05:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-05-11:/how-to-rescue-logs-and-config-from-a-failed-citrix-netscaler-app-gateway/</id><summary type="html">&lt;p&gt;Today our production Citrix NetScaler broke. The box wouldn't boot and
our only backup copy of the config was on the NetScaler itself.&lt;/p&gt;
&lt;p&gt;Being the only Unix guy around I attempted to help out the admins
working the outage. I SSH'd into the development NetScaler and noticed
it runs on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today our production Citrix NetScaler broke. The box wouldn't boot and
our only backup copy of the config was on the NetScaler itself.&lt;/p&gt;
&lt;p&gt;Being the only Unix guy around I attempted to help out the admins
working the outage. I SSH'd into the development NetScaler and noticed
it runs on FreeBSD.&lt;/p&gt;
&lt;p&gt;I suggested fetching the Hard drive and mounting it on a Linux computer.
The NetScaler has one SATA (not SAS) disk so my desktop was compatible.&lt;/p&gt;
&lt;p&gt;I installed the disk in the Linux tower and mounted the filesystem using
the following command:&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;mount &lt;span class="pre"&gt;--read-only&lt;/span&gt; &lt;span class="pre"&gt;--type=ufs&lt;/span&gt;&amp;nbsp; &lt;span class="pre"&gt;--test-opts&lt;/span&gt; ufstype=44bsd /dev/sda5 /mnt&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Once mounted I was able to SCP interesting files to a safe location.&lt;/p&gt;
&lt;p&gt;Warning, this procedure might void your warranty. If in doubt, call
support first.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>The most valuable registration field: How did you hear about us?</title><link href="https://russell.ballestrini.net/the-most-valuable-registration-field-how-did-you-hear-about-us/" rel="alternate"></link><published>2012-04-26T22:21:00-04:00</published><updated>2012-04-26T22:21:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-04-26:/the-most-valuable-registration-field-how-did-you-hear-about-us/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;How did you hear about us?&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I first answered this question when joining Linode.
I remember thinking &amp;quot;Wow, this is a great time to ask me!&amp;quot; because the real answer was still in my short term memory.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When I launched &lt;a class="reference external" href="https://linkpeek.com/signup?plan=better"&gt;LinkPeek&lt;/a&gt; I applied this technique.
After an amazing launch (thank …&lt;/p&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;How did you hear about us?&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I first answered this question when joining Linode.
I remember thinking &amp;quot;Wow, this is a great time to ask me!&amp;quot; because the real answer was still in my short term memory.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When I launched &lt;a class="reference external" href="https://linkpeek.com/signup?plan=better"&gt;LinkPeek&lt;/a&gt; I applied this technique.
After an amazing launch (thank you colleagues from HackerNews) the true significance of this field was exposed.
I would argue that the answer to this simple question holds more value than collecting a username.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Here is why:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It may seem obvious after reading this post, but this is what I learned
and I am sharing to help other startups.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Asking &amp;quot;How did you hear about us?&amp;quot; will help you determine:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;how your customers found you&lt;/li&gt;
&lt;li&gt;which of your marketing efforts are working&lt;/li&gt;
&lt;li&gt;where to spend money or time marketing (and where to stop)&lt;/li&gt;
&lt;li&gt;your true target market&lt;/li&gt;
&lt;li&gt;how your customers will use your product&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some other benefits to this technique. It opens a dialog or
conversation with the customer, which in return should help lower
friction during the sale. You already have the the customer engaged
during the signup process. I am firmly against the traditional method of
surveying customers. I believe the same data can be collected without
being abrasive or wasting the customers time.&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;How did you hear about us?&amp;quot; is the gift that keeps on giving.&lt;/blockquote&gt;
&lt;p&gt;It is commonly accepted that shorter registration forms lead to better
conversions. I totally agree, but in this case the sacrifice is worth
learning more about my target market and customer needs.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;How to ask this question properly:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Just ask!&lt;/strong&gt; I second guessed my decision to add this field right
before launch but that anxiety promptly faded after reading the first
answer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't use pre-filled answers!&lt;/strong&gt; Having pre-filled answers is
counter productive because you will not learn anything new. You are
forcing the user into choosing one of your answers...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allow the user to type as much as they like!&lt;/strong&gt; A few of my
customers nearly wrote a book in the text field.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;If you liked this post, you should follow me on twitter
`here &amp;lt;https://twitter.com/RussellBal&amp;gt;`__.&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>I just purchased Instagram for 1B and all I got was this lousy image filter</title><link href="https://russell.ballestrini.net/i-just-purchased-instagram-for-1b-and-all-i-got-was-this-lousy-image-filter/" rel="alternate"></link><published>2012-04-09T17:47:00-04:00</published><updated>2012-04-09T17:47:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-04-09:/i-just-purchased-instagram-for-1b-and-all-i-got-was-this-lousy-image-filter/</id><summary type="html">&lt;div class="section" id="warning-update"&gt;
&lt;h2&gt;Warning / Update!&lt;/h2&gt;
&lt;p&gt;This post was originally written from a place of jealousy and bitterness.&lt;/p&gt;
&lt;p&gt;Turns out I was wrong about this transaction and for better-or-worse, Facebook
(and Mark Zuckerburg) solidified their edge as the king of social for the last 6 years.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="original-post"&gt;
&lt;h2&gt;Original Post&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Facebook purchased Instagram on Monday, April …&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="warning-update"&gt;
&lt;h2&gt;Warning / Update!&lt;/h2&gt;
&lt;p&gt;This post was originally written from a place of jealousy and bitterness.&lt;/p&gt;
&lt;p&gt;Turns out I was wrong about this transaction and for better-or-worse, Facebook
(and Mark Zuckerburg) solidified their edge as the king of social for the last 6 years.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="original-post"&gt;
&lt;h2&gt;Original Post&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Facebook purchased Instagram on Monday, April 9th 2012 for
$1,000,000,000 US.&lt;/strong&gt; Instagram, a free to use image sharing iPhone
application, has absolutely NO REVENUE STREAM. The application is free
to download, free to use, and has no monetization support from
advertisements.&lt;/p&gt;
&lt;blockquote&gt;
$1000000000.00 / 30000000 users = &lt;strong&gt;33.33 per user&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;At the time of the sale Instagram had 30 million users in total
(30,000,000). That means Facebook just paid heavy &lt;strong&gt;$33.33&lt;/strong&gt; per user...
This is obviously a bad deal for Facebook.&lt;/p&gt;
&lt;p&gt;Instagram did not have a method to monetize its application. I hedge my
bets that Facebook will never recoup the one billion dollars spent on
this deal.&lt;/p&gt;
&lt;p&gt;Mark Zuckerburg, I just thought up the perfect T-Shirt for you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;I just purchased Instagram for 1B and all I got was this lousy
image filter.&lt;/strong&gt;&lt;/blockquote&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Trouble mounting filesystem on KVM guest after reboot</title><link href="https://russell.ballestrini.net/trouble-mounting-filesystem-on-kvm-guest-after-reboot/" rel="alternate"></link><published>2012-03-27T12:04:00-04:00</published><updated>2012-03-27T12:04:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-03-27:/trouble-mounting-filesystem-on-kvm-guest-after-reboot/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Just found this out the hard way...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It looks like the attachment of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/KVMROOT/guest-dev-app.img&lt;/span&gt;&lt;/tt&gt; on
guest-dev did not persist when the KVM host rebooted for patching.&lt;/p&gt;
&lt;p&gt;As it appears the &lt;tt class="docutils literal"&gt;virsh &lt;span class="pre"&gt;attach-disk&lt;/span&gt;&lt;/tt&gt; command works a lot like the
&lt;tt class="docutils literal"&gt;mount&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;In order to have a disk attachment persist …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Just found this out the hard way...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It looks like the attachment of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/KVMROOT/guest-dev-app.img&lt;/span&gt;&lt;/tt&gt; on
guest-dev did not persist when the KVM host rebooted for patching.&lt;/p&gt;
&lt;p&gt;As it appears the &lt;tt class="docutils literal"&gt;virsh &lt;span class="pre"&gt;attach-disk&lt;/span&gt;&lt;/tt&gt; command works a lot like the
&lt;tt class="docutils literal"&gt;mount&lt;/tt&gt; command.&lt;/p&gt;
&lt;p&gt;In order to have a disk attachment persist after a reboot, I think we still need to do a &lt;tt class="docutils literal"&gt;virsh edit&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;the &lt;tt class="docutils literal"&gt;virsh &lt;span class="pre"&gt;attach-disk&lt;/span&gt;&lt;/tt&gt; command is useful because it allows us to
attach disk images to guests without restarting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;virsh &lt;span class="pre"&gt;attach-disk&lt;/span&gt;&lt;/tt&gt; is to &lt;tt class="docutils literal"&gt;mount&lt;/tt&gt; as&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;virsh edit&lt;/tt&gt; is to &lt;tt class="docutils literal"&gt;vim /etc/fstab&lt;/tt&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="DevOps"></category></entry><entry><title>nosslsearch cname is a bad idea and solution</title><link href="https://russell.ballestrini.net/nosslsearch-cname-is-a-bad-idea-and-solution/" rel="alternate"></link><published>2012-03-26T11:51:00-04:00</published><updated>2012-03-26T11:51:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-03-26:/nosslsearch-cname-is-a-bad-idea-and-solution/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Google SafeSearch and SSL Search for Schools suggests implementing
the following changes to the network:&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
To utilize the no SSL option for your network, configure the DNS
entry for www.google.com to be a CNAME for nosslsearch.google.com.&lt;/blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Here are the reasons why this is a bad idea …&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Google SafeSearch and SSL Search for Schools suggests implementing
the following changes to the network:&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
To utilize the no SSL option for your network, configure the DNS
entry for www.google.com to be a CNAME for nosslsearch.google.com.&lt;/blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Here are the reasons why this is a bad idea and solution:&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;In order to create a CNAME record for www.google.com we need to
become an authoritative master of that zone.&lt;/li&gt;
&lt;li&gt;If you become an authoritative master you need to host all of
Google's DNS resource records for the domain.&lt;/li&gt;
&lt;li&gt;Google is asking us to DNS poison it's flag ship product on our
networks.&lt;/li&gt;
&lt;li&gt;If other companies follow suit the internet will quickly become
unmanageable. DNS was not ment to work this way.&lt;/li&gt;
&lt;li&gt;Not all networks have a local DNS server&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;This is a bad idea. Please change your stance on this matter.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Reference:
&lt;a class="reference external" href="https://support.google.com/websearch/bin/answer.py?hl=en&amp;amp;answer=186669"&gt;https://support.google.com/websearch/bin/answer.py?hl=en&amp;amp;answer=186669&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Opinion"></category><category term="Security"></category></entry><entry><title>"I see" said the blind man, to the deaf dog, as he walked off the cliff.</title><link href="https://russell.ballestrini.net/i-see-said-the-blind-man-to-the-deaf-dog-as-he-walked-off-the-cliff/" rel="alternate"></link><published>2012-03-23T22:04:00-04:00</published><updated>2012-03-23T22:04:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-03-23:/i-see-said-the-blind-man-to-the-deaf-dog-as-he-walked-off-the-cliff/</id><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;I see&amp;quot; said the blind man, to the deaf dog, as he walked off the
cliff.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As far as I can tell, I am the originator of this version of this quote.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EDIT:&lt;/strong&gt; changed &amp;quot;originator of the quote&amp;quot; to &amp;quot;originator of this
version of this quote&amp;quot;.&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>What do you name your python virtualenv?</title><link href="https://russell.ballestrini.net/what-do-you-name-your-python-virtualenv/" rel="alternate"></link><published>2012-03-07T21:42:00-05:00</published><updated>2012-03-07T21:42:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-03-07:/what-do-you-name-your-python-virtualenv/</id><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;What do you name your python virtualenv?&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I name my virtualenv 'virtpy'. Is there a standard name being used out
there?&lt;/p&gt;
&lt;p&gt;Maybe we can come to a consensus as a standard name? Please feel free to
post your virtualenv names here as a sort of poll.&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>How to save hundreds of dollars on groceries without clipping coupons</title><link href="https://russell.ballestrini.net/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/" rel="alternate"></link><published>2012-03-03T19:54:00-05:00</published><updated>2012-03-03T19:54:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-03-03:/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/</id><summary type="html">&lt;p class="first last"&gt;Jenn explains how she drastically decreased our grocery bill.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;This is the first time I've had a guest blogger on my site. It may
sound campy but my wife Jenn wrote this article after explaining how our
grocery bill decreased so drastically.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/3247325203_6108897833_o/"&gt;&lt;img alt="image0" src="/uploads/2012/03/3247325203_6108897833_o.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to save hundreds of dollars on groceries without clipping
coupons&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was recently re-working the cabinets of the kitchen, anticipating a
new and organized year. With a growing family and limited space, I
decided to do some consolidating.&lt;/p&gt;
&lt;p&gt;I allocated a whole section of cabinet space to arts and craft supplies
for the kids (play-doh, paints, etc), and another to bottles and
formula. I suddenly realized that my food products remained scattered
across the counter and there was only one lonely cabinet left to fill.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/6876071849_b82bca1076_o/"&gt;&lt;img alt="image1" src="/uploads/2012/03/6876071849_b82bca1076_o.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Among the food items on the counter, many were far expired, forgotten in
the dusty back corners of the cabinets. I thought to myself “Hey! I
worked hard to find coupons and sales for all these things.” and I
regretfully filled a garbage bag with old food.&lt;/p&gt;
&lt;p&gt;I had been clipping coupons and striving to match the savings of super
couponers. These couponers, featured on popular reality shows, often
recommend stock-piling when food items go on sale and you have coupons
to purchase them.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/6876069657_5a2f4ae487_o/"&gt;&lt;img alt="image2" src="/uploads/2012/03/6876069657_5a2f4ae487_o.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I opened the refrigerator to find a similar and familiar situation: a
head of lettuce that had seen better days, a tomato that had lost it's
freshness, and other food items that were past the date of recommended
consumption.&lt;/p&gt;
&lt;p&gt;Looking into the garbage bag, I realized that... every week I was
wastefully throwing food and money away. I decided to make a commitment
that has cut my grocery bill in half! Do I still use coupons? YES, if
they fit into my weekly plan. Do I still look at the sale flyers?
ABSOLUTELY! But...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;These two strategies have saved me much more money:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;1. Cut down food storage spaces&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;If you currently use 4 food cabinets try designating only 2. In my
case I consolidated my food items to 1 cabinet. This consolidation
saved money in two distinct ways:&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;em&gt;First,&lt;/em&gt; I am easily able to assess which food items I have and when
they expire. This helps prevent re-buying a product we already have
at home (Remember the time you were at the grocery store buying all
the ingredients for those chocolate chip cookies and couldn't
remember if you had any brown sugar? Undoubtebly you re-bought,
probably to discover you had a brand new, unopened package in the
cabinet).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Secondly,&lt;/em&gt; it prevents stock piling. I can buy only what I need for
the week ahead, after all, it is all I have room for. If you generate
the sense that “my kitchen is full of food”, it eliminates the need
to buy for the sake of filling the cabinets.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;2. Let your menu dictate your shopping list&lt;/strong&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;My son complains “There are no more crackers.&amp;quot; My husband sighs,
“We're all out of bananas.&amp;quot; My answer used to be probably a lot like
yours “OK, I'll put it on my list.&amp;quot; I had the perception that I
constantly needed to replenish. It is unnecessary and wasteful to have
three different brands of crackers, five types of cereal, and every
kind of fruit known to the produce section. Without fail, I was
throwing away money each week.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Instead, I sit down each week with two sheets of paper. One serves as
dinner menu for the week and the other as my shopping list. When
building my menu, I consider the sales and coupons I have on hand. If I
notice a great deal on pasta sauce, we might have spaghetti one night
and meatball subs another. I immediately begin a shopping list writing
ONLY items used for the companion menu. Additionally, I add two
breakfast choices and two lunch choices for the week if needed (some
breakfast items such as pancake batter or a family size box of cereal
often last longer). I also include 2 choices of fresh fruit or
vegetable. To reduce waste and cost I ONLY add the staples (eggs, milk,
and bread) when it is on the menu. If none of my breakfast, lunch, or
dinner choices call for bread, I skip that aisle for this trip.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It took me only a few short weeks to notice that by cutting my food
storage space in half, and adopting a disciplined approach to menu and
list making, I had saved hundreds of dollars. I challenge you to cut
down your food storage spaces and let your menu dictate your shopping
list!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/how-to-save-hundreds-of-dollars-on-groceries-without-clipping-coupons/6876068965_db8a5d12b2_o/"&gt;&lt;img alt="image3" src="/uploads/2012/03/6876068965_db8a5d12b2_o.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>How to capture HTTPS SSL TLS packets with wireshark</title><link href="https://russell.ballestrini.net/how-to-capture-https-ssl-tls-packets-with-wireshark/" rel="alternate"></link><published>2012-02-29T19:14:00-05:00</published><updated>2012-02-29T19:14:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-29:/how-to-capture-https-ssl-tls-packets-with-wireshark/</id><summary type="html">&lt;p&gt;This article will explain how to use wireshark to capture TCP/IP
packets. Specifically I will show how to capture encrypted (HTTPS)
packets and attempt to document the &amp;quot;dance&amp;quot; a client and server do to
build an SSL tunnel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is Wireshark?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wireshark is a network protocol analyzer for Windows …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This article will explain how to use wireshark to capture TCP/IP
packets. Specifically I will show how to capture encrypted (HTTPS)
packets and attempt to document the &amp;quot;dance&amp;quot; a client and server do to
build an SSL tunnel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is Wireshark?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wireshark is a network protocol analyzer for Windows, OSX, and Linux. It
lets you capture and interactively browse the traffic running on a
computer network. Similar software includes tcpdump on Linux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install Wireshark&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First step, acquire Wireshark for your operating system.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ubuntu Linux:&lt;/em&gt; &lt;tt class="docutils literal"&gt;sudo &lt;span class="pre"&gt;apt-get&lt;/span&gt; install wireshark&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Windows or Mac OSX:&lt;/em&gt; search for wireshark and download the binary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to capture packets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is Wireshark's main menu:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image0" src="/uploads/2012/02/wireshark.png" /&gt;&lt;/p&gt;
&lt;p&gt;To start a capture, click the following icon:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" src="/uploads/2012/02/wireshark-start-capture.png" /&gt;&lt;/p&gt;
&lt;p&gt;A new dialog box should have appeared. Click start on your preferred
interface:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2" src="/uploads/2012/02/wireshark-sniff.png" /&gt;&lt;/p&gt;
&lt;p&gt;You are now capturing packets. The packet information is displayed in
the table below the main menu:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image3" src="/uploads/2012/02/wireshark-packets.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now browse to an HTTPS website with your browser. I went to
&lt;a class="reference external" href="https://linkpeek.com"&gt;https://linkpeek.com&lt;/a&gt; and after the page completely loaded, I stopped the
Wireshark capture:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image4" src="/uploads/2012/02/wireshark-stop-capture.png" /&gt;&lt;/p&gt;
&lt;p&gt;Depending on your network, you could have just captured MANY packets. To
limit our view to only interesting packets you may apply a filter.
Filter the captured packets by ssl and hit Apply:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image5" src="/uploads/2012/02/wireshark-filter.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now we should be only looking at SSL packets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next we will analyze the SSL packets and answer a few questions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; For each of the first 8 Ethernet frames, specify the source of
the frame (client or server), determine the number of SSL records that
are included in the frame, and list the SSL record types that are
included in the frame. Draw a timing diagram between client and server,
with one arrow for each SSL record.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Frame 1 client | 1 record | Arrival Time: Feb 15, 2012
15:38:55.601588000&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Frame 2 server | 1 record | Arrival Time: Feb 15, 2012
15:38:55.688170000&lt;/div&gt;
&lt;div class="line"&gt;Frame 3 server | 2 record | Arrival Time: Feb 15, 2012
15:38:55.688628000&lt;/div&gt;
&lt;div class="line"&gt;Frame 4 client | 3 record | Arrival Time: Feb 15, 2012
15:38:55.697705000&lt;/div&gt;
&lt;div class="line"&gt;frame 5 server | 2 record | Arrival Time: Feb 15, 2012
15:38:55.713139000&lt;/div&gt;
&lt;div class="line"&gt;frame 6 client | 1 record | Arrival Time: Feb 15, 2012
15:38:55.713347000&lt;/div&gt;
&lt;div class="line"&gt;frame 7 server | 0 record | Arrival Time: Feb 15, 2012
15:38:55.713753000&lt;/div&gt;
&lt;div class="line"&gt;frame 8 server | 1 record | Arrival Time: Feb 15, 2012
15:38:55.715003000&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Each of the SSL records begins with the same three fields (with
possibly different values). One of these fields is “content type” and
has length of one byte. List all three fields and their lengths.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Each hexadecimal digit (also called a &amp;quot;nibble&amp;quot;) represents four binary
digits (bits) so each pair of hexadecimal digits equals 1 byte.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;a. Destination mac address | 6 btyes | 00 21 9b 31 99 51&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;b. Source mac address | 6 bytes | 00 10 db ff 20&lt;/div&gt;
&lt;div class="line"&gt;c. Type: IP | 2 byte | 08 00&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;ClientHello Records&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;3.&lt;/strong&gt;Expand the ClientHello record. (If your trace contains
multiple ClientHello&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;records, expand the frame that contains the first one.) What is the
value of the&lt;/div&gt;
&lt;div class="line"&gt;content type?&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line"&gt;hex: 16 (16+6=22) Handshake&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;4.&lt;/strong&gt; Does the ClientHello record advertise the cipher suites it
supports? If so, in the first listed suite, what are the public-key
algorithm, the symmetric-key algorithm, and the hash algorithm?&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;MD5, SHA, RSA, DSS, DES, AES&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;ServertHello Records&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;5.&lt;/strong&gt; Look to the ServerHello packet. What cipher suite does it
choose?&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;6.&lt;/strong&gt; Does this record include a nonce? If so, how long is it? What
is the purpose of the&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;client and server nonces in SSL?&lt;/div&gt;
&lt;div class="line"&gt;Yes, 28 bytes. The ClientHello packet also generated a nonces. They
are used to make the session communication between the two nodes
unique. It &amp;quot;salts&amp;quot; the communication to prevent replay attacks. A
replay attack happens when data from old communications is used to
&amp;quot;crack&amp;quot; a current communication.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;7.&lt;/strong&gt;Does this record include a session ID? What is the purpose of
the session ID?&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Yes, This is to make things efficient, in case the client has any
plans of closing the current connection and reconnect in the near
future.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;8.&lt;/strong&gt;How many frames does the SSL certificate take to send?&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;In this case it took 4 frames&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Guide"></category><category term="Security"></category></entry><entry><title>Zenoss or Nagios monitoring of HTTPS using client certificate authentication</title><link href="https://russell.ballestrini.net/zenoss-or-nagios-monitoring-of-https-using-client-certificate-authentication/" rel="alternate"></link><published>2012-02-26T19:21:00-05:00</published><updated>2012-02-26T19:21:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-26:/zenoss-or-nagios-monitoring-of-https-using-client-certificate-authentication/</id><summary type="html">&lt;p&gt;I recently needed to monitor an HTTPS API for response time and
availability. At first I planned to just use the Nagios check_http
command.&lt;/p&gt;
&lt;p&gt;After gathering more requirements I learned that the API was protected
by client certificate authentication. After some research I quickly
found that no solution existed to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently needed to monitor an HTTPS API for response time and
availability. At first I planned to just use the Nagios check_http
command.&lt;/p&gt;
&lt;p&gt;After gathering more requirements I learned that the API was protected
by client certificate authentication. After some research I quickly
found that no solution existed to monitor HTTP protected by client
certs. I needed to write my own plugin.&lt;/p&gt;
&lt;p&gt;This is the python plugin I came up with:
&lt;strong&gt;check_http_client_cert.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#!/usr/bin/python

&amp;quot;&amp;quot;&amp;quot;Nagios/Zenoss client cert https checker&amp;quot;&amp;quot;&amp;quot;

import httplib
from optparse import OptionParser
from time import time
from sys import exit

def request( hostname, port, cert_file, path ):
    &amp;quot;&amp;quot;&amp;quot;request a resource and return response object&amp;quot;&amp;quot;&amp;quot;
    try:
        c = httplib.HTTPSConnection( hostname, port, cert_file=cert_file )
        c.request( &amp;quot;GET&amp;quot;, path )
        return c.getresponse()
    except:
        return False

if __name__ == '__main__':
    parser = OptionParser()
    parser.add_option('-H', '--hostname', dest='hostname')
    parser.add_option('-p', '--port', dest='port')
    parser.add_option('-c', '--cert_file', dest='cert_file')
    parser.add_option('-P', '--path', dest='path',
    help=&amp;quot;Path relative to root, like /image/search&amp;quot;)

    o, args = parser.parse_args()
    #print o

    start = time()
    r = request( o.hostname, o.port, o.cert_file, o.path )
    elapse = time() - start

    if r:
        if r.status &amp;gt;= 200 and r.status &amp;lt; 400:
            print &amp;quot;HTTP OK:&amp;quot;, r.status, r.reason, &amp;quot;|time=&amp;quot; + str(elapse) + &amp;quot;s;;;&amp;quot;
            exit( 0 )
        print &amp;quot;HTTP Critical:&amp;quot;, r.status, r.reason

    exit( 2 )
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category></entry><entry><title>Jake, Finn, and Ice King</title><link href="https://russell.ballestrini.net/jake/" rel="alternate"></link><published>2012-02-18T13:34:00-05:00</published><updated>2012-02-18T13:34:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-18:/jake/</id><summary type="html">&lt;p class="first last"&gt;My Adventure Time art, painted with MyPaint and wacom tablet.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;Jake from Adventure Time, painted with MyPaint and wacom tablet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I did this because my 4 month old &amp;quot;cracks-up&amp;quot; laughing whenever this
character is on screen. I think the combination of colors and Jake's
voice, played by John William DiMaggio, get him going.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew Jake from Adventure Time" src="/uploads/2012/02/mypaint-jake-adventure-time.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finn from Adventure Time.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Took a shot at painting Finn.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew Finn from Adventure Time" src="/uploads/2012/02/mypaint-finn-adventure-time.png" /&gt;&lt;/p&gt;
&lt;p&gt;I'm on a roll! Here is Ice King.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew Ice King from Adventure Time" src="/uploads/2012/02/ice-king-adventure-time.png" /&gt;&lt;/p&gt;
&lt;p&gt;Jake standing up, painted this this morning&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew Jake again from Adventure Time" src="/uploads/2012/02/jake-standing.png" /&gt;&lt;/p&gt;
&lt;p&gt;Lumpy princess&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew lumpy princess from Adventure Time" src="/uploads/2012/02/lump-princess.png" /&gt;&lt;/p&gt;
&lt;p&gt;I'm getting faster, this was completed in 20 minutes&lt;/p&gt;
&lt;p&gt;&lt;img alt="I drew sad Jake from Adventure Time" src="/uploads/2012/02/sad-jake.png" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Art"></category></entry><entry><title>My 4 month old's 15 minutes of fame</title><link href="https://russell.ballestrini.net/my-4-month-olds-15-minutes-of-fame/" rel="alternate"></link><published>2012-02-16T18:42:00-05:00</published><updated>2012-02-16T18:42:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-16:/my-4-month-olds-15-minutes-of-fame/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/my-4-month-olds-15-minutes-of-fame/cutest-redsox-fan-ever/"&gt;&lt;img alt="image0" src="/uploads/2012/02/cutest-redsox-fan-ever.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My sister &lt;a class="reference external" href="https://twitter.com/#!/VeronicaBal"&gt;&amp;#64;VeronicaBal&lt;/a&gt;
was watching my son (&lt;a class="reference external" href="https://twitter.com/#!/RussellBal"&gt;&amp;#64;RussellBal&lt;/a&gt;)
tweeted a picture of him.&lt;/p&gt;
&lt;p&gt;The official &lt;a class="reference external" href="https://twitter.com/#!/RedSox"&gt;&amp;#64;RedSox&lt;/a&gt; re-tweeted the image to all 197,035 followers!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'm a proud daddy!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://russell.ballestrini.net/my-4-month-olds-15-minutes-of-fame/screenshot-at-2012-02-16-172630/"&gt;&lt;img alt="image1" src="/uploads/2012/02/Screenshot-at-2012-02-16-172630.png" /&gt;&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Block cipher lab</title><link href="https://russell.ballestrini.net/block-cipher-lab/" rel="alternate"></link><published>2012-02-14T01:36:00-05:00</published><updated>2012-02-14T01:36:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-14:/block-cipher-lab/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Consider the following block cipher.&lt;/strong&gt; Suppose that each block
cipher T simply reverses the order of the eight input bits (so that,
for example 11110000 becomes 00001111).&lt;/div&gt;
&lt;div class="line"&gt;Further suppose that the 64-bit scrambler does not modify any bits.
With n = 3 iterations and the original 64-bit input equal to 10100000 …&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;Consider the following block cipher.&lt;/strong&gt; Suppose that each block
cipher T simply reverses the order of the eight input bits (so that,
for example 11110000 becomes 00001111).&lt;/div&gt;
&lt;div class="line"&gt;Further suppose that the 64-bit scrambler does not modify any bits.
With n = 3 iterations and the original 64-bit input equal to 10100000
repeated eight times, what is the value of the output?&lt;/div&gt;
&lt;div class="line"&gt;Now change the last bit of the original 64-bit input from 0 to a 1.
Now suppose that the 64-bit scrambler inverses the order of the 64
bits.&lt;/div&gt;
&lt;div class="line"&gt;&lt;strong&gt;Solution in python:&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
def chunks( l, n ):
    &amp;quot;&amp;quot;&amp;quot;accept a list and chuck size, return chunks&amp;quot;&amp;quot;&amp;quot;
    return [ l[ i:i+n ] for i in range( 0, len(l), n ) ]


def T( blocks ):
    &amp;quot;&amp;quot;&amp;quot;for each block, reverse block, return blocks&amp;quot;&amp;quot;&amp;quot;
    result = []
    for block in blocks:
        result.append( ''.join( [bit for bit in reversed( block )] ) )

    return result

def scrambler( input ):
    &amp;quot;&amp;quot;&amp;quot;inverse the order of input&amp;quot;&amp;quot;&amp;quot;
    return ''.join( [i for i in reversed( input ) ] )


def cipher1( input, n = 3, chunk_length = 8 ):
    &amp;quot;&amp;quot;&amp;quot;make chucks out of input, reverse each chunk return result&amp;quot;&amp;quot;&amp;quot;
    blocks = chunks( input, chunk_length )
    for i in range( 0, n ): blocks = T( blocks )
    return ''.join( blocks )


def cipher2( input, n = 3, chunk_length = 8 ):
    &amp;quot;&amp;quot;&amp;quot;same as cipher1 but with scrambler&amp;quot;&amp;quot;&amp;quot;
    blocks = chunks( input, chunk_length )
    for i in range( 0, n ):
        blocks = T( blocks )
        blocks = chunks( scrambler( ''.join( blocks ) ), chunk_length )
    return ''.join( blocks )


if __name__ == &amp;quot;__main__&amp;quot;:

    input = &amp;quot;1010000010100000101000001010000010100000101000001010000010100000&amp;quot;
    print cipher1( input )
    # output: 0000010100000101000001010000010100000101000001010000010100000101

    input = &amp;quot;1010000010100000101000001010000010100000101000001010000010100001&amp;quot;
    print cipher1( input )
    # output: 0000010100000101000001010000010100000101000001010000010110000101

    input = &amp;quot;1010000010100000101000001010000010100000101000001010000010100000&amp;quot;
    print cipher2( input )
    # output: 1010000010100000101000001010000010100000101000001010000010100000

    input = &amp;quot;1010000010100000101000001010000010100000101000001010000010100001&amp;quot;
    print cipher2( input )
    # output: 1010000110100000101000001010000010100000101000001010000010100000
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Security"></category></entry><entry><title>Monoalphabetic Cipher and Inverse Written in Python</title><link href="https://russell.ballestrini.net/monoalphabetic-cipher-and-inverse-written-in-python/" rel="alternate"></link><published>2012-02-13T22:39:00-05:00</published><updated>2012-02-13T22:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-13:/monoalphabetic-cipher-and-inverse-written-in-python/</id><summary type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#introduction-and-background" id="toc-entry-1"&gt;introduction and background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#monoalphabetic-cipher-py" id="toc-entry-2"&gt;monoalphabetic_cipher.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#monoalphabetic-cipher-py-example-usage" id="toc-entry-3"&gt;monoalphabetic_cipher.py example usage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="introduction-and-background"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;introduction and background&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A monoalphabetic cipher uses fixed substitution over the entire message.&lt;/p&gt;
&lt;p&gt;You can build a monoalphabetic cipher using a Python dictionary, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;v&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;z …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#introduction-and-background" id="toc-entry-1"&gt;introduction and background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#monoalphabetic-cipher-py" id="toc-entry-2"&gt;monoalphabetic_cipher.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#monoalphabetic-cipher-py-example-usage" id="toc-entry-3"&gt;monoalphabetic_cipher.py example usage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="introduction-and-background"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;introduction and background&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A monoalphabetic cipher uses fixed substitution over the entire message.&lt;/p&gt;
&lt;p&gt;You can build a monoalphabetic cipher using a Python dictionary, like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;v&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;j&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;k&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;l&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;j&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;o&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;k&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;p&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;l&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;p&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;o&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;u&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;u&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;v&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;e&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can create an inverse of this cipher dictionary by switching the key and value places:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;inverse_monoalpha_cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iteritems&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;inverse_monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have both the cipher and the inverse_cipher, we may encrypt a message.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encryption example:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;This is an easy problem&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;encrypted_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Result:&lt;/dt&gt;
&lt;dd&gt;&lt;tt class="docutils literal"&gt;Tasi si mj cmiw lokngch&lt;/tt&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Using the inverse_cipher, We may &lt;em&gt;decrypt&lt;/em&gt; a message.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decryption example:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;encrypted_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rmij&amp;#39;u uamu xyj?&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;decrypted_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="n"&gt;decrypted_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;inverse_monoalpha_cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;decrypted_message&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;Result:&lt;/dt&gt;
&lt;dd&gt;&lt;tt class="docutils literal"&gt;wasn't that fun?&lt;/tt&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="monoalphabetic-cipher-py"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;monoalphabetic_cipher.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is a toy library I wrote to make the process repeatable -&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;monoalphabetic_cipher.py&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;string&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random_monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generate a Monoalphabetic Cipher&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;letters&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;
    &lt;span class="n"&gt;original_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shuffled_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shuffled_pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shuffled_pool&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inverse_monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Given a Monoalphabetic Cipher (dictionary) return the inverse.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;inverse_monoalpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iteritems&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;inverse_monoalpha&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inverse_monoalpha&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encrypt_with_monoalpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;encrypted_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt_with_monoalpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;encrypt_with_monoalpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="n"&gt;encrypted_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;inverse_monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="monoalphabetic-cipher-py-example-usage"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;monoalphabetic_cipher.py example usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here I show how to use the library:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# load the module / library as &amp;#39;mc&amp;#39;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;monoalphabetic_cipher&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;mc&lt;/span&gt;


&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# generate a random cipher (only if needed).&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_monoalpha_cipher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# output the cipher (store for safe keeping).&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# encrypt a message with the cipher.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encrypt_with_monoalpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello all you hackers out there!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;sXGGt SGG Nt0 HSrLXFC t0U UHXFX!&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# decrypt a message with the cipher.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt_with_monoalpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sXGGt SGG Nt0 HSrLXFC t0U UHXFX!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;Hello all you hackers out there!&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Security"></category></entry><entry><title>Why does a Hash provide better message integrity then an Internet checksum?</title><link href="https://russell.ballestrini.net/why-does-a-hash-provide-better-message-integrity-then-an-internet-checksum/" rel="alternate"></link><published>2012-02-13T19:19:00-05:00</published><updated>2012-02-13T19:19:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-13:/why-does-a-hash-provide-better-message-integrity-then-an-internet-checksum/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Why does a Hash provide better message integrity then an Internet
checksum?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hash function and checksum function both return a value which cannot be
reversed.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;An Internet checksum (TCP checksum or IP checksum) is designed to
detect common errors quickly and efficiently. An Internet checksum
does not attempt to prevent …&lt;/div&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Why does a Hash provide better message integrity then an Internet
checksum?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hash function and checksum function both return a value which cannot be
reversed.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;An Internet checksum (TCP checksum or IP checksum) is designed to
detect common errors quickly and efficiently. An Internet checksum
does not attempt to prevent collisions.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;em&gt;Man cksum for more info.&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A Hash provides better message integrity because it has less collisions
then an Internet checksum. A collision means there is more then one way
to produce the same sum. A great hash function aims to reduce the
occurrence of collisions. &lt;em&gt;Man md5 and sha for more info.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is a collision&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Let H() be a hash function. Let x and y be two differing messages.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;em&gt;H(x) = H(y)&lt;/em&gt; would be a collision.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;I like to use python to show examples of hash functions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this example I pass a message into the MD5 hash function to produce a
resulting hash of the message. You can think of this hashed output as a
finger print of the message.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import hashlib as h
message = &amp;quot;This message will be placed into an MD5 hash function to authenticate its integrity.&amp;quot;
print h.md5(message).hexdigest()
&lt;/pre&gt;
&lt;/p&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;em&gt;Hash Output:&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
18f189f94b245ad8566206c199b4f60a
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Now If I passed that message to you along with its MD5 hash hex
representation, you could put the message into your own MD5 hash
function and compare the resulting hash. This method is used to validate
the message or verify data integrity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can you &amp;quot;decrypt&amp;quot; a hash of a message to get the original message&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No! A hash may &lt;em&gt;not&lt;/em&gt; be reversed, which means it cannot be decrypted.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;By design a hash algorithm has no inverse, there is no way to get the
original message from the hash. This is good news, turns out we have
some really great applications for this type of function. We can
validate messages, we can securely store passwords, and we can quickly
determine if a message or file has been tampered with.&lt;/p&gt;
&lt;p&gt;When using a publicly known hash function for storing password hashes,
make sure to always use a salt or shared secret. Failure to do so will
make your storage scheme susceptible to a rainbow table attack. A
rainbow table allows a cracker to quickly match a list of hashes with a
table of previously computed hash values and correlated passwords.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is salt or a shared secret?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can use salt or a shared secret to add extra data to a message
before hashing with a publicly known algorithm. Below I will document
how to properly add salt to a message before generating a SHA256 hash.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import hashlib as h
message = &amp;quot;This message and some salt will be hashed with SHA 256.&amp;quot;
salt = &amp;quot;This is some secret salt data&amp;quot;
print h.sha256(message+salt).hexdigest()
&lt;/pre&gt;
&lt;/p&gt;&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;em&gt;Hash Output:&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
5e8d86bab9604620f19cfbc5f836f47feb9e8c9e74264fff1f4938bdaab1eeaa
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;Adding a salt to the message allows us to use a publicly know algorithm
in a more protected manner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can you spot the error in the python code below?&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import hashlib as h
message = &amp;quot;This message and some salt will be hashed with SHA 256.&amp;quot;
salt = &amp;quot;This is some secret salt data&amp;quot;
print h.sha256(message).hexdigest()+salt
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;If you guessed that the message and salt BOTH need to be hashed together
then you are correct!&lt;/p&gt;
&lt;p&gt;The above code would have produced the following invalid hash:&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;em&gt;Hash Output:&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="literal-block"&gt;
79cd4bfa1bcb71a7a1b5bfd5e8cfc8368a6cc6cb836d24bf04f2ef2bd0e81261This is some secret salt data
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;You should follow me on twitter:&lt;/strong&gt; &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;&amp;#64;russellbal&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Security"></category></entry><entry><title>Symmetric Encryption vs Public Key Encryption</title><link href="https://russell.ballestrini.net/symmetric-encryption-vs-public-key-encryption/" rel="alternate"></link><published>2012-02-13T17:25:00-05:00</published><updated>2012-02-13T17:25:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-13:/symmetric-encryption-vs-public-key-encryption/</id><summary type="html">&lt;p&gt;&lt;strong&gt;How many keys are involved for symmetric key encryption? How about
public key encryption?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Suppose you have N people who want to communicate with each other using
symmetric keys. All communication between any two people, i and j, is
visible to group N. Only person i and person j can …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;How many keys are involved for symmetric key encryption? How about
public key encryption?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Suppose you have N people who want to communicate with each other using
symmetric keys. All communication between any two people, i and j, is
visible to group N. Only person i and person j can decrypt each others
messages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How many keys would Symmetric Encryption require to protect group N?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I solved this with the following python function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def count_symmetric_keys( N=2 ):
    &amp;quot;&amp;quot;&amp;quot;Provide the number of entities in group N.
    return the number of symmetric keys needed for this group&amp;quot;&amp;quot;&amp;quot;
    keys = 0
    for i in range( 0, N ): keys += i
    return keys
&lt;/pre&gt;
&lt;p&gt;A reader suggested the following optimized formula:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def calc_symmetric_keys( N=2 ):
    &amp;quot;&amp;quot;&amp;quot;Provide the number of entities in group N.
    return the number of symmetric keys needed for this group&amp;quot;&amp;quot;&amp;quot;
    return N*(N-1)/2
&lt;/pre&gt;
&lt;/p&gt;&lt;p&gt;If group N had 10 members, it would need to generate and maintain 45
Symmetric Keys.&lt;/p&gt;
&lt;p&gt;If group N had 50 members, it would need to generate and maintain 1225
Symmetric Keys.&lt;/p&gt;
&lt;p&gt;Symmetric keys are also susceptible to man-in-the-middle attacks. This
attack occurs when an entity poses as a trusted entity. Let i and j be
trusted entities. Let k be an untrusted attacker. If k determined the
Symmetric key it could send or receive messages posing as i or j.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How many keys would Public-key Encryption require to protect group
N?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Public Key Encryption requires 2n keys or two keys per person in group
N. Public key encryption also does not require 'pre sharing' the secret
key before communication may start. Each member would need 1 public key
and 1 private key.&lt;/p&gt;
&lt;p&gt;If group N had 10 members, it would need to generate and maintain 20
Public/Private Keys.&lt;/p&gt;
&lt;p&gt;If group N had 50 members, it would need to generate and maintain 100
Public/Private Keys.&lt;/p&gt;
</content><category term="misc"></category><category term="Security"></category></entry><entry><title>Attributes of an 8-block cipher</title><link href="https://russell.ballestrini.net/attributes-of-an-8-block-cipher/" rel="alternate"></link><published>2012-02-13T00:50:00-05:00</published><updated>2012-02-13T00:50:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-13:/attributes-of-an-8-block-cipher/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Consider an 8-block cipher and answer the following:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How many possible input blocks does this cipher have?&lt;/p&gt;
&lt;p&gt;How many possible mappings are there?&lt;/p&gt;
&lt;p&gt;If we view each mapping as a key, then how many possible keys does this cipher have?&lt;/p&gt;
&lt;p&gt;To find the input blocks of this cipher we raise …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Consider an 8-block cipher and answer the following:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How many possible input blocks does this cipher have?&lt;/p&gt;
&lt;p&gt;How many possible mappings are there?&lt;/p&gt;
&lt;p&gt;If we view each mapping as a key, then how many possible keys does this cipher have?&lt;/p&gt;
&lt;p&gt;To find the input blocks of this cipher we raise 2 to the 8th power. 2^8 = 256 possible inputs.&lt;/p&gt;
&lt;p&gt;To find the number of possible mappings we take the 256 input blocks and
find it's factorial. There are 256! possible mappings.&lt;/p&gt;
&lt;p&gt;We can view each of these mappings as a key, so this cipher has 256! keys.&lt;/p&gt;
</content><category term="misc"></category><category term="Security"></category></entry><entry><title>Reasons why some Internet entities might want secure communication</title><link href="https://russell.ballestrini.net/reasons-why-some-internet-entities-might-want-secure-communication/" rel="alternate"></link><published>2012-02-08T18:23:00-05:00</published><updated>2012-02-08T18:23:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-08:/reasons-why-some-internet-entities-might-want-secure-communication/</id><summary type="html"></summary><content type="html">&lt;p&gt;Internet entities often desire to communicate securely, some reasons include:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;strong&gt;Web Servers:&lt;/strong&gt; Communication on the Internet, or any network for that
matter, should be encrypted before transmitting sensitive data. This
will help prevent snooping from unauthorized parties. Most often SSL or
HTTPS may be used to create a secure communication “tunnel” between a
web server and a web client (browser).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server Administration:&lt;/strong&gt;
Operators should always use a secure protocal when working on a server or remote computer.
SSH (Secure Shell) wins the popularity contest with server admins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS Servers:&lt;/strong&gt;
Using DNSSEC could help prevent DNS poisoning and certifies DNS data.
DNS was first conceived as a distributed and highly scalable address lookup system.
Security was not its top priority.
Since then we have new DNSSEC extensions which allow for origin authentication of DNS data,
authenticated denial of existence, and data integrity.
DNSSEC does not attempt to solve availability and confidentiality.&lt;/li&gt;
&lt;/ol&gt;
</content><category term="misc"></category><category term="Security"></category></entry><entry><title>What are the differences between message confidentiality and message integrity</title><link href="https://russell.ballestrini.net/what-are-the-differences-between-message-confidentiality-and-message-integrity/" rel="alternate"></link><published>2012-02-08T17:47:00-05:00</published><updated>2012-02-08T17:47:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-02-08:/what-are-the-differences-between-message-confidentiality-and-message-integrity/</id><summary type="html"></summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What are the differences between message confidentiality and message
integrity? Can you have confidentiality without integrity? Can you have
integrity without confidentiality?&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;message confidentiality&lt;/dt&gt;
&lt;dd&gt;Two or more hosts communicate securely, typically using encryption.
The communication cannot be monitored (sniffed) by untrusted hosts.
The communication between trusted parties is confidential.&lt;/dd&gt;
&lt;dt&gt;message integrity&lt;/dt&gt;
&lt;dd&gt;The message transported has not been tampered with or altered. A
message has integrity when the payload sent is the same as the
payload received.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Sending a message confidentially does not guarantee data integrity. Even
when two nodes have authenticated each other, the integrity of a message
could be compromised during the transmission of a message.&lt;/p&gt;
&lt;p&gt;Yes, you can have integrity of a message without confidentiality. One
can take a hash or sum of the message on both sides to compare. Often we
share downloadable files and provide data integrity using md5 hash sums.&lt;/p&gt;
</content><category term="misc"></category><category term="Security"></category></entry><entry><title>Today I lost a customer</title><link href="https://russell.ballestrini.net/today-i-lost-a-customer/" rel="alternate"></link><published>2012-01-18T23:30:00-05:00</published><updated>2012-01-18T23:30:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2012-01-18:/today-i-lost-a-customer/</id><summary type="html">&lt;p&gt;Today I lost a customer.&lt;/p&gt;
&lt;p&gt;I added some new code to &lt;a class="reference external" href="https://linkpeek.com/website-thumbnail-generator"&gt;LinkPeek&lt;/a&gt; to accept coupons and I didn't think of an edge case.
This ended up creating an uncaught exception in my server side code which ultimatly served the newly subscribing customer an HTTP 500 error page.&lt;/p&gt;
&lt;p&gt;The damage was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today I lost a customer.&lt;/p&gt;
&lt;p&gt;I added some new code to &lt;a class="reference external" href="https://linkpeek.com/website-thumbnail-generator"&gt;LinkPeek&lt;/a&gt; to accept coupons and I didn't think of an edge case.
This ended up creating an uncaught exception in my server side code which ultimatly served the newly subscribing customer an HTTP 500 error page.&lt;/p&gt;
&lt;p&gt;The damage was done.&lt;/p&gt;
&lt;p&gt;This error was catastrophic and ultimately killed the conversion. Here
is an excerpt of how the user felt after the experience:&lt;/p&gt;
&lt;blockquote&gt;
At this point the website has failed spectacularly enough that I can
no longer trust you with my business. Please void the charge on my
American Express card before it's processed. I need to hear from you
ASAP.&lt;/blockquote&gt;
&lt;p&gt;Customers and prospects are forgiving for normal bugs in software.
However, customers are intolerant to bugs in the sign up or payment
flow. An error in payment flow will cause friction and friction will
kill the sale.&lt;/p&gt;
&lt;p&gt;Running a start up is hard work, and negative (but justified) feedback
hurts more then I thought it would. This email made me feel awful.&lt;/p&gt;
&lt;p&gt;Fortunately I learned from this mistake and the user's card was never
charged.&lt;/p&gt;
&lt;p&gt;Always make sure to extensively test payment and sign up code. Try all
the edge cases. Try to make your software break. Don't inflict friction
on your potential customers.&lt;/p&gt;
</content><category term="misc"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>LinkPeek.com, webpage to image, was a by-product</title><link href="https://russell.ballestrini.net/linkpeek-com-webpage-to-image-was-a-by-product/" rel="alternate"></link><published>2011-12-19T00:23:00-05:00</published><updated>2011-12-19T00:23:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-12-19:/linkpeek-com-webpage-to-image-was-a-by-product/</id><summary type="html">&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt; When faced with pivoting or killing a project, take a good
look at all possible by-products. Don't miss the hidden gem in a
project's slag!&lt;/p&gt;
&lt;p&gt;Last year I built yoursitemakesmebarf.com, a novelty web application
which allowed anonymous link submission. The software would
automatically take
&lt;a class="reference external" href="https://russell.ballestrini.net/linkpeek-com-web-address-thumbnail-api-alpha-release/"&gt;screenshots&lt;/a&gt;
of submitted links …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt; When faced with pivoting or killing a project, take a good
look at all possible by-products. Don't miss the hidden gem in a
project's slag!&lt;/p&gt;
&lt;p&gt;Last year I built yoursitemakesmebarf.com, a novelty web application
which allowed anonymous link submission. The software would
automatically take
&lt;a class="reference external" href="https://russell.ballestrini.net/linkpeek-com-web-address-thumbnail-api-alpha-release/"&gt;screenshots&lt;/a&gt;
of submitted links and curate a blog. I enjoyed building the site and
the project served as my first Pyramid application.&lt;/p&gt;
&lt;p&gt;The project's original intent was to jokingly poke fun at ugly design.
The idea never caught on. Instead the application angered website owners
and attracted undesirable people. Eventually, I decided to take it down
and come up with less combative idea.&lt;/p&gt;
&lt;p&gt;After witnessing the Goog release of &amp;quot;instant previews&amp;quot;, I knew there
was a market for a fast and reliable web screen shot service.&lt;/p&gt;
&lt;a href="https://linkpeek.com" target="_blank"&gt;&lt;img src="https://linkpeek.com/api/v1?uri=https%3A//linkpeek.com&amp;apikey=9fhvyH9KP&amp;token=8a46bf52022d2f837d203eaad7b97fad&amp;size=500x240&amp;viewport=" title="web page screen shot service" align="" style="float: left; border-radius: 15px; margin-right: 15px;" class="" alt="" /&gt;&lt;/a&gt;&lt;p&gt;So I decided to bring instant previews to anyone who needed them. I
wanted to build a fast, flexible, and easy to use screenshot API. After
a few months I had a working prototype. I named the product LinkPeek
because it described what the service was, and the domain was available.&lt;/p&gt;
&lt;p&gt;Next I built a website thumbnail generator to show off the software. The
generator application helped reinforce the simplicity of the underlying
LinkPeek API. It didn't require any downloading, installation, or
waiting.&lt;/p&gt;
&lt;p&gt;About a week later on a whim I posted the generator to hacker news with an honest title (&lt;a class="reference external" href="https://linkpeek.com/website-thumbnail-generator"&gt;Convert Any Webpage to an Image&lt;/a&gt;) and in about 30 minutes it reached the number 1 spot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remember, when faced with pivoting or killing a project, take a good
look at all possible by-products. Don't miss the hidden gem in a
project's slag!&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>flash mob office meeting definition</title><link href="https://russell.ballestrini.net/flash-mob-office-meeting-definition/" rel="alternate"></link><published>2011-12-12T15:34:00-05:00</published><updated>2011-12-12T15:34:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-12-12:/flash-mob-office-meeting-definition/</id><content type="html">&lt;p&gt;&lt;strong&gt;Flash Meeting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In a office or cubicle environment a group of uninvited people gather
and hover around your desk to talk to you. A meeting forms in immaculate
conception as you sit bewildered at your desk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Other names:&lt;/strong&gt; &lt;em&gt;Flash Meeting&lt;/em&gt;, &lt;em&gt;Flash Mob Meeting&lt;/em&gt;, &lt;em&gt;Flash Office
Meeting&lt;/em&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Uncategorized"></category></entry><entry><title>LinkPeek.com Number One on Hacker News</title><link href="https://russell.ballestrini.net/linkpeek-com-number-one-on-hacker-news/" rel="alternate"></link><published>2011-12-05T00:39:00-05:00</published><updated>2011-12-05T00:39:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-12-05:/linkpeek-com-number-one-on-hacker-news/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;We made the number one spot on Hacker News.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;LinkPeek was used to take a snapshot of the event:&lt;/div&gt;
&lt;div class="line"&gt;&lt;img alt="image0" src="/uploads/2011/12/linkpeek-number-1-on-hacker-news.png" /&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>How to Incorporate Custom Configuration in a Pyramid Application</title><link href="https://russell.ballestrini.net/how-to-incorporate-custom-configuration-in-a-pyramid-application/" rel="alternate"></link><published>2011-11-30T22:47:00-05:00</published><updated>2011-11-30T22:47:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-11-30:/how-to-incorporate-custom-configuration-in-a-pyramid-application/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post shows an old way to modify the request object.
I discuss a new way in my &lt;a class="reference external" href="/register-super-powers-with-pyramid-add-request-method/"&gt;Pyramid add_request_method&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;Imagine that you have just built a wiki, blog, or cms web application
that will be deployed multiple times by different people. You would like
to provide the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post shows an old way to modify the request object.
I discuss a new way in my &lt;a class="reference external" href="/register-super-powers-with-pyramid-add-request-method/"&gt;Pyramid add_request_method&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;Imagine that you have just built a wiki, blog, or cms web application
that will be deployed multiple times by different people. You would like
to provide the ability to configure some aspects of the program without
the end user altering your python or template code. This guide explains
how to incorporate custom configuration from the project's .ini file
with the rest of the application.&lt;/p&gt;
&lt;p&gt;To explain this process we will add a customizable Google Analytics key
to our project.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Make a configuration key/value pair for Google Analytics&lt;/li&gt;
&lt;li&gt;Add the Google Analytics javascript code to the template&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Make a configuration key/value pair for Google Analytics&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inside production.ini place the following in the &lt;tt class="docutils literal"&gt;[app:main]&lt;/tt&gt; section:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
#google_analytics_key = UA-55555555-1
google_analytics_key =
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Add the Google Analytics javascript code to the template&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this case I will show an example in mako. Other template solutions
should look similar.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;lt;%def name=&amp;quot;google\_analytics()&amp;quot;&amp;gt;
% if request.registry.settings['google_analytics_key']:


 &amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/p&amp;gt;
 &amp;lt;p&amp;gt;var _gaq = _gaq || [];&amp;lt;br /&amp;gt;
           _gaq.push(['_setAccount', &amp;quot;${request.registry.settings['google_analytics_key']}&amp;quot;]);&amp;lt;br /&amp;gt;
           _gaq.push(['_trackPageview']);&amp;lt;/p&amp;gt;
 &amp;lt;p&amp;gt;(function() {&amp;lt;br /&amp;gt;
           var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;&amp;lt;br /&amp;gt;
           ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'https://www') + '.google-analytics.com/ga.js';&amp;lt;br /&amp;gt;
           var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);&amp;lt;br /&amp;gt;
           })();&amp;lt;/p&amp;gt;
 &amp;lt;p&amp;gt;&amp;lt;/script&amp;gt;

% endif
&lt;/pre&gt;
&lt;p&gt;Then in the head section call the function:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
${ google_analytics() }
&lt;/pre&gt;
&lt;p&gt;That was easy because the configuration string just needed to be
substituted into the JavaScript in the template. What if you needed to
do something with the provided key/value before using it? Next I will
show you a method for building renderer globals again showing a
different way to configure Google Analytics:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make an inject_renderer_globals subscriber&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Define &lt;tt class="docutils literal"&gt;inject_renderer_globals(event)&lt;/tt&gt; function in the project's
&lt;tt class="docutils literal"&gt;__init__.py&lt;/tt&gt; file.&lt;/p&gt;
&lt;p&gt;I normally place it at the bottom of the file and it looks like this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def inject_renderer_globals(event):
    &amp;quot;&amp;quot;&amp;quot;Inject some renderer globals before passing to template&amp;quot;&amp;quot;&amp;quot;

    request = event['request']

    # Build ${google_analytics_key} from the configuration file
    event['google_analytics_key'] = request.registry.settings[ 'google_analytics_key' ]
&lt;/pre&gt;
&lt;p&gt;Import BeforeRender at the top of the &lt;tt class="docutils literal"&gt;__init__.py&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from pyramid.events import BeforeRender
&lt;/pre&gt;
&lt;p&gt;In the main function, add the &lt;tt class="docutils literal"&gt;inject_renderer_globals&lt;/tt&gt; to the
subscribers:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
config.add_subscriber(inject_renderer_globals, BeforeRender)
&lt;/pre&gt;
&lt;p&gt;Now you can use &lt;tt class="docutils literal"&gt;${google_analytics_key}&lt;/tt&gt; anywhere in your template.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thank you for reading, and feel free to leave comments&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category></entry><entry><title>Career development is a game of chutes and ladders</title><link href="https://russell.ballestrini.net/career-development-is-a-game-of-chutes-and-ladders/" rel="alternate"></link><published>2011-11-29T20:42:00-05:00</published><updated>2011-11-29T20:42:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-11-29:/career-development-is-a-game-of-chutes-and-ladders/</id><summary type="html">&lt;p class="first last"&gt;find out what is the ladder.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;If career development was a game of chutes and ladders, job networking
would be the ladder. Ladders provide a shortcut to the top, a direct route
to win, in this case you win your dream job.&lt;/p&gt;
&lt;img alt="chutes and ladders" class="align-right" src="/uploads/2011/11/job-networking-chutes-and-ladders.gif" /&gt;
&lt;p&gt;At work today, a colleague was reviewing resumes for an open requisition
within the Unix group. Later that night I decided to clean up &lt;a class="reference external" href="/uploads/russell.ballestrini.resume.pdf"&gt;my
resume&lt;/a&gt;
to make it more relevant.&lt;/p&gt;
&lt;p&gt;I felt like I did something positive for my career.
After coming down from the high of resume writing I began to question my rational and came to the contradictory conclusion that a great resume holds less importance than a mediocre recommendation.&lt;/p&gt;
&lt;p&gt;It is better for an employer to learn about you from a recommendation then your resume.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Hiring new people holds risk.
A hiring manager will reduce risk by promoting from within or using recommendations.
In both cases the resume becomes a document of formality instead of a document of credentials.&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;Its not what you know, its who you know.&amp;quot;&lt;/blockquote&gt;
&lt;p&gt;What can we assume about the position they are extending to the public?&lt;/p&gt;
&lt;p&gt;Can we assume that the manager has already promoted somebody internally for the high risk, enjoyable position?
Depending on the industry, they might be looking for bottom feeder (with a resume) to fill the newly vacant position.
Keep this in mind the next time a head hunter sends you a proposition.&lt;/p&gt;
&lt;p&gt;Taking shortcuts normally goes against the conventional wisdom for success.&lt;/p&gt;
&lt;p&gt;Job networking however, allows candidates to skip ahead and arrive at their dream occupation more directly.
Meeting new people seems like the single best way to land a job doing what you love.
Get out there and meet people with similar interests!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did you like this post?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You should checkout the first chapter of my e-book, &lt;em&gt;How-to Work From Home&lt;/em&gt; titled &lt;a class="reference external" href="/how-to-work-from-home-the-road-to-remote-chapter-1/"&gt;The Road to Remote&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>I'm petrified of launching my web application</title><link href="https://russell.ballestrini.net/im-petrified-of-launching-my-web-application/" rel="alternate"></link><published>2011-11-03T21:35:00-04:00</published><updated>2011-11-03T21:35:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-11-03:/im-petrified-of-launching-my-web-application/</id><summary type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;I'm petrified of launching my web application because I'm fearful
that I won't ...&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;acquire users&lt;/li&gt;
&lt;li&gt;support my users well&lt;/li&gt;
&lt;li&gt;scale in a timely manner&lt;/li&gt;
&lt;li&gt;react quickly to feedback&lt;/li&gt;
&lt;li&gt;monetize the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But most of all I'm scared that nobody will like me. I'm scared of
failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now that I got …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;strong&gt;I'm petrified of launching my web application because I'm fearful
that I won't ...&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;acquire users&lt;/li&gt;
&lt;li&gt;support my users well&lt;/li&gt;
&lt;li&gt;scale in a timely manner&lt;/li&gt;
&lt;li&gt;react quickly to feedback&lt;/li&gt;
&lt;li&gt;monetize the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But most of all I'm scared that nobody will like me. I'm scared of
failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now that I got that out of my system please check out https://linkpeek.com&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>Webmaster tools alerted issue turned out Pylon session files flooded inodes</title><link href="https://russell.ballestrini.net/webmaster-tools-alerted-issue-turned-out-pylon-session-files-flooded-inodes/" rel="alternate"></link><published>2011-10-29T15:30:00-04:00</published><updated>2011-10-29T15:30:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-29:/webmaster-tools-alerted-issue-turned-out-pylon-session-files-flooded-inodes/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;This graph could happen to you if you ever forget to configure munin
email alerting:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/10/df_inode-year.png" /&gt;&lt;/p&gt;
&lt;p&gt;It only took approximately 1 hour to diagnoses and resolve this issue
however most of my web applications hosted on this server were down for
about 11 hours. I was lucky that this outage fell on a weekend otherwise
I would not have known about the problem till around 6:30pm!&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" src="/uploads/2011/10/df_inode-day.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Two of my pylons apps had session files that slowly ran away on me. The
session files don't consume much capacity however the shear quantity of
them caused my inode usage to hit 100%.&lt;/p&gt;
&lt;p&gt;Had I properly configured Munin's email alerting this issue would have
been identified well before it was a problem.&lt;/p&gt;
&lt;p&gt;Want to know what alerted me to the problem? G Webmaster's tools claimed
it could not read my robots.txt on a few of my sites... After
investigating I learned the site was down. Checking the Apache error
logs pointed me to disk space issues. &lt;tt class="docutils literal"&gt;df &lt;span class="pre"&gt;-ha&lt;/span&gt;&lt;/tt&gt; reported everything was
fine, however &lt;tt class="docutils literal"&gt;df &lt;span class="pre"&gt;-hi&lt;/span&gt;&lt;/tt&gt; reported 100% inode usage! At this point I
started looking to cache and log locations to find lots of files, which
lead me to my pylons web applications data/sessions directories.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Delete the session cache tree directories and allow the applications to
rebuild them.&lt;/p&gt;
&lt;p&gt;Todo: move /www off the root disk partition. This issue could have been
much worse if I was unable to boot or login to remedy. Moving /www off
root should prevent the web server from effecting the systems ability to
boot.&lt;/p&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Opinion"></category></entry><entry><title>Occupy Wall Street Stack vs Queue</title><link href="https://russell.ballestrini.net/occupy-wall-street-stack-vs-queue/" rel="alternate"></link><published>2011-10-21T13:31:00-04:00</published><updated>2011-10-21T13:31:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-21:/occupy-wall-street-stack-vs-queue/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Occupy Wall Street contributors claim to use a &amp;quot;stack&amp;quot; to determine
speaking arrangements.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I plan to explain how the term &amp;quot;stack&amp;quot; used in this scenario does not
align itself with the mathematical or computer science definition.&lt;/p&gt;
&lt;p&gt;The term stack means First In Last Out or &amp;quot;FILO&amp;quot;. For example: a person …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Occupy Wall Street contributors claim to use a &amp;quot;stack&amp;quot; to determine
speaking arrangements.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I plan to explain how the term &amp;quot;stack&amp;quot; used in this scenario does not
align itself with the mathematical or computer science definition.&lt;/p&gt;
&lt;p&gt;The term stack means First In Last Out or &amp;quot;FILO&amp;quot;. For example: a person
placed on the stack in the morning would be the last to speak at the end
of the day. This isn't happening like that...&lt;/p&gt;
&lt;p&gt;Occupy Wall Street compatriots really use a technique called &amp;quot;queue&amp;quot;.
Mathematicians and Computer Scientists define a queue as First In First
Out or &amp;quot;FIFO&amp;quot;. A real world example of a queue would be a line at the
grocery store. The first person in line is the first person to leave the
line.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;An object enters a stack from the rear and exits the rear.&lt;/li&gt;
&lt;li&gt;An object enters a queue from the rear and exits from the front.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;img alt="image0" src="/uploads/2011/10/stack-vs-queue.png" /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>Adding inline image support to a gmail messages</title><link href="https://russell.ballestrini.net/adding-inline-image-support-to-a-gmail-messages/" rel="alternate"></link><published>2011-10-20T17:46:00-04:00</published><updated>2011-10-20T17:46:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-20:/adding-inline-image-support-to-a-gmail-messages/</id><summary type="html">&lt;p&gt;Enable ability to insert images into a message body. You can upload and
insert image files in your computer, or insert images by URLs. This lab
will not work if you have offline enabled.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Go to google labs: &lt;a class="reference external" href="https://mail.google.com/mail/#settings/labs"&gt;https://mail.google.com/mail/#settings/labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Search &amp;quot;images&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Enable&lt;/em&gt; &amp;quot;Inserting images …&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;Enable ability to insert images into a message body. You can upload and
insert image files in your computer, or insert images by URLs. This lab
will not work if you have offline enabled.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Go to google labs: &lt;a class="reference external" href="https://mail.google.com/mail/#settings/labs"&gt;https://mail.google.com/mail/#settings/labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Search &amp;quot;images&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Enable&lt;/em&gt; &amp;quot;Inserting images - by Kent T&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Welcome!&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>Cell shading practice, A sketched skull, and an old wind mill photograph</title><link href="https://russell.ballestrini.net/cell-shading-practice-a-sketched-skull-and-wind-mill-photograph/" rel="alternate"></link><published>2011-10-16T12:56:00-04:00</published><updated>2011-10-16T12:56:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-16:/cell-shading-practice-a-sketched-skull-and-wind-mill-photograph/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;Some of my resent works using my Ubuntu + Wacom Bamboo + MyPaint +
Gimp Workflow!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First some cell shading practice. Inside MyPaint I only used two colors
in my palate but to gain shadow and depth I used variations of opacity.
This technique produces a neat cartoon effect.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/10/smile-guy.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Next I sketched a skull. I attempted to keep all my strokes in the same
direction. This piece is monotone. I used only one color and deviated
each layer with different opacity settings.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image1" src="/uploads/2011/10/skull.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Last is a photograph I snapped during a family outing. I had to lower
the quality of this image to prepare for use on the web, but I still
adore the colors spectrum of this shot. The white birch tree frames the
right side while the grass and brush frame the bottom. The wind mill was
intentionally placed in the center with the apex extending upward. The
ivy creeps up the structure and the cloud filled sky appears to stretch
forever. The foliage emits a euphoric glow under the sun saturated rays.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2" src="/uploads/2011/10/wind.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Come back soon!&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Art"></category></entry><entry><title>I cancelled my xbox live automatic renewal</title><link href="https://russell.ballestrini.net/i-cancelled-my-xbox-live-automatic-renewal/" rel="alternate"></link><published>2011-10-15T19:26:00-04:00</published><updated>2011-10-15T19:26:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-15:/i-cancelled-my-xbox-live-automatic-renewal/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;I cancelled my xbox live automatic renewal today because I no longer
use the service.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I find humor in Microsoft's list of reasons to keep Xbox live:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/10/microsoft-resells-free-internet-services.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6 out of 8 services above are FREE Internet services!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If I want to use those free Internet services on my TV,
I'd rather use my tiny media center PC (&lt;a class="reference external" href="https://www.foxhop.net/nT330i"&gt;nT330i&lt;/a&gt;)
which is amazing and near silent.&lt;/p&gt;
&lt;p&gt;Microsoft prevents subscribers from cancelling their service over the
web, and thus they forced me to call their service center. After waiting
5 minutes I finally spoke to a sales representative who seemed friendly
and understanding.&lt;/p&gt;
&lt;p&gt;She asked me why I wanted to cancel automatic payments. I explained &amp;quot;I
want to pay month to month&amp;quot;. After listening to my position the sales
representative offered &lt;strong&gt;1 month of Xbox Live for $1.00.&lt;/strong&gt; So I'll have
Xbox live until December 2011 after all.&lt;/p&gt;
&lt;p&gt;Microsoft, give your users a web browser on the Xbox and stop trying to
sell subscriptions to Free Internet services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;*PlayStation 3 launched with a web browser and a FREE network.*&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; If you opt into the $1 for one month deal, you are also
opting back into the automatic renewal program... Even if the person on
the phone tells you otherwise.&lt;/p&gt;
&lt;p&gt;Call us directly by dialing:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Toll free:
(800) 4MY-XBOX or (800) 469-9269
Hearing impaired (TDD device):
(866) 740-9269 or (425) 635-7102
9 am to 1 am Eastern Time
6 am to 10 pm Pacific Time
&lt;/pre&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>r8168 driver issues after Ubuntu 11.10 upgrade kernel linux 3.0</title><link href="https://russell.ballestrini.net/r8168-driver-issues-after-ubuntu-11-10-upgrade-kernel-linux-3-0/" rel="alternate"></link><published>2011-10-15T11:15:00-04:00</published><updated>2011-10-15T11:15:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-15:/r8168-driver-issues-after-ubuntu-11-10-upgrade-kernel-linux-3-0/</id><summary type="html">&lt;p&gt;&lt;strong&gt;I had network issues after upgrading to Ubuntu 11.10 which has the
linux 3.0 kernel.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used &lt;a class="reference external" href="https://www.foxhop.net/realtek-dropping-packets-on-linux-ubuntu-and-fedora"&gt;this guide&lt;/a&gt;
to compile the r8168 driver however, I needed to alter the Makefile to support for linux 3.0 kernel.&lt;/p&gt;
&lt;p&gt;Edit &lt;em&gt;src/Makefile&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;#KEXT  := $(shell echo $(KVER) | sed -ne &amp;#39;s …&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;I had network issues after upgrading to Ubuntu 11.10 which has the
linux 3.0 kernel.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used &lt;a class="reference external" href="https://www.foxhop.net/realtek-dropping-packets-on-linux-ubuntu-and-fedora"&gt;this guide&lt;/a&gt;
to compile the r8168 driver however, I needed to alter the Makefile to support for linux 3.0 kernel.&lt;/p&gt;
&lt;p&gt;Edit &lt;em&gt;src/Makefile&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;#KEXT  := $(shell echo $(KVER) | sed -ne &amp;#39;s/^2\.[567]\..*/k/p&amp;#39;)o&lt;/span&gt;
KEXT&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;KVER&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-ne&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/^[23]\.[1-9]\..*/k/p&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;o-
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Now you should follow the rest of the guide.&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="guide"></category></entry><entry><title>A better way to show website backlinks</title><link href="https://russell.ballestrini.net/a-better-way-to-show-website-backlinks/" rel="alternate"></link><published>2011-10-12T20:05:00-04:00</published><updated>2011-10-12T20:05:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-12:/a-better-way-to-show-website-backlinks/</id><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Early web pilgrims of the Internet fashioned search queries like&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;tt class="docutils literal"&gt;link:russell.ballestrini.net&lt;/tt&gt; to gather backlinks for a domain.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I however advocate a revolutionary search pattern like -&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;quot;russell.ballestrini.net&amp;quot; -site:russell.ballestrini.net
&lt;/pre&gt;
&lt;p&gt;to gather an improved representation of backlinks.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.google.com/#sclient=psy-ab&amp;amp;hl=en&amp;amp;source=hp&amp;amp;q=%22school.yohdah.com%22+-site:school.yohdah.com&amp;amp;pbx=1&amp;amp;oq=%22school.yohdah.com%22+-site:school.yohdah.com&amp;amp;aq=f&amp;amp;aqi=&amp;amp;aql=1&amp;amp;gs_sm=e&amp;amp;gs_upl=1836l15311l0l17007l43l35l0l0l0l8l274l5507l4.23.8l35l0&amp;amp;bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&amp;amp;fp=18aa9f7b4fee5b6d&amp;amp;biw=1485&amp;amp;bih=912"&gt;Click here to try
now!&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Guide"></category><category term="Opinion"></category></entry><entry><title>CSS frameworks not rendering properly on all browsers</title><link href="https://russell.ballestrini.net/css-frameworks-not-rendering-properly-on-all-browsers/" rel="alternate"></link><published>2011-10-11T00:07:00-04:00</published><updated>2011-10-11T00:07:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-11:/css-frameworks-not-rendering-properly-on-all-browsers/</id><summary type="html">&lt;p class="first last"&gt;Read on for the one line fix.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I ran into an issue when testing skeleton css framework and twitter
bootstrap where some browsers were not rendering the pages properly.
After some research I determined that I forgot the DOCTYPE declaration
tag.&lt;/p&gt;
&lt;p&gt;The DOCTYPE tag tells the browser what &amp;quot;rules&amp;quot; or standards to use when
rendering markup.&lt;/p&gt;
&lt;p&gt;The following code block displays the minimal approach to setting the
document type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This doctype tag forces the broswer into standards mode which allows the pages to render properly.&lt;/p&gt;
&lt;p&gt;Thanks!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category></entry><entry><title>LinkPeek.com web address thumbnail api alpha release</title><link href="https://russell.ballestrini.net/linkpeek-com-web-address-thumbnail-api-alpha-release/" rel="alternate"></link><published>2011-10-05T20:58:00-04:00</published><updated>2011-10-05T20:58:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-05:/linkpeek-com-web-address-thumbnail-api-alpha-release/</id><summary type="html">&lt;p class="first last"&gt;Convert any webpage to an image.&lt;/p&gt;
</summary><content type="html">&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;a href="https://linkpeek.com" target="_blank"&gt;&lt;img src="https://linkpeek.com/api/v1?uri=https%3A//linkpeek.com&amp;apikey=9fhvyH9KP&amp;token=28442d5a0ad7ca0053bdca96b6f5f958&amp;size=140x100&amp;viewport=" title="" align="" style="margin-right: 25px; float: left;" class="" alt="" /&gt;&lt;/a&gt;&lt;p&gt;The &lt;a class="reference external" href="https://linkpeek.com/"&gt;LinkPeek&lt;/a&gt; API had an alpha release!&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;LinkPeek is a website screenshot service. Convert any webpage to an
image. No software, no downloading and absolutely no waiting! Unlimited
free 140 pixel thumbnails!&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="LinkPeek"></category></entry><entry><title>How do I calculate the M in my MVP?</title><link href="https://russell.ballestrini.net/how-do-i-calculate-the-m-in-my-mvp/" rel="alternate"></link><published>2011-10-01T15:32:00-04:00</published><updated>2011-10-01T15:32:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-10-01:/how-do-i-calculate-the-m-in-my-mvp/</id><summary type="html">&lt;p class="first last"&gt;Is my idea too minimal?&lt;/p&gt;
</summary><content type="html">&lt;dl class="docutils"&gt;
&lt;dt&gt;MVP (Minimal Viable Product):&lt;/dt&gt;
&lt;dd&gt;The smallest version of your idea that produces value for customers.&lt;/dd&gt;
&lt;/dl&gt;
&lt;a href="https://www.remarkbox.com" target="_blank"&gt;&lt;img src="https://linkpeek.com/api/v1?uri=https%3A//www.remarkbox.com&amp;apikey=9fhvyH9KP&amp;token=2312deb08af7a2e1e871790e170d5c60&amp;size=280x220&amp;viewport=" title="" align="" style="margin-left: 25px; float: right; border-radius: 8px;" class="" alt="" /&gt;&lt;/a&gt;&lt;p&gt;Recently I have contemplated the idea of building a screen capture
service for websites. I anticipate the service would provide
functionality similar to Google's &amp;quot;Instant Preview&amp;quot;. I also pondered the
idea of archiving the screen captures and providing a history similar to
the internet Wayback Machine.&lt;/p&gt;
&lt;p&gt;After floundering on my first web application,
&lt;a class="reference external" href="https://four2go.gumyum.com"&gt;https://four2go.gumyum.com&lt;/a&gt;, I'm afraid to spend lots of time and energy
on another project if I cannot earn users or customers.&lt;/p&gt;
&lt;a href="https://four2go.gumyum.com" target="_blank"&gt;&lt;img src="https://linkpeek.com/api/v1?uri=https%3A//four2go.gumyum.com&amp;apikey=9fhvyH9KP&amp;token=2ddfa66b9be03a5a72eb5e2f49805f72&amp;size=280x220&amp;viewport=" title="" align="" style="margin-right: 25px; float: left; border-radius: 8px;" class="" alt="" /&gt;&lt;/a&gt;&lt;p&gt;I feel the market segment for this type of service would include website
directories, live portfolio generation for website designers, and
websites in general for people who wish to keep track of the overall
look of their site over time.&lt;/p&gt;
&lt;p&gt;As an MVP I plan to build a free live web capture application to
basically provide a &amp;quot;Gravatar&amp;quot; for website addresses.&lt;/p&gt;
&lt;a href="https://linkpeek.com" target="_blank"&gt;&lt;img src="https://linkpeek.com/api/v1?uri=https%3A//linkpeek.com&amp;apikey=9fhvyH9KP&amp;token=e4c6810facf27f566e819d6656cafb35&amp;size=280x220&amp;viewport=" title="" align="" style="margin-left: 25px; float: right; border-radius: 8px;" class="" alt="" /&gt;&lt;/a&gt;&lt;p&gt;&lt;strong&gt;Do you think anyone would use my MVP or service?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LinkPeek provides an easy to use API which enables anyone to instantly
create live web page thumbnails. We provide the API free of charge,
think of LinkPeek as Gravatar for web addresses.&lt;/p&gt;
&lt;p&gt;Have I missed any other market segments?&lt;/p&gt;
</content><category term="misc"></category><category term="LinkPeek"></category><category term="Opinion"></category></entry><entry><title>New Baby Homecoming</title><link href="https://russell.ballestrini.net/new-baby-homecoming/" rel="alternate"></link><published>2011-09-27T16:53:00-04:00</published><updated>2011-09-27T16:53:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-09-27:/new-baby-homecoming/</id><summary type="html">&lt;p class="first last"&gt;family += 1&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/09/RJ-and-Carter.jpg" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Uncategorized"></category></entry><entry><title>Hacker Olive Oil Lamp Crafted From Home Materials</title><link href="https://russell.ballestrini.net/hacker-olive-oil-lamp-crafted-from-home-materials/" rel="alternate"></link><published>2011-09-09T19:01:00-04:00</published><updated>2011-09-09T19:01:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-09-09:/hacker-olive-oil-lamp-crafted-from-home-materials/</id><summary type="html">&lt;p class="first last"&gt;Really fast and clean design.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/09/hacker-olive-oil-lamp.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;In loom of the recent and persisting hurricane season I present a
&lt;strong&gt;&amp;quot;Hacker's Olive Oil Lamp&amp;quot;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YES, this lamp is ...&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Fun and simple to build&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fun and simple to use&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to Cleanup&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gentle on environment&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Energy efficient&lt;/strong&gt; (1 cup ~= 16 hours)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safe&lt;/strong&gt; (flame smothers if lamp tips over)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;NO&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Odor&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smoke&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heat near base&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt; (household materials)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waste&lt;/strong&gt; (Store and seal oil in lamp with jar top)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Materials&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;1x Pickle jar with lid&lt;/li&gt;
&lt;li&gt;1x Washcloth 100% cotton&lt;/li&gt;
&lt;li&gt;1x Steel wire (salvaged from paint can handle)&lt;/li&gt;
&lt;li&gt;2x Cups of olive oil&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Needle nose plyers&lt;/li&gt;
&lt;li&gt;Scissors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;center&gt;
&lt;iframe width="420" height="345" src="https://www.youtube.com/embed/3I1W2ddJaAs" frameborder="0" allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/center&gt;
&lt;/p&gt;&lt;p&gt;[gallery link=&amp;quot;file&amp;quot;]&lt;/p&gt;
</content><category term="misc"></category><category term="Art"></category><category term="Guide"></category><category term="Project"></category></entry><entry><title>A system administrators guide to installing and maintaining multiple python environments</title><link href="https://russell.ballestrini.net/a-system-administrators-guide-to-installing-and-maintaining-multiple-python-environments/" rel="alternate"></link><published>2011-09-08T19:28:00-04:00</published><updated>2011-09-08T19:28:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-09-08:/a-system-administrators-guide-to-installing-and-maintaining-multiple-python-environments/</id><summary type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-python-2-7" id="toc-entry-1"&gt;Install Python 2.7&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#compile-from-source" id="toc-entry-2"&gt;Compile from source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-package-from-scl" id="toc-entry-3"&gt;Install package from scl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#virtualenv" id="toc-entry-4"&gt;virtualenv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Some operating systems depend on a specific version of python to
function properly. For example, Yum on Redhat Enterprise Linux 5 (RHEL5)
depends on python 2.4.3. This version of python lacks support from many
utilities …&lt;/p&gt;</summary><content type="html">&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-python-2-7" id="toc-entry-1"&gt;Install Python 2.7&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#compile-from-source" id="toc-entry-2"&gt;Compile from source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-package-from-scl" id="toc-entry-3"&gt;Install package from scl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#virtualenv" id="toc-entry-4"&gt;virtualenv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Some operating systems depend on a specific version of python to
function properly. For example, Yum on Redhat Enterprise Linux 5 (RHEL5)
depends on python 2.4.3. This version of python lacks support from many
utilities and 3rd party libraries. This guide will cover installing an
alternative python instance while leaving the system's python alone.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This guide supports the following operating systems: Redhat, CentOS,
and Fedora. As of this publication the latest Python version was 2.7.8;
You might want to determine if a newer version exists.&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="install-python-2-7"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Install Python 2.7&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I found two methods which work for installing Python 2.7 side-by-side with the system's Python.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;compile from source&lt;/li&gt;
&lt;li&gt;install package via the &lt;em&gt;scl&lt;/em&gt; repo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Choose which ever strategy you find easier.&lt;/p&gt;
&lt;div class="section" id="compile-from-source"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Compile from source&lt;/a&gt;&lt;/h3&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Gather the dependencies:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
yum install gcc zlib-devel python-setuptools readline-devel
&lt;/pre&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;gcc&lt;/strong&gt; is a compiler used to build python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;zlib-devel&lt;/strong&gt; allows the python zlib module to be built&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;python-setuptools&lt;/strong&gt; provides the easy_install application&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;readline-devel&lt;/strong&gt; arrows readline and history handling in python shell&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Download and untar the python sourcecode:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
wget https://www.python.org/ftp/python/2.7.8/Python-2.7.8.tgz
tar -xzvf Python-2.7.8.tgz
cd Python-2.7.8
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Compile the sourcecode:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
./configure
make altinstall
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Test new alternative python:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python2.7 --version
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Document path to new python2.7, you will need it if you plan to use a virtualenv:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
which python2.7
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Now we can install third party libraries into our alternative python:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
python2.7 -m easy_install
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="install-package-from-scl"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Install package from scl&lt;/a&gt;&lt;/h3&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;install centos-release-scl repolists:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo yum install centos-release-scl
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;install python27:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo yum install python27
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;enable python27 via scl:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
scl enable python27 bash
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Document path to new python2.7, you will need it if you plan to use a virtualenv:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
which python2.7
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="virtualenv"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;virtualenv&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Optionally we can create a virtualenv (for development) based on the
python 2.7 install. Virtual environments appear useful for testing
packages and libraries without installing them to the system owned
python site-packages directory.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Install pip using easy_install:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo easy_install pip
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Install virtualenv using pip:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo pip install virtualenv
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Create a new virtual python environment named virtpy:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
cd ~
virtualenv -p /usr/local/bin/python2.7 virtpy
&lt;/pre&gt;
&lt;p&gt;This will create a virtual python 2.7.2 environment named virtpy in your present working directory.&lt;/p&gt;
&lt;p&gt;To invoke this environment run source virtpy/bin/activate and your prompt should change to reflect the active virtualenv.&lt;/p&gt;
&lt;p&gt;Now you can run easy_install to install packages into virtpy/lib/python2.7/site-packages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading, that's all for now.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="DevOps"></category><category term="Guide"></category></entry><entry><title>Deliberating the Viewers vs. Doers concept</title><link href="https://russell.ballestrini.net/deliberating-the-viewers-vs-doers-concept/" rel="alternate"></link><published>2011-09-06T10:20:00-04:00</published><updated>2011-09-06T10:20:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-09-06:/deliberating-the-viewers-vs-doers-concept/</id><summary type="html">&lt;p class="first last"&gt;America has succumbed to &amp;quot;spectatoritis&amp;quot;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="Viewers vs Doers Lecture" src="/uploads/2011/09/lecture-viewers-vs-doers.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://artofmanliness.com/2011/08/28/viewers-vs-doers-the-rise-of-spectatoritis/"&gt;Brett &amp;amp; Kate McKay&lt;/a&gt; from artofmanliness.com recently subscribed to an idea that America has succumbed to
&amp;quot;spectatoritis, a blanket description to cover all kinds of passive
amusement, an entering into the handiest activity merely to escape
boredom.&amp;quot; -Jay B. Nash, 1938&lt;/p&gt;
&lt;p&gt;The basic concept discussed how people can fall into one of two groups
when interacting in life, the viewers and the doers. Viewers or
spectators watch an activity while doers perform the said activity. For
example:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Viewers&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Sports Fans&lt;/li&gt;
&lt;li&gt;Audience&lt;/li&gt;
&lt;li&gt;Couch Potatoes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Doers&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Athletes&lt;/li&gt;
&lt;li&gt;Musicians&lt;/li&gt;
&lt;li&gt;Actors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although the above patterns persists in the physical world, intellectual
situations operate differently. For instance on the surface reading
seems like a viewer or spectator task and writing appears to behave like
a doer task. But what happens when a reader learns or responds to a blog
post? We seem to need a stronger definition what constitutes doing a
task.&lt;/p&gt;
&lt;blockquote&gt;
We should not regard spectating as bad, however we should try to
stay lucid of our current rolls when engaging in activities.&lt;/blockquote&gt;
&lt;p&gt;Spectating has some important purposes in our society and culture.
Viewing a performance grants power to the actor, whether that person
plays football or plays guitar. For this power and influence to occur a
viewer must witness and pay attention to the event. What if the next
great political leader took the podium and no one bothered to view his
speech?&lt;/p&gt;
&lt;p&gt;The truth is our American society has conditioned us to spend much of
our lives spectating. During school a good student will view and listen
to the lecture. While driving to work one radio DJ broadcasts his
thoughts to many.&lt;/p&gt;
&lt;p&gt;Spectating also plays and integral roll in learning. Human infants learn
by first watching and then imitating. Experts also agree that
inspiration for a creative works often occur after observation of prior
productions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; We should not battle the viewers vs the doers because both
hold importance and need each other.&lt;/p&gt;
&lt;p&gt;If you liked this article, you should follow me &lt;a class="reference external" href="https://plus.google.com/101342467879466559261/posts"&gt;here&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Python Image Grabber pig.py</title><link href="https://russell.ballestrini.net/python-image-grabber-pig-py/" rel="alternate"></link><published>2011-08-22T18:48:00-04:00</published><updated>2011-08-22T18:48:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-08-22:/python-image-grabber-pig-py/</id><summary type="html">&lt;p class="first last"&gt;Download all the images from a URI with this simple tool!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="pig.py" class="wordwrap-left" src="/uploads/2011/08/pig1-color1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python Image Grabber pig.py&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Python Image Grabber or pig.py is a &lt;em&gt;very&lt;/em&gt; simple python command line
tool to download all the images from a given uri.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download and Installation:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/pig/raw/tip/pig.py"&gt;https://bitbucket.org/russellballestrini/pig/raw/tip/pig.py&lt;/a&gt;&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Usage:&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;tt class="docutils literal"&gt;python pig.py&lt;/tt&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;tt class="docutils literal"&gt;python pig.py &lt;span class="pre"&gt;https://www.foxhop.net&lt;/span&gt;&lt;/tt&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Open Source:&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/pig/raw/tip/pig.py"&gt;pig.py&lt;/a&gt; has been placed in the public domain and its sourcecode may be viewed or branched here here: &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/pig"&gt;https://bitbucket.org/russellballestrini/pig&lt;/a&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;strong&gt;Pigpy Gimp Project&lt;/strong&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="/uploads/2011/08/pigpy.xcf"&gt;pigpy.xcf&lt;/a&gt;&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category><category term="Project"></category></entry><entry><title>Security Professionals: Yes we appear vulnerable but that attack vector will never happen</title><link href="https://russell.ballestrini.net/security-professionals-yes-we-appear-vulnerable-but-that-attack-vector-will-never-happen/" rel="alternate"></link><published>2011-08-18T21:36:00-04:00</published><updated>2011-08-18T21:36:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-08-18:/security-professionals-yes-we-appear-vulnerable-but-that-attack-vector-will-never-happen/</id><summary type="html">&lt;p&gt;In loom of recent internet attacks many institutions have started
scrambling in attempt to &amp;quot;strengthen&amp;quot; their security stance. I agree
that auditing our systems and networks for potential flaws seems
appropriate at this time to prevent getting &amp;quot;caught with our pants
down&amp;quot;. Incidentally, I have recently witnessed the introduction of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In loom of recent internet attacks many institutions have started
scrambling in attempt to &amp;quot;strengthen&amp;quot; their security stance. I agree
that auditing our systems and networks for potential flaws seems
appropriate at this time to prevent getting &amp;quot;caught with our pants
down&amp;quot;. Incidentally, I have recently witnessed the introduction of silly
and at times ineffective security adjustments. Many of these new
procedures, rules, and requirements do not make us more secure and worse
instill a false sense of security.&lt;/p&gt;
&lt;p&gt;I have previously addressed the fallacy of absolute security. No system
is perfect. A successful security model accomplishes fortitude by
implementing layers like an onion. Through the use of security
layers we can significantly hamper attack vectors and create a safer
complex.&lt;/p&gt;
&lt;p&gt;When analyzing a potential attack vector we must first determine our
current location in the security layers. This step serves two purposes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;to prevent wasting time and energy on vulnerabilities that don't
matter at that point in our matrix.&lt;/li&gt;
&lt;li&gt;to prevent causing outages and unneeded administrator and customer
heartache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a vulnerability requires root or elevated privileges to occur, don't
waste your time resolving it. If the attacker already has root, you have
bigger problems on your hands.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some real life examples:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Firewall denying a large range of IP addresses (like entire
countries). This truly does not increase security, it just creates
headaches for users. An attacker could just proxy to an open range
(like a VPS based in a more trusted zone) and gain access from there.
Also if you decide to ignore this advice and create blanket IP range
deny rules, DON'T also block services intended to be internet-facing.
For example, don't block your Internet-facing DNS server if it is
authoritative for public domains. This will cause countless
intermittent issues and will be a nightmare to diagnose.&lt;/li&gt;
&lt;li&gt;Weekly Scanning for Windows viruses on network shares or data at
rest. This hammers the servers for no reason. If all the desktops run
antivirus then the file was already scanned when it was downloaded.
That same file will be scanned again when retrieved on the share. If
you want the warm and fuzzys of virus scanning network file shares,
do it once a year. These scans waste time and resources. I feel even
more outraged when asked to virus scan network shares hosted on UNIX
servers or NAS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I speculate that most of these arbitrary ideas come about because the
people in charge make uninformed decisions out of fear without first
consulting the appropriate subject matter experts.&lt;/p&gt;
&lt;p&gt;Unfortunately, once a security mandate occurs it seems difficult to
expunge. People are just not willing to put their neck on the chopping
block to banish a legacy or silly mandates; So we end up living with
nonsensical rules and procedures.&lt;/p&gt;
&lt;p&gt;&lt;img alt="https://xkcd.com/936/" src="/uploads/2011/08/password-strength.png" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category><category term="Security"></category></entry><entry><title>virt-back: restoring from backups</title><link href="https://russell.ballestrini.net/virt-back-restoring-from-backups/" rel="alternate"></link><published>2011-08-10T23:20:00-04:00</published><updated>2011-08-10T23:20:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-08-10:/virt-back-restoring-from-backups/</id><summary type="html">&lt;p&gt;&lt;strong&gt;In a perfect world we should create backups but never need them.&lt;/strong&gt;
Although this statement holds truth, creating guest backups provides
many more benefits.&lt;/p&gt;
&lt;p&gt;The most common reasons system administrators restore from a virt-back
guest backup:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;recovering from data corruption&lt;/li&gt;
&lt;li&gt;recovering deleted files&lt;/li&gt;
&lt;li&gt;recovering from a virus infection&lt;/li&gt;
&lt;li&gt;recovering from …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;In a perfect world we should create backups but never need them.&lt;/strong&gt;
Although this statement holds truth, creating guest backups provides
many more benefits.&lt;/p&gt;
&lt;p&gt;The most common reasons system administrators restore from a virt-back
guest backup:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;recovering from data corruption&lt;/li&gt;
&lt;li&gt;recovering deleted files&lt;/li&gt;
&lt;li&gt;recovering from a virus infection&lt;/li&gt;
&lt;li&gt;recovering from a compromised server&lt;/li&gt;
&lt;li&gt;backing out a failed change&lt;/li&gt;
&lt;li&gt;rolling back to a previous state&lt;/li&gt;
&lt;li&gt;testing disaster recovery plans&lt;/li&gt;
&lt;li&gt;cloning a server&lt;/li&gt;
&lt;li&gt;building test environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During this article we will cover how to restore a system from a
virt-back guest backup. This article will not cover how to restore a VM
host server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Virt-back guest restore procedure&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this guide our guest mbison has failed with a major corruption and we
would like to restore from our backups. We have our running production
guest images in /KVMROOT and our virt-back guest backups in /KVMBACK. We
will be restoring the backup on the same hypervisor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overview:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Ensure the guest is shut off.&lt;/li&gt;
&lt;li&gt;move the bad image file out of the way&lt;/li&gt;
&lt;li&gt;untar the virt-back backup into place&lt;/li&gt;
&lt;li&gt;power up the guest&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Detailed Procedure:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Verify the guest is shut off by running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-back --info-all
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;We noticed that mbison was still running so we invoked:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-back --shutdown mbison
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Move the corrupted image file out of the way:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
mv /KVMROOT/mbison.img /KVMROOT/mbison.img.NFG
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Unzip and unarchive the backup using the following command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo tar -xzvf /KVMBACK/mbison.tar.gz -C /KVMROOT --strip 1
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;When the untar completes, start the guest:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
virt-back --create mbison
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Connect to the guest over SSH and verify that all required services
and applications start. Determine if the restore was successful.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Restore guest backup on new hypervisor:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The details in this section were adapted from a tutorial given by
&lt;a class="reference external" href="http://fabianrodriguez.com/"&gt;Fabian Rodriguez&lt;/a&gt;.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Re-create any bridge network interfaces on new hypervisor
(/etc/network/interfaces for Debian)&lt;/li&gt;
&lt;li&gt;Adjust mbison.xml if needed (for example if you are changing paths)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="literal-block"&gt;
sudo mkdir /KVMROOT
sudo tar -xvzf mbison.tar.gz -C /KVMROOT --strip 1
virsh create /KVMROOT/mbison.xml
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We use virsh create instead of virt-back create. While both
commands start guest DOMs, virsh create will also register the DOM into
the hypervisor.&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category><category term="Guide"></category><category term="Project"></category></entry><entry><title>Programming is like Alchemy</title><link href="https://russell.ballestrini.net/programming-is-like-alchemy/" rel="alternate"></link><published>2011-08-08T21:52:00-04:00</published><updated>2011-08-08T21:52:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-08-08:/programming-is-like-alchemy/</id><summary type="html">&lt;p class="first last"&gt;except instead of exchanging matter, we programmers exchange time.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="This image comes from Michael Maier's 1618 treatise on alchemy. It combines music, image and text to communicate alchemical knowledge to adepts. Note the combination of Pythagorean imagery with alchemical practice." class="wordwrap-right" src="/uploads/2011/08/Alchemy_2.jpg" style="width: 270px;" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Programming is like Alchemy&lt;/strong&gt; except instead of exchanging matter, we
programmers exchange time. Also depending on the program the exchange of
time worked (coding) increases the productivity (time) of its users.&lt;/p&gt;
&lt;p&gt;On second thought, perhaps programmers correlate less with Alchemists
and more with Time Travelers; Or at the very least time manipulators.
For example, on a good day a programmer can easily complete a task that
would take one thousand men. See, time created! On a bad week we can
procrastinate and do nothing at all. Time lost!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Golem-soul-caliber" class="wordwrap-left" src="/uploads/2011/08/Golem-soul-caliber1.jpg" style="width: 220px;" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Programming embodies other magic like wizardry.&lt;/strong&gt; For example,
our programs typically live as golems performing one task,
repeatedly, over and over. Golem programs, without a soul, stuck in a
loop of servitude.&lt;/p&gt;
&lt;p&gt;Recently we have started coding creations with artificial intelligence.
These smart programs act like familiar spirits (Wikipedia) and assist
their creator in conjuring even more magic.&lt;/p&gt;
&lt;p&gt;So we settled it! Programmers are like bad ass, time travelling, wizard
alchemists!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Or maybe not ...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is more accurate to group programs with technology then magic, but
less fun. Programs are leveraged tools born to save people time and energy.&lt;/p&gt;
&lt;p&gt;Thanks for reading, you should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="time traveling wizard alchemist" src="/uploads/2011/08/time-travelling-wizard-alchemists.jpg" /&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category></entry><entry><title>The Great Gist Heist</title><link href="https://russell.ballestrini.net/the-great-gist-heist/" rel="alternate"></link><published>2011-08-07T02:30:00-04:00</published><updated>2011-08-07T02:30:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-08-07:/the-great-gist-heist/</id><summary type="html">&lt;p class="first last"&gt;Please listen to my story before jumping to conclusions.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;The Great Gist Heist&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have crawled, downloaded, and archived all of gist.github.com. Please
hear my story before jumping to conclusions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm currently building software that requires a large corpus of source
code.&lt;/p&gt;
&lt;p&gt;I began to search for a collection of source code documents but my
pursuit appeared fruitless. Feeling displeased I attempted to gather all
of my own source code. My collection lacked fidelity perhaps because of
my revere for the python language.&lt;/p&gt;
&lt;p&gt;Regardless of the reasoning, I needed a higher quantity of samples. I
needed unbiased samples from all programming languages. I needed, most
importantly, samples in a variation of quality that only the most
popular paste sites have... sites like gist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why are you sharing it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I feel a little bad about using Github's bandwidth.&lt;/p&gt;
&lt;p&gt;Sharing this collection should reduce the chances that others will crawl
for the same data. If you need a large collection of source code,
&lt;a class="reference external" href="/uploads/2011/08/the-great-gist-heist.torrent"&gt;download this torrent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How did you do it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wrote a short, 30 line, python script. The script is part of the
torrent.&lt;/p&gt;
&lt;p&gt;At the peak of the scrape I had 14 threads running of the script, using
approximately 580Kbps (I used iftop).&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Project"></category></entry><entry><title>a hack to gain 80 percent efficiency when creating github projects</title><link href="https://russell.ballestrini.net/a-hack-to-gain-80-percent-efficiency-when-creating-github-projects/" rel="alternate"></link><published>2011-07-11T21:47:00-04:00</published><updated>2011-07-11T21:47:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-07-11:/a-hack-to-gain-80-percent-efficiency-when-creating-github-projects/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/07/80-percent-github-hack.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last night I decided to learn how to use git for its popularity and
github to code more socially.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have to admit early on that I enjoy hg and bitbucket so it came to a
surprise that github would have me jump through hoops to create a new
repository...&lt;/p&gt;
&lt;p&gt;Below I have copied the seemingly bloated github instructions for
creating a new project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;create project/repo using the form&lt;/li&gt;
&lt;li&gt;mkdir scratch&lt;/li&gt;
&lt;li&gt;cd scratch&lt;/li&gt;
&lt;li&gt;git init&lt;/li&gt;
&lt;li&gt;touch README&lt;/li&gt;
&lt;li&gt;git add README&lt;/li&gt;
&lt;li&gt;git commit -m 'first commit'&lt;/li&gt;
&lt;li&gt;git remote add origin&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="mailto:git&amp;#64;github.com"&gt;git&amp;#64;github.com&lt;/a&gt;:russellballestrini/scratch.git&lt;/li&gt;
&lt;li&gt;git push -u origin master&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Wow...&lt;/p&gt;
&lt;p&gt;I like my method better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My Github clone hack:&lt;/strong&gt;&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;create project/repo using the form&lt;/li&gt;
&lt;li&gt;git clone
&lt;a class="reference external" href="https://russellballestrini&amp;#64;github.com/russellballestrini/scratch.git"&gt;https://russellballestrini&amp;#64;github.com/russellballestrini/scratch.git&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Awesome, 2 steps versus 10. That hack appears 80% more efficient! Now if
only my name was shorter... LOL&lt;/p&gt;
&lt;p&gt;You should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category></entry><entry><title>Add a Breadcrumb Subscriber to a Pyramid project using 4 simple steps</title><link href="https://russell.ballestrini.net/add-a-breadcrumb-subscriber-to-a-pyramid-project-using-4-simple-steps/" rel="alternate"></link><published>2011-07-02T17:25:00-04:00</published><updated>2011-07-02T17:25:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-07-02:/add-a-breadcrumb-subscriber-to-a-pyramid-project-using-4-simple-steps/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post shows an old way to modify the request object.
I discuss a new way in my &lt;a class="reference external" href="/register-super-powers-with-pyramid-add-request-method/"&gt;Pyramid add_request_method&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;This article will explain how to add a breadcrumb subscriber to a
Pyramid project using 4 simple steps.&lt;/p&gt;
&lt;p&gt;While programming &lt;a class="reference external" href="https://school.yohdah.com/"&gt;https://school.yohdah.com/&lt;/a&gt; I needed the ability to easily create
breadcrumb links from the current url. You may view the &lt;a class="reference external" href="https://bitbucket.org/russellballestrini/bread/src/tip/bread.py"&gt;bread.py source
code
here&lt;/a&gt;.
The following guide describes the process I took to add this
functionality.&lt;/p&gt;
&lt;p&gt;1. Download and include
&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/bread/raw/tip/bread.py"&gt;bread.py&lt;/a&gt;
at the top of your Pyramid project's __init__.py file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
from yourproject.bread import Bread
from pyramid.events import subscriber, NewRequest
&lt;/pre&gt;
&lt;p&gt;2. At the bottom of the projects __init__.py file create the
following subscriber function as follows:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
def bread_subscriber( event ):
    &amp;quot;&amp;quot;&amp;quot; Build Bread object and add to request &amp;quot;&amp;quot;&amp;quot;
    event.request.bread = Bread( event.request.url )
&lt;/pre&gt;
&lt;p&gt;3. Now we can add our new subscriber to our Pyramid config inside
main and above the routes:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
config.add_subscriber(bread_subscriber, NewRequest)
&lt;/pre&gt;
&lt;p&gt;4. You are done! Now you should have the ability to use the bread
object from your template. Below I have provided a mako template
snippet:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
% for link in request.bread.links:
  ${ link | n } /
% endfor
&lt;/pre&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Guide"></category></entry><entry><title>Google Bot Attempts to Crawl Shortest Urls First</title><link href="https://russell.ballestrini.net/google-bot-attempts-to-crawl-shortest-urls-first/" rel="alternate"></link><published>2011-06-25T09:37:00-04:00</published><updated>2011-06-25T09:37:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-06-25:/google-bot-attempts-to-crawl-shortest-urls-first/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;Recently I built https://school.yohdah.com a Python, Pyramid, and
mongoDB project during the last couple weekends.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="https://school.yohdah.com"&gt;&lt;img alt="school.yohdah.com" class="wordwrap-left" src="/uploads/2011/06/us-public-schools1.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The site features a directory style navigation of nearly every public
school in the US. We have 61 state pages, approximately 19,000 city
pages, and over 103,000 school pages.&lt;/p&gt;
&lt;p&gt;It seems the Google Bots have noticed
&lt;a class="reference external" href="https://school.yohdah.com"&gt;school.yohdah.com&lt;/a&gt; and started crawling
the site. Since the initial crawl I started reviewing a sample of the
sites apache logs in an attempt to track the bot's activity. After a few
minutes of viewing the logs, I locked onto a pattern; Google Bot's
algorithm appears to crawl the short URLs first!&lt;/p&gt;
&lt;p&gt;PersonalCompute (a user) attached a graph of the fetched URL lengths
here:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2011/06/school.yohdah.com_.graph_.png"&gt;&lt;img alt="school.yohdah.com.graph" src="/uploads/2011/06/school.yohdah.com_.graph_.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have attached a zip containing the apache google bot crawl logs
here:
&lt;a class="reference external" href="/uploads/2011/06/access-school.yohdah.log_.zip"&gt;access-school.yohdah.log.zip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I found the pattern by opening the file in vim and scrolling very
quickly down. You will notice the log lines will grow slowly to the
right, as the urls being fetched increase by one character.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why does Google do this? Does anyone have speculation as to what this
means?&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Greatest Hits"></category><category term="Opinion"></category><category term="Project"></category></entry><entry><title>ATT Uverse Residential Gateway Broadband LED flashes red intermittently</title><link href="https://russell.ballestrini.net/att-uverse-residential-gateway-broadband-led-flashes-red-intermittently/" rel="alternate"></link><published>2011-06-18T10:08:00-04:00</published><updated>2011-06-18T10:08:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-06-18:/att-uverse-residential-gateway-broadband-led-flashes-red-intermittently/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;ATT Uverse Residential Gateway Broadband LED flashes red
intermittently&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have struggled with my uverse Internet and TV service randomly and
intermittently shutting off for the last 3 months. When this occurs the
Residential Gateway's broadband LED flashes red.&lt;/p&gt;
&lt;p&gt;Also I get Uverse DSL link errors:&lt;/p&gt;
&lt;p&gt;
&lt;center&gt;&lt;p&gt;&lt;img alt="uverse link errors" src="/uploads/2011/06/uverse-dsl-link-errors.jpg" /&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;/p&gt;&lt;p&gt;When I 'live support chatted' with the uverse technical support they
claimed I had bridge tap on my lines, and that my lines had other
'issues'.&lt;/p&gt;
&lt;p&gt;They had to dispatch 3 technicians before one of them figured out the
problem. It turns out I had a faulty 'voice/data' splitter.&lt;/p&gt;
&lt;p&gt;I snapped a picture of the demarcation box before and after he replaced
the faulty splitter.&lt;/p&gt;
&lt;p&gt;
&lt;center&gt;&lt;p&gt;&lt;img alt="old uverse splitter" src="/uploads/2011/06/old-splitter.jpg" /&gt;&lt;img alt="new uverse splitter" src="/uploads/2011/06/new-splitter.jpg" /&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;center&gt;&lt;p&gt;&lt;em&gt;Old Uverse filter on the left, New filter on the right&lt;/em&gt;&lt;/p&gt;
&lt;center&gt;
&lt;/p&gt;&lt;p&gt;&lt;strong&gt;So far I have had no Uverse DSL Link Errors!&lt;/strong&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Connecticut killed affliate marketing with Amazon.com</title><link href="https://russell.ballestrini.net/connecticut-killed-affliate-marketing-with-amazon-com/" rel="alternate"></link><published>2011-06-11T14:10:00-04:00</published><updated>2011-06-11T14:10:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-06-11:/connecticut-killed-affliate-marketing-with-amazon-com/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;Connecticut killed affliate marketing with Amazon.com&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I just opened my amazon account and read this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Account Closed&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;This account is closed and will not generate referrals. Access to
this site is for historical purposes only.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;When I checked my email, I had this letter from Amazon.com:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hello,&lt;/p&gt;
&lt;p&gt;For well over a decade, the Amazon Associates Program has worked
with thousands of Connecticut residents. &lt;strong&gt;Unfortunately, the budget
signed by Governor Malloy contains a sales tax provision that
compels us to terminate this program for Connecticut-based
participants effective immediately.&lt;/strong&gt; It specifically imposes the
collection of taxes from consumers on sales by online retailers -
including but not limited to those referred by Connecticut-based
affiliates like you - even if those retailers have no physical
presence in the state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We opposed this new tax law because it is unconstitutional and
counterproductive. It was supported by big-box retailers, most of
which are based outside Connecticut, that seek to harm the affiliate
advertising programs of their competitors.&lt;/strong&gt; Similar legislation in
other states has led to job and income losses, and little, if any,
new tax revenue. We deeply regret that we must take this action.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As a result of the new law, contracts with all Connecticut
residents participating in the Amazon Associates Program will be
terminated today, June 10, 2011.&lt;/strong&gt; Those Connecticut residents will
no longer receive advertising fees for sales referred to Amazon.com
, Endless.com , MYHABIT.COM or SmallParts.com . Please be assured
that all qualifying advertising fees earned on or before today, June
10, 2011, will be processed and paid in full in accordance with the
regular payment schedule.&lt;/p&gt;
&lt;p&gt;You are receiving this email because our records indicate that you
are a resident of Connecticut. If you are not currently a resident
of Connecticut, or if you are relocating to another state in the
near future, you can manage the details of your Associates account
here . And if you relocate to another state after June 10, 2011,
please contact us for reinstatement into the Amazon Associates
Program.&lt;/p&gt;
&lt;p&gt;To avoid confusion, we would like to clarify that this development
will only impact our ability to offer the Associates Program to
Connecticut residents and will not affect their ability to purchase
from www.amazon.com .&lt;/p&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;We have enjoyed working with you and other Connecticut-based
participants in the Amazon Associates Program and, if this
situation is rectified, would very much welcome the opportunity to
re-open our Associates Program to Connecticut residents.&lt;/div&gt;
&lt;div class="line-block"&gt;
&lt;div class="line"&gt;Regards,&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Amazon Associates Team&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Amazon.com does not have a facility in CT.&lt;/li&gt;
&lt;li&gt;CT passed a new bill.&lt;/li&gt;
&lt;li&gt;The bill declares: if a customer from any state clicks on a CT
resident's affiliate link and purchases an item, that
customer/Amazon.com should have to pay CT tax.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;My Opinion&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;This seems bad.&lt;/li&gt;
&lt;li&gt;I have less money to spend in Connecticut.&lt;/li&gt;
&lt;li&gt;This smells like double taxation.&lt;/li&gt;
&lt;li&gt;One more reason to move out of Connecticut when looking for Tech or
IT jobs.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category><category term="Opinion"></category></entry><entry><title>Dropbox Encryption with TrueCrypt</title><link href="https://russell.ballestrini.net/dropbox-encryption-with-truecrypt/" rel="alternate"></link><published>2011-04-16T12:50:00-04:00</published><updated>2011-04-16T12:50:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-04-16:/dropbox-encryption-with-truecrypt/</id><summary type="html">&lt;p class="first last"&gt;The best security acts like an onion.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://dereknewton.com/2011/04/dropbox-authentication-static-host-ids/"&gt;Derek Newton&lt;/a&gt;
recently invoked discussion about insecurities in Dropbox authentication.
In his article he describes how an attacker could exploit Dropbox and gain access to unshared files.
The concerns he raised do appear accurate however we must remember that security is an onion.&lt;/p&gt;
&lt;p&gt;&lt;img alt="onion" src="/uploads/2011/04/onion.png" /&gt;&lt;/p&gt;
&lt;p&gt;An onion, like security, has layers to protect its vital parts.
The vital parts are more vulnerable when its security model only possess one layer.
As we add layers to our security model, our system's protection grows exponentially.&lt;/p&gt;
&lt;p&gt;In the case of Dropbox, the username and password act as the first
layer. Experts agree that a simple authentication layer will provide
enough protection for nonsensitive data. However when attempting to
protect sensitive data we must pair authorization with encryption.&lt;/p&gt;
&lt;p&gt;Generally speaking file systems have maintained a sense of insecurity,
which makes them useful. Not encrypting files on Dropbox is akin to not
encrypting files on a shared PC. Sensitive data should always be
encrypted &lt;em&gt;regardless&lt;/em&gt; of its location or media. We should treat
sensitive data-at-rest on Dropbox the same way we treat sensitive data
on local, optical or flash disk. &lt;em&gt;We should encrypt it!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how does a user encrypt their Dropbox?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My strongly opinionated solution uses TrueCrypt to create an encrypted
volume in the Dropbox directory. Simply treat the Dropbox like a normal
directory, follow the TrueCrypt documentation to build a volume, and
give Dropbox a chance to sync the data. When the sync completes, the
TrueCrypt volume will be mountable on each of your Dropbox enabled
computers.&lt;/p&gt;
&lt;p&gt;I have to admit at first I was skeptical, but the software cooperates
surprisingly well and after the initial sync proceeding syncs occur
quickly! I prefer TrueCrypt because it is open source, cross platform,
and free (both in freedom and cost). TrueCrypt also functions and
performs better then any other solution including commercial products
like GuardianEdge or PGP both recently acquired by Symantec.&lt;/p&gt;
&lt;p&gt;All security and encryption software should remain open sourced and peer
reviewed to prevent harmful tampering. Commercial software, written in a
black-box vacuum, prevents customers from viewing its code and
procedures. We cannot trust software for security when we cannot view
its source code.&lt;/p&gt;
&lt;p&gt;You should follow me on twitter
&lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Greatest Hits"></category><category term="Opinion"></category><category term="Security"></category></entry><entry><title>Voice Over IP with TeamSpeak</title><link href="https://russell.ballestrini.net/voice-over-ip-with-teamspeak/" rel="alternate"></link><published>2011-04-03T12:11:00-04:00</published><updated>2011-04-03T12:11:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-04-03:/voice-over-ip-with-teamspeak/</id><summary type="html">&lt;p&gt;&lt;strong&gt;This article will cover running a Voice Over IP service like TeamSpeak
on a VPS.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voice Over IP allows users to communicate using audio over the Internet.&lt;/p&gt;
&lt;p&gt;When planning for this article I originally was going to cover ventrilo,
but their download link was obfuscated behind a heinous php session …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;This article will cover running a Voice Over IP service like TeamSpeak
on a VPS.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voice Over IP allows users to communicate using audio over the Internet.&lt;/p&gt;
&lt;p&gt;When planning for this article I originally was going to cover ventrilo,
but their download link was obfuscated behind a heinous php session
script. Ventrilo also does not have a Linux client although they have
been promising one for quite some time.&lt;/p&gt;
&lt;p&gt;Instead we will cover how to install and configure
&lt;a class="reference external" href="https://www.teamspeak.com/en/"&gt;TeamSpeak&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installing a teamspeak server on your VPS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a user to run teamspeak.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo adduser teamspeak
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;follow the prompts&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Personally I don't want the teamspeak user to have ssh access so I added
the following to /etc/ssh/sshd_config:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
DenyUsers teamspeak
&lt;/pre&gt;
&lt;p&gt;Then reload ssh server config:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo service ssh reload
&lt;/pre&gt;
&lt;p&gt;Setup the installation environment.&lt;/p&gt;
&lt;p&gt;The following 4 commands will create a directory to hold your
installation, change the ownership of the directory to the teamspeak
user, change the working directory to the new folder and then become the
teamspeak user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo mkdir /opt/teamspeak
sudo chown teamspeak:teamspeak /opt/teamspeak
cd /opt/teamspeak
sudo su teamspeak
&lt;/pre&gt;
&lt;p&gt;Download and extract TeamSpeak server software.&lt;/p&gt;
&lt;p&gt;Find the proper package for your VPS and download it, in my case I ran:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
wget https://teamspeak.gameserver.gamed.de/ts3/releases/beta-30/teamspeak3-server_linux-amd64-3.0.0-beta30.tar.gz
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;For best results download the latest version of teamspeak.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A teamspeak tarball should now exist in your present working directory.
We can extract the files from the tarball by issuing the following
command:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
tar xvf teamspeak3-server_linux-amd64-3.0.0-beta30.tar.gz --strip-components=1
&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;No you don't have to type that file name out! The bash shell has tab
completion, type 'tar xvf teamsp' and then press tab. : )&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Install the TeamSpeak server software.&lt;/p&gt;
&lt;p&gt;I had success running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
./ts3server_startscript.sh start
&lt;/pre&gt;
&lt;p&gt;Write down the auto generated serveradmin password.&lt;/p&gt;
&lt;p&gt;Configure TeamSpeak to start at system bootup.&lt;/p&gt;
&lt;p&gt;Create a cronjob under the teamspeak user:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
crontab -e
&lt;/pre&gt;
&lt;p&gt;Place the following into teamspeak's crontab:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
&amp;#64;reboot /opt/teamspeak/ts3server_startscript.sh start
&lt;/pre&gt;
</content><category term="misc"></category><category term="Guide"></category></entry><entry><title>Porting the ChaosTheory Wordpress theme to Pylowiki</title><link href="https://russell.ballestrini.net/porting-the-chaostheory-wordpress-theme-to-pylowiki/" rel="alternate"></link><published>2011-03-26T22:00:00-04:00</published><updated>2011-03-26T22:00:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-03-26:/porting-the-chaostheory-wordpress-theme-to-pylowiki/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;strong&gt;As you may have noticed, I recently switched this blog over to the
ChaosTheory Wordpress theme.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I had to tweak some of my pre-existing articles to make them look
'right' but upon completion I was very pleased with the aesthetics of
each page.&lt;/p&gt;
&lt;p&gt;Later on I reviewed the theme of foxhop.net, my pylowiki demo site.
Pylowiki felt bland in comparison to this wordpress blog.&lt;/p&gt;
&lt;p&gt;So, I made up my mind to build themes for Pylowik, and I started by
porting ChaosTheory. You can view the finished product at
&lt;a class="reference external" href="https://www.foxhop.net"&gt;https://www.foxhop.net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You should also give Pylowiki a try. Pylowiki has futuristic, live
preview, same page, section edit functionality.&lt;/p&gt;
</content><category term="misc"></category><category term="Code"></category><category term="Project"></category></entry><entry><title>virt-back: a python libvirt backup utility for kvm xen virtualbox</title><link href="https://russell.ballestrini.net/virt-back-a-python-libvirt-backup-utility-for-kvm-xen-virtualbox/" rel="alternate"></link><published>2011-03-20T11:32:00-04:00</published><updated>2011-03-20T11:32:00-04:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-03-20:/virt-back-a-python-libvirt-backup-utility-for-kvm-xen-virtualbox/</id><summary type="html">&lt;p class="first last"&gt;backup your virtual maching guests.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;img alt="image0" src="/uploads/2011/03/virt-back.png" /&gt;&lt;/p&gt;
&lt;div class="contents topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#installation" id="toc-entry-1"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#test-installation" id="toc-entry-2"&gt;Test installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#example-cronjob" id="toc-entry-3"&gt;Example cronjob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#manual" id="toc-entry-4"&gt;Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#restoring" id="toc-entry-5"&gt;Restoring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Over the weekend I wrote virt-back, a backup utility for QEMU, KVM,
XEN, or Virtualbox guests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;virt-back is a python application that uses the libvirt API to safely
shutdown, gzip, and restart guests.&lt;/p&gt;
&lt;p&gt;The backup process logs to syslog for auditing and virt-back works great
with cron for scheduling outages. Virt-back is in active development so
feel free to give suggestions or branch the source.&lt;/p&gt;
&lt;p&gt;virt-back has been placed in the public domain and the latest version
may be downloaded here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/virt-back"&gt;https://bitbucket.org/russellballestrini/virt-back&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="installation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The fastest way to install virt-back is to use pip or setuptools.&lt;/p&gt;
&lt;p&gt;Try &lt;cite&gt;sudo pip install virt-back&lt;/cite&gt; or &lt;cite&gt;sudo easy_install virt-back&lt;/cite&gt;&lt;/p&gt;
&lt;p&gt;Otherwise you may manually install virt-back&lt;/p&gt;
&lt;pre class="literal-block"&gt;
sudo wget https://bitbucket.org/russellballestrini/virt-back/raw/tip/virt-back \
-O  /usr/local/bin/virt-back
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
sudo chmod 755 /usr/local/bin/virt-back
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="test-installation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Test installation&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="literal-block"&gt;
virt-back --help
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="example-cronjob"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Example cronjob&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="literal-block"&gt;
15  2  *  *  1  /usr/local/bin/virt-back --quiet --backup sagat
15  23 *  *  5  /usr/local/bin/virt-back --quiet --backup mbison
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="manual"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Manual&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="literal-block"&gt;
russell&amp;#64;host:~$ virt-back --help
Usage: virt-back [options]
Options:
  -h, --help            show this help message and exit
  -q, --quiet           prevent output to stdout
  -d, --date            append date to tar filename [default: no date]
  -g, --no-gzip         do not gzip or tar the resulting files
  -a amount, --retention=amount
                        backups to retain [default: 3]
  -p 'PATH', --path='PATH'
                        backup path [default: '/KVMBACK']
  -u 'URI', --uri='URI'
                        optional hypervisor uri

  Actions for info testing:
    These options display info or test a list of guests.

    -i, --info          info/test a list of guests (space delimited dom names)
    --info-all          attempt to show info on ALL guests

  Actions for a list of dom names:
    WARNING:  These options WILL bring down guests!

    -b, --backup        backup a list of guests (space delimited dom names)
    -r, --reboot        reboot a list of guests (space delimited dom names)
    -s, --shutdown      shutdown a list of guests (space delimited dom names)
    -c, --create        start a list of guests (space delimited dom names)

  Actions for all doms:
    WARNING:  These options WILL bring down ALL guests!

    --backup-all        attempt to shutdown, backup, and start ALL guests
    --reboot-all        attempt to shutdown and then start ALL guests
    --shutdown-all      attempt to shutdown ALL guests
    --create-all        attempt to start ALL guests
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="restoring"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Restoring&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="/virt-back-restoring-from-backups/"&gt;virt-back: restoring from backups&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Code"></category><category term="DevOps"></category><category term="Greatest Hits"></category><category term="Guide"></category><category term="Project"></category></entry><entry><title>Response to L-Theanine: a 4000 Year Old Mind-Hack</title><link href="https://russell.ballestrini.net/response-to-l-theanine-a-4000-year-old-mind-hack/" rel="alternate"></link><published>2011-01-11T17:06:00-05:00</published><updated>2011-01-11T17:06:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-01-11:/response-to-l-theanine-a-4000-year-old-mind-hack/</id><summary type="html">&lt;p class="first last"&gt;Solve problems and write code in your sleep.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;strong&gt;RE: https://worldoftea.org/caffeine-and-l-theanine&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2011/01/dream.jpg"&gt;&lt;img alt="dream-solving" class="wordwrap-left" src="/uploads/2011/01/dream.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As the original article speculates, the combination and amount of
L-Theanine and caffeine present in tea, appears to have notable affects
on my programming and problem solving abilities. It's difficult to show
conclusive evidence to this claim, but I generally feel most alert and
organized when coding under the influence of 2 - 3 cups. In a typical
day I drink about 4 cups, dosed an hour apart. I feel compelled to
introduce another &amp;quot;mind hack&amp;quot; for problem solving or programming, which
I call &amp;quot;Dream coding&amp;quot;&lt;/p&gt;
&lt;p&gt;&amp;quot;Dream coding&amp;quot; is just that, coding at night during sleep. One can
successfully invoke this &amp;quot;mind hack&amp;quot; by provoking thought about the
problem while drifting to sleep. Once sleeping I'm able to see my code
and my brain seems to iteratively problem solve. Most often I'm lucid
during these events; aware of the problem I'm trying to solve and of the
possible solutions. Other times I don't recall performing the solutions,
but when I wake and after my first cup of tea, I'm able to solve my
problem with a optimal and beautiful solution.&lt;/p&gt;
&lt;p&gt;PBS NOVA published an episode about this phenomenon: &lt;a class="reference external" href="https://www.pbs.org/wgbh/nova/dreams/"&gt;What are
dreams?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Does this happen to you? Have you experienced this?&lt;/p&gt;
&lt;p&gt;You should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"></category><category term="code"></category><category term="L-Theanine"></category><category term="mind hack"></category><category term="tea"></category></entry><entry><title>I just used Google to purchase Chinese food takeout for two</title><link href="https://russell.ballestrini.net/i-just-used-google-to-purchase-chinese-food-takeout-for-two/" rel="alternate"></link><published>2011-01-10T20:03:00-05:00</published><updated>2011-01-10T20:03:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-01-10:/i-just-used-google-to-purchase-chinese-food-takeout-for-two/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;img alt="china-food" src="/uploads/2011/01/food1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Yes, it has been done. Using my Google Chromium browser I searched
Google Maps for the closest local Chinese restaurant. The small town
venue doesn't have a website or menu posted online.&lt;/p&gt;
&lt;p&gt;So I used Google Web to search for the restaurant name. I found
takeouttonight.com (no longer works) which had the entire menu and VALID prices
for the restaurant hosted online! I choose 'Sweet and Sour chicken', my
wife selected Chicken and Broccoli.&lt;/p&gt;
&lt;p&gt;I then logged into my Google Gmail account and clicked Call phone. Using
the mouse I dialed the number I previously researched and after 3 rings
an Asian speaking fellow answered the phone.&lt;/p&gt;
&lt;p&gt;I said, &amp;quot;Hello, I'd like to place a delivery order.&amp;quot; His reply, &amp;quot;Ok.&amp;quot;
resonated out of my 5.1 speaker setup.&lt;/p&gt;
&lt;p&gt;I read off our selections and was super excited that it was WORKING! In
my titillated state, I ended up ordering an extra side of egg rolls just
to keep the conversation going.&lt;/p&gt;
&lt;p&gt;He asked for my phone number to complete the order. I obliged by reading
my Google phone number.&lt;/p&gt;
&lt;p&gt;Google might be spamy these days, but it's still useful.&lt;/p&gt;
&lt;p&gt;Who would have thought a lazy hacker could combine six different Google
services to order Chinese food without leaving the desk?&lt;/p&gt;
&lt;p&gt;I know it's possible now, I've seen me do it.&lt;/p&gt;
&lt;p&gt;You should follow me on twitter
&lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"></category><category term="Chinese"></category><category term="Google"></category><category term="hack"></category></entry><entry><title>Graphic Design Powerhouse: Wacom Bamboo, Ubuntu, and MyPaint</title><link href="https://russell.ballestrini.net/graphic-design-powerhouse-wacom-bamboo-ubuntu-and-mypaint/" rel="alternate"></link><published>2011-01-09T00:14:00-05:00</published><updated>2011-01-09T00:14:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-01-09:/graphic-design-powerhouse-wacom-bamboo-ubuntu-and-mypaint/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2011/01/pot.png"&gt;&lt;img alt="pot" class="wordwrap-left" src="/uploads/2011/01/pot.png" style="width: 150px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For Christmas this year my wife gifted me a Wacom Bamboo pen tablet.
She claimed to have researched many products and looked
for a model that worked well with Ubuntu and had positive user reviews.
She ultimately chose the Wacom Bamboo model and I was so excited when I
unwrapped it!&lt;/p&gt;
&lt;p&gt;The driver install was flawless and I was up and running after about 5
mins and the first application I attempted to use was Gimp. Gimp worked
but the flow felt &amp;quot;clunky&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external image-reference" href="/uploads/2011/01/treeman1.png"&gt;&lt;img alt="treeman" class="wordwrap-right" src="/uploads/2011/01/treeman1.png" style="width: 300px;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I wanted an experience that better emulated
a pencil or brush and canvas. I went to bed that night feeling a little
unsatisfied.&lt;/p&gt;
&lt;p&gt;The next day I decided to give the tablet another try, but instead of
using Gimp I decided to try my luck with other software called
&lt;a class="reference external" href="http://mypaint.org/about/"&gt;MyPaint&lt;/a&gt;. MyPaint's interface feels
perfect and it is loaded with brushes, pencils, and inks. MyPaint
implements brush stroke pressure with unmatched precision!&lt;/p&gt;
&lt;p&gt;The art on this page was created with my wacom BAMBOO tablet and
MyPaint.&lt;/p&gt;
&lt;p&gt;You should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you so much MyPaint developers for such a stellar piece of
software!&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;MyPaint is a fast and easy open-source graphics application for
digital painters. It lets you focus on the art instead of the
program. You work on your canvas with minimum distractions, bringing
up the interface only when you need it.&lt;/strong&gt;&lt;/blockquote&gt;
</content><category term="misc"></category><category term="Art"></category><category term="Bamboo"></category><category term="MyPaint"></category><category term="Ubuntu"></category><category term="Wacom"></category></entry><entry><title>How did Stack Overflow get initial traction?</title><link href="https://russell.ballestrini.net/how-did-stack-overflow-get-initial-traction/" rel="alternate"></link><published>2011-01-05T02:57:00-05:00</published><updated>2011-01-05T02:57:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-01-05:/how-did-stack-overflow-get-initial-traction/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Stack Overflow was a progressive and natural evolution of the standard
clunky forum.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using ajax it created a more fun and clean user experience.&lt;/p&gt;
&lt;p&gt;Using badges and karma to gain responsibility allow forums post to
become a game. People naturally like to see progression and growth,
being able to watch …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Stack Overflow was a progressive and natural evolution of the standard
clunky forum.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using ajax it created a more fun and clean user experience.&lt;/p&gt;
&lt;p&gt;Using badges and karma to gain responsibility allow forums post to
become a game. People naturally like to see progression and growth,
being able to watch my karma go up was a rush, similar to a video game.
(some people plan elaborate flights to rack up frequent flyer miles,
planning a trip properly could cost as little as 50 dollars to fly
around the world in 24 hours, and gaining thousands of miles using point
multipliers)&lt;/p&gt;
&lt;img alt="StackOverflow Logo" class="wordwrap-left" src="/uploads/2011/01/stack1.png" /&gt;
&lt;p&gt;Stack Overflow was search-able! Here is a thought,
create a forum that is easily index by search engines...
One question per page, on topic with the most relevant posts at the top of the page.
Who cares about user bounce backs, if they see your URL enough (StackOverflow.com) they will end up creating an account and becoming a user who creates even more quality content!&lt;/p&gt;
&lt;p&gt;The site was geared toward geeks, and programmers! They adopt new tech
the quickest.&lt;/p&gt;
&lt;p&gt;Did I miss any reasons? Feel free to comment below.&lt;/p&gt;
&lt;p&gt;You should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="Opinion"></category><category term="social network"></category></entry><entry><title>A homegrown python bread crumb module</title><link href="https://russell.ballestrini.net/a-homegrown-python-bread-crumb-module/" rel="alternate"></link><published>2011-01-02T14:14:00-05:00</published><updated>2011-01-02T14:14:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2011-01-02:/a-homegrown-python-bread-crumb-module/</id><summary type="html">&lt;p&gt;I have placed
&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/bread/raw/tip/bread.py"&gt;bread.py&lt;/a&gt;, a python breadcrumb module, into the public domain.&lt;/p&gt;
&lt;p&gt;The bread object accepts a url string and grants access to the url
crumbs (parts) or url links (list of hrefs to each crumb).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tutorial:&lt;/strong&gt; &lt;a class="reference external" href="/add-a-breadcrumb-subscriber-to-a-pyramid-project-using-4-simple-steps/"&gt;Add a Breadcrumb Subscriber to a Pyramid project using 4 simple
steps …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have placed
&lt;a class="reference external" href="https://bitbucket.org/russellballestrini/bread/raw/tip/bread.py"&gt;bread.py&lt;/a&gt;, a python breadcrumb module, into the public domain.&lt;/p&gt;
&lt;p&gt;The bread object accepts a url string and grants access to the url
crumbs (parts) or url links (list of hrefs to each crumb).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tutorial:&lt;/strong&gt; &lt;a class="reference external" href="/add-a-breadcrumb-subscriber-to-a-pyramid-project-using-4-simple-steps/"&gt;Add a Breadcrumb Subscriber to a Pyramid project using 4 simple
steps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Demo:&lt;/strong&gt; &lt;a class="reference external" href="https://school.yohdah.com/"&gt;https://school.yohdah.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You should follow me on twitter &lt;a class="reference external" href="https://twitter.com/russellbal"&gt;here&lt;/a&gt;&lt;/p&gt;
</content><category term="misc"></category><category term="bread"></category><category term="code"></category><category term="crumb"></category><category term="python"></category></entry><entry><title>four2go!: A new spin on an old classic</title><link href="https://russell.ballestrini.net/four2go-a-new-spin-on-an-old-classic/" rel="alternate"></link><published>2010-12-31T20:33:00-05:00</published><updated>2010-12-31T20:33:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2010-12-31:/four2go-a-new-spin-on-an-old-classic/</id><summary type="html"></summary><content type="html">&lt;p&gt;&lt;img alt="four2go! logo" src="/uploads/2010/12/four2go.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For the past two months&lt;/strong&gt; I have feverishly worked on my side project.
Initially I set out to work on this application for submission to
Hacker News, lets make November &amp;quot;Launch an App Month&amp;quot;.&lt;/p&gt;
&lt;p&gt;The whole project took longer than expected but I was please with my
progress so I continued development. Today, a month later, I have added
the finishing touches to the app. It gives me great pleasure to announce
the official launch of &lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A new spin ...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go!&lt;/a&gt; brings a new spin to the
classic &amp;quot;four in a row&amp;quot; genre.&lt;/p&gt;
&lt;p&gt;Instead of weighted tokens, &lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go!&lt;/a&gt;
uses balloons which float up rather than down. This new game mechanic
requires players to retrain their brains and can easily throw off a
novice player.&lt;/p&gt;
&lt;p&gt;Another new spin, rather than playing on a coffee table, users play
using a computer over the internet. The four2go! web application
requires a simple account registration but does not require any download
or installation!&lt;/p&gt;
&lt;p&gt;Users can play with anyone, at anytime, anywhere in the world. The game
sessions are persistent meaning the board will not disappear. This
persistence allows players to check their games much like they would
check their email.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gumyum logo" src="/uploads/2010/12/gumyumgameslogo.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The gumyum framework&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The game itself was completed after the first weekend of coding.
Programming the game was fun and exciting and originally four2go! was a
command prompt application with a text-based gui. That version was not
accessible and in order for people to play we needed to move to a
different medium. So I set out to code a web front end.&lt;/p&gt;
&lt;p&gt;After another weekend of coding I had the game working in the browser.
It was very clunky (It didn't refresh after player moves) and didn't
have any authentication, anyone could play any game.&lt;/p&gt;
&lt;blockquote&gt;
&lt;strong&gt;Coding the game was simple, the framework was the difficult
part...&lt;/strong&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Enter Stage Left:&lt;/em&gt; The gumyum framework. I needed a way to
authentication users and a way to store and retrieve game sessions. I
also needed a way to track user statistics and player history. Most
importantly I needed a user interface that would be intuitive and fun to
use! The remainder of the project set out to solve each of those needs.
I needed the gumyum framework to make four2go! popular. I spent the rest
of the time (1.5 months) working on gumyum and I feel safe claiming that
the framework appears complete.&lt;/p&gt;
&lt;p&gt;The great part about having a framework is the code reusability, I
should be able to &amp;quot;plug&amp;quot; new games or new mechanics behind the gumyum
framework with little trouble. My labor and efforts should pay off if I
ever decide to build another game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artwork and graphic design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was very fortunate to work with my brother &lt;a class="reference external" href="https://joey.ballestrini.net"&gt;Joey
Ballestrini&lt;/a&gt; on some of the concept and
game art. We designed the &lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go!&lt;/a&gt; logo,
the &lt;a class="reference external" href="https://gumyum.com"&gt;gumyum&lt;/a&gt; logo, and the balloons. Joey designed
each of the &amp;quot;create&amp;quot; game icons. The clouds, the grass, and the dirt
were created for another game and I decided they were a good fit so they
were re-purposed. We both use gimp when creating graphical computer art.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LETS PLAY!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have attached a video of the game, but I
urge you to try &lt;a class="reference external" href="https://four2go.gumyum.com"&gt;four2go!&lt;/a&gt; out for
yourself!&lt;/p&gt;
&lt;p&gt;
&lt;center&gt;
&lt;iframe title="YouTube video player" width="480" height="338" src="https://www.youtube.com/embed/wmB9PeKBAlA" frameborder="0"&gt;
&lt;/iframe&gt;
&lt;/center&gt;
&lt;/p&gt;</content><category term="misc"></category><category term="four2go!"></category><category term="gumyum"></category><category term="programming"></category><category term="python"></category></entry><entry><title>The Barrymores stole my heart then crushed it</title><link href="https://russell.ballestrini.net/the-barrymores-stole-my-heart-then-crushed-it/" rel="alternate"></link><published>2010-11-30T04:37:00-05:00</published><updated>2010-11-30T04:37:00-05:00</updated><author><name>Russell Ballestrini</name></author><id>tag:russell.ballestrini.net,2010-11-30:/the-barrymores-stole-my-heart-then-crushed-it/</id><summary type="html"></summary><content type="html">&lt;img alt="The Barrymores Ska Band" src="/uploads/2010/11/thebarrymores.png" /&gt;
&lt;p&gt;&lt;strong&gt;Recently while surfing pandora&lt;/strong&gt; I stumbled upon a new favorite song,
&amp;quot;Think Straight Again&amp;quot;, composed by a ska band named The Barrymores. &amp;nbsp;I
embraced the bands alluring melodies after the first play.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What caught my attention?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well the whole band rocks, &lt;em&gt;hard&lt;/em&gt;. &amp;nbsp;The main components of The
Barrymores shape a full and energetic sound. &amp;nbsp;They have a drummer,
a&amp;nbsp;bassist, &amp;nbsp;a lead&amp;nbsp;guitarist and two horn players (trumpet&amp;nbsp;and
trombone). &amp;nbsp;Oh, I almost forgot to mention they&amp;nbsp;contrast most ska bands
by featuring a kickass lead female singer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What compelled me to write about The Barrymores?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Though The Barrymores are a&amp;nbsp;small town band, their technical abilities
and prowess mimic more a band of a major record label. &amp;nbsp;Their rocking
upbeat style is similar to other ska bands. &amp;nbsp;Each track creates a
euphoric&amp;nbsp;sensation and&amp;nbsp;even the &amp;quot;sad&amp;quot;&amp;nbsp;songs feel uplifting. When The
Barrymores are on my stereo I typically&amp;nbsp;end up chanting the lyrics while
skanking around my office. &amp;nbsp;I feel they bring the ska punk rock genre
into another level of excellence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And then I found the depressing news ...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I started researching The Barrymores because I was&amp;nbsp;fascinated&amp;nbsp;with their music.
This research&amp;nbsp;inevitably&amp;nbsp;lead to some depressing news,
The Barrymores are no longer together!&lt;/p&gt;
&lt;blockquote&gt;
&amp;quot;I Am Jack's Broken Heart&amp;quot; -Fight Club.&lt;/blockquote&gt;
&lt;p&gt;Not often do I get excited about new music.
In moments I grew attached to the group only to get crushed.
I&amp;nbsp;decided&amp;nbsp;to investigate to uncover more.&lt;/p&gt;
&lt;p&gt;Apparently The Barrymores went into&amp;nbsp;hiatus after one of the founding
members passed away.&amp;nbsp;&amp;nbsp;&amp;nbsp;The
Barrymore&amp;nbsp;&lt;a class="reference external" href="https://www.myspace.com/thebarrymores"&gt;myspace&lt;/a&gt; page
still seems active and continues to hosts a few of the bands&amp;nbsp;tracks.&lt;/p&gt;
&lt;p&gt;Sorry about the bummer, but you should still give the music a listen.&lt;/p&gt;
</content><category term="misc"></category><category term="Barrymores"></category><category term="music"></category></entry></feed>