<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python on Major Hayden</title><link>https://major.io/tags/python/</link><description>Recent content in Python on Major Hayden</description><generator>Hugo</generator><language>en</language><managingEditor>major@mhtx.net (Major Hayden)</managingEditor><webMaster>major@mhtx.net (Major Hayden)</webMaster><copyright>All content licensed [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)</copyright><lastBuildDate>Sun, 10 May 2026 09:15:13 +0000</lastBuildDate><atom:link href="https://major.io/tags/python/index.xml" rel="self" type="application/rss+xml"/><item><title>Coding love for Porkbun</title><link>https://major.io/p/love-for-porkbun/</link><pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/love-for-porkbun/</guid><description>&lt;p&gt;&lt;a href="https://porkbun.com"&gt;Porkbun&lt;/a&gt; does exactly what I need and nothing more.
I came to them a few years ago with domains scattered across a few different registrars that were quickly becoming too difficult to manage.
Their site has minimal upselling, quick renewals, and self-servicing your domains is easy. 🐷&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m always on the hunt for ways to automate all of my infrastructure, so I set out to build some Porkbun automation.
However, many of the python modules for Porkbun only did certain things, such as just DNS or just buying domains.
I wanted the whole thing: domain pricing, DNS, DNSSec management.
&lt;strong&gt;Everything.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dig into the code. 👨‍💻&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Important note:&lt;/strong&gt; None of these projects are affiliated with Porkbun in any way.
They&amp;rsquo;re just passion projects from someone who really enjoys using their service!&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="oinker-"&gt;oinker 🐖&lt;/h1&gt;
&lt;p&gt;My initial project is &lt;a href="https://github.com/major/oinker"&gt;oinker&lt;/a&gt;.
It&amp;rsquo;s a simple Python client (async-first, but sync is there, too) that helps you manage all aspects of your Porkbun account in a type-safe and Pythonic way.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;oinker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncPiglet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARecord&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;AsyncPiglet&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;piglet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# List DNS records&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;piglet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Create an A record&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;piglet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ARecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.2.3.4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;www&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a lot more you can do with it!
The &lt;a href="https://major.github.io/oinker/"&gt;documentation&lt;/a&gt; covers lots of examples and has an extensive set of &lt;a href="https://major.github.io/oinker/api/client/"&gt;API reference material&lt;/a&gt;, too.&lt;/p&gt;
&lt;h1 id="porkbun-mcp-"&gt;porkbun-mcp 🤖&lt;/h1&gt;
&lt;p&gt;LLMs are all around us these days, right?
Sometimes I like to talk to Claude via &lt;a href="https://opencode.ai/"&gt;opencode&lt;/a&gt; about my DNS records.
This gets handy when I&amp;rsquo;m struggling to find out why one of my kubernetes services isn&amp;rsquo;t responding and it&amp;rsquo;s due to a missing DNS record.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s &lt;a href="https://github.com/major/porkbun-mcp"&gt;porkbun-mcp&lt;/a&gt; for that!
You can chat with LLMs about your DNS records to get advice, troubleshoot problems, or just create records on the go.&lt;/p&gt;
&lt;p&gt;&lt;img alt="opencode-mx.png &amp;ldquo;Asking about my MX records in OpenCode&amp;rdquo;" loading="lazy" src="https://major.io/p/love-for-porkbun/opencode-mx.png"&gt;&lt;/p&gt;
&lt;p&gt;Check the &lt;a href="https://major.github.io/porkbun-mcp/getting-started/"&gt;getting started&lt;/a&gt; docs to configure porkbun-mcp in your favorite MCP client or LLM tool.&lt;/p&gt;
&lt;h1 id="octodns-porkbun-"&gt;octodns-porkbun 🐙&lt;/h1&gt;
&lt;p&gt;Finally, there&amp;rsquo;s &lt;a href="https://github.com/major/octodns-porkbun"&gt;octodns-porkbun&lt;/a&gt; for those who manage DNS as code with &lt;a href="https://github.com/octodns/octodns"&gt;octoDNS&lt;/a&gt;.
This provider plugin uses oinker under the hood and supports all the record types you&amp;rsquo;d expect.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re already using octoDNS to manage DNS across multiple providers, this makes it easy to add Porkbun to the mix.&lt;/p&gt;
&lt;h1 id="pull-requests-welcome"&gt;Pull requests welcome!&lt;/h1&gt;
&lt;p&gt;If you find a way to improve upon any of these projects, please let me know!
You can always stop by one of the repos to open an issue, fix a bug, or add a feature!&lt;/p&gt;</description></item><item><title>Fun with docling</title><link>https://major.io/p/fun-with-docling/</link><pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/fun-with-docling/</guid><description>&lt;p&gt;My team at work does lots of work with retrieval-augmented generation, or RAG, and parsing documents is really painful.
It&amp;rsquo;s so painful that I recently delivered a talk on this very topic at &lt;a href="https://major.io/p/devconf-rag/"&gt;DevConf.US 2025&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Another one of the talks at DevConf.US this year was about &lt;a href="https://docling-project.github.io/docling/"&gt;docling&lt;/a&gt;.
We&amp;rsquo;ve been using it on our team for a while and we really enjoy how it takes challenging documents in various formats and parses them into a single, common document.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll walk you through setting up docling in your project and show you some fun things you can do with it.&lt;/p&gt;
&lt;h1 id="adding-docling-to-a-project"&gt;Adding docling to a project&lt;/h1&gt;
&lt;p&gt;I usually use &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt; for my Python projects.
You can start a new project like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pipx install uv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir fun-with-docling
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; fun-with-docling
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv init --package --name doclingfun
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can add docling to the project.
But first, I prefer to install &lt;code&gt;torch-cpu&lt;/code&gt; to avoid downloading lots of unnecessary CUDA libraries if I don&amp;rsquo;t need them.&lt;/p&gt;
&lt;p&gt;Start by adding a &lt;code&gt;torch-cpu&lt;/code&gt; source in your &lt;code&gt;pyproject.toml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;torch-cpu&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://download.pytorch.org/whl/cpu&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;explicit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;torch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;torch-cpu&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;torchvision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;torch-cpu&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then add &lt;code&gt;torch&lt;/code&gt;, &lt;code&gt;torchvision&lt;/code&gt;, and &lt;code&gt;docling&lt;/code&gt; to your dependencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv add torch torchvision docling
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify the installation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; uv run docling --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:02:48,742 - INFO - Loading plugin &lt;span class="s1"&gt;&amp;#39;docling_defaults&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:02:48,743 - INFO - Registered ocr engines: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;easyocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;ocrmac&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;rapidocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;tesserocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;tesseract&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Docling version: 2.54.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Docling Core version: 2.48.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Docling IBM Models version: 3.9.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Docling Parse version: 4.5.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Python: cpython-313 &lt;span class="o"&gt;(&lt;/span&gt;3.13.3&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Platform: Linux-6.16.8-200.fc42.x86_64-x86_64-with-glibc2.41
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="parsing-a-document"&gt;Parsing a document&lt;/h2&gt;
&lt;p&gt;Financial markets are a hobby of mine, so I always enjoy reading research on patterns in the market.
There&amp;rsquo;s a &lt;a href="https://arxiv.org/abs/2509.16137"&gt;great document&lt;/a&gt; that we can start with.
Download the PDF and parse it with docling:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -L -o enhancing_ohlc_data.pdf https://arxiv.org/pdf/2509.16137
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; uv run docling enhancing_ohlc_data.pdf --from pdf --to json --output .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,958 - INFO - Loading plugin &lt;span class="s1"&gt;&amp;#39;docling_defaults&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,959 - INFO - Registered ocr engines: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;easyocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;ocrmac&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;rapidocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;tesserocr&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;tesseract&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,964 - INFO - paths: &lt;span class="o"&gt;[&lt;/span&gt;PosixPath&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/tmp243lux62/enhancing_ohlc_data.pdf&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,964 - INFO - detected formats: &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;InputFormat.PDF: &lt;span class="s1"&gt;&amp;#39;pdf&amp;#39;&lt;/span&gt;&amp;gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,971 - INFO - Going to convert document batch...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,971 - INFO - Initializing pipeline &lt;span class="k"&gt;for&lt;/span&gt; StandardPdfPipeline with options &lt;span class="nb"&gt;hash&lt;/span&gt; f1301fa0db91f613a1f4baa1a2a11518
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,973 - INFO - Loading plugin &lt;span class="s1"&gt;&amp;#39;docling_defaults&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:07:59,974 - INFO - Registered picture descriptions: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vlm&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;api&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:00,241 - INFO - Accelerator device: &lt;span class="s1"&gt;&amp;#39;cpu&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:01,278 - INFO - Accelerator device: &lt;span class="s1"&gt;&amp;#39;cpu&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:05,085 - INFO - Accelerator device: &lt;span class="s1"&gt;&amp;#39;cpu&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:05,381 - INFO - Processing document enhancing_ohlc_data.pdf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:51,604 - INFO - Finished converting document enhancing_ohlc_data.pdf in 51.64 sec.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:51,604 - INFO - writing JSON output to enhancing_ohlc_data.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:51,667 - INFO - Processed &lt;span class="m"&gt;1&lt;/span&gt; docs, of which &lt;span class="m"&gt;0&lt;/span&gt; failed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-09-26 11:08:51,672 - INFO - All documents were converted in 51.71 seconds.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Success!
We now have a JSON file that contains the parsed document.
(If you&amp;rsquo;re in a hurry and just want to view the JSON, I&amp;rsquo;ve &lt;a href="enhancing_ohlc_data.json"&gt;uploaded it here&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s break down how the documents work&lt;/p&gt;
&lt;h1 id="groups-and-texts"&gt;Groups and texts&lt;/h1&gt;
&lt;p&gt;Groups are collections of related content.
Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;groups&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;self_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/groups/0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;parent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/body&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;children&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/texts/53&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/texts/54&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/texts/55&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/texts/56&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;content_layer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;label&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This group has four children which are called &amp;ldquo;texts&amp;rdquo; and we can tell that this is a list of some sort.&lt;/p&gt;
&lt;p&gt;If we look for &lt;code&gt;#/texts/53&lt;/code&gt;, we can see what the first item in the list is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;self_ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/texts/53&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;parent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#/groups/0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;children&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;content_layer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;label&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;list_item&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;prov&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;page_no&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;bbox&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;l&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;86.945&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;669.104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;r&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;540.004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;644.616&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;coord_origin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BOTTOMLEFT&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;charspan&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;187&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;orig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;\u00b7 Market relevance : VWAP is widely followed by institutional investors, trading algorithms, and benchmark providers [6] [2]. As such, it is a meaningful and valuable quantity to predict.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Market relevance : VWAP is widely followed by institutional investors, trading algorithms, and benchmark providers [6] [2]. As such, it is a meaningful and valuable quantity to predict.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;enumerated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;marker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;\u00b7&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a single line of text from the document, but it&amp;rsquo;s part of a list.
You can see the parent relationship back to &lt;code&gt;#/groups/0&lt;/code&gt;.
This text has a label of &lt;code&gt;list_item&lt;/code&gt; which indicates that it&amp;rsquo;s part of a list.
There&amp;rsquo;s also a &lt;code&gt;marker&lt;/code&gt; field so you can extract the bullet point character if you want to.&lt;/p&gt;
&lt;p&gt;What does this look like in the original document?&lt;/p&gt;
&lt;p&gt;&lt;img alt="list-in-document.png" loading="lazy" src="https://major.io/p/fun-with-docling/list-in-document.png"&gt;&lt;/p&gt;
&lt;p&gt;So if you found a piece of text that interest you, you can walk backwards to the group and then higher in the document if needed.&lt;/p&gt;
&lt;h1 id="wandering-around-documents"&gt;Wandering around documents&lt;/h1&gt;
&lt;p&gt;Let&amp;rsquo;s break out some Python and see what we can do with documents.&lt;/p&gt;
&lt;h2 id="getting-child-items"&gt;Getting child items&lt;/h2&gt;
&lt;p&gt;First off, let&amp;rsquo;s find that &lt;code&gt;&amp;quot;#/groups/0&amp;quot;&lt;/code&gt; from earlier and get the texts inside of it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docling_core.types.doc.document&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rich&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enhancing_ohlc_data.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Extract group 0 and find its children.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;my_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;refs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Loop through the children,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# resolve their location,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# and print the original text.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;text&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;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After running this, I get the text from the bulleted list:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;· Market relevance : VWAP is widely followed by institutional investors, trading algorithms, and benchmark providers [6] [2]. As such, it is a meaningful and valuable quantity to predict.
· Interval-wide robustness : Unlike point-in-time prices such as the open or close, VWAP summarizes trading activity across the full interval, making it a more stable and representative measure of price.
· Reduced discretization noise : VWAP is a continuous, volume-weighted price and is therefore less affected by the rounding effects of penny-level price changes. In contrast, returns computed from last-trade prices (e.g., close-to-close) are often either exactly zero or
artificially large due to the $0.01 minimum tick increment.
· Weaker arbitrage incentives : Predicting the direction of VWAP changes does not lend itself to straightforward arbitrage. A trader who knows that the next bar&amp;#39;s VWAP will be higher than the current one cannot directly profit from this knowledge unless they had already
executed near the current VWAP. As a result, patterns in VWAP returns may persist even in broadly efficient markets.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="getting-parent-items"&gt;Getting parent items&lt;/h2&gt;
&lt;p&gt;What if we wanted to go the other way around?
Perhaps there was something really good in &lt;code&gt;#/texts/55&lt;/code&gt; that we wanted to find in the document.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docling_core.types.doc.document&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rich&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enhancing_ohlc_data.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Extract text 55 and get the parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;my_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;parent_ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Resolve the parent reference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;parent_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent_ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run this and we get the details of &lt;code&gt;#/groups/0&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ListGroup(
 self_ref=&amp;#39;#/groups/0&amp;#39;,
 parent=RefItem(cref=&amp;#39;#/body&amp;#39;),
 children=[RefItem(cref=&amp;#39;#/texts/53&amp;#39;), RefItem(cref=&amp;#39;#/texts/54&amp;#39;), RefItem(cref=&amp;#39;#/texts/55&amp;#39;), RefItem(cref=&amp;#39;#/texts/56&amp;#39;)],
 content_layer=&amp;lt;ContentLayer.BODY: &amp;#39;body&amp;#39;&amp;gt;,
 name=&amp;#39;list&amp;#39;,
 label=&amp;lt;GroupLabel.LIST: &amp;#39;list&amp;#39;&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="removing-items"&gt;Removing items&lt;/h2&gt;
&lt;p&gt;You can also remove individual items from a document or certain classes of items.
Let&amp;rsquo;s assume that you don&amp;rsquo;t want any lists in your document.
You can remove them like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docling_core.types.doc.document&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ListItem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rich&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enhancing_ohlc_data.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;total_texts&lt;/span&gt; &lt;span class="o"&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;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Total texts in document: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_texts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;list_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&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;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&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;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ListItem&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;total_texts_after_deletion&lt;/span&gt; &lt;span class="o"&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;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Total texts after deletion: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_texts_after_deletion&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running this shows that the list items were removed:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt; uv run python src/doclingfun/main.py
Total texts in document: 338
Total texts after deletion: 324
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="converting-to-other-formats"&gt;Converting to other formats&lt;/h2&gt;
&lt;p&gt;Finally, you can convert documents to other formats.
You can certainly do something simple like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_as_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enhancing_ohlc_data_modified.md&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But what if we wanted our group from earlier serialized to markdown without any other document contents.
You can!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docling_core.transforms.serializer.markdown&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MarkdownDocSerializer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;docling_core.types.doc.document&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rich&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DoclingDocument&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_from_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;enhancing_ohlc_data.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;my_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MarkdownDocSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ser_res&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;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;my_group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ser_res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That results in the same text as before, but now the bullets are removed and replaced with Markdown-style dashes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;- Market relevance : VWAP is widely followed by institutional investors, trading algorithms, and benchmark providers [6] [2]. As such, it is a meaningful and valuable quantity to predict.
- Interval-wide robustness : Unlike point-in-time prices such as the open or close, VWAP summarizes trading activity across the full interval, making it a more stable and representative measure of price.
- Reduced discretization noise : VWAP is a continuous, volume-weighted price and is therefore less affected by the rounding effects of penny-level price changes. In contrast, returns computed from last-trade prices (e.g., close-to-close) are often either exactly zero or
artificially large due to the $0.01 minimum tick increment.
- Weaker arbitrage incentives : Predicting the direction of VWAP changes does not lend itself to straightforward arbitrage. A trader who knows that the next bar&amp;#39;s VWAP will be higher than the current one cannot directly profit from this knowledge unless they had already
executed near the current VWAP. As a result, patterns in VWAP returns may persist even in broadly efficient markets.
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="more-to-explore"&gt;More to explore&lt;/h1&gt;
&lt;p&gt;Docling has &lt;em&gt;lots more features&lt;/em&gt; that I haven&amp;rsquo;t covered here.
For example, it can do &lt;a href="https://docling-project.github.io/docling/examples/tesseract_lang_detection/"&gt;OCR on images&lt;/a&gt;.
You can also &lt;a href="https://docling-project.github.io/docling/examples/minimal_vlm_pipeline/"&gt;use LLMs to help with parsing&lt;/a&gt;.
It also comes with a great &lt;a href="https://docling-project.github.io/docling/examples/hybrid_chunking/#basic-usage"&gt;hybrid chunker&lt;/a&gt; to break documents into smaller pieces for embedding into a RAG database.&lt;/p&gt;</description></item><item><title>Mounting the AWS Elastic File Store on Fedora</title><link>https://major.io/p/aws-elastic-file-system-fedora/</link><pubDate>Wed, 13 Sep 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/aws-elastic-file-system-fedora/</guid><description>&lt;hr&gt;
&lt;p&gt;I package a few things here and there in &lt;a href="https://fedoraproject.org/"&gt;Fedora&lt;/a&gt; and one of my latest packages is &lt;a href="https://src.fedoraproject.org/rpms/efs-utils"&gt;efs-utils&lt;/a&gt;.
AWS offers a mount helper for their &lt;a href="https://aws.amazon.com/efs/"&gt;Elastic File System (EFS)&lt;/a&gt; product on &lt;a href="https://github.com/aws/efs-utils"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll explain how to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Launch a Fedora instance on AWS EC2&lt;/li&gt;
&lt;li&gt;Install &lt;em&gt;efs-utils&lt;/em&gt; and launch the watchdog service&lt;/li&gt;
&lt;li&gt;Create an EFS volume in the AWS console&lt;/li&gt;
&lt;li&gt;Mount the EFS volume inside the Fedora instance&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;strong&gt;Always check the pricing for any cloud service before you use it!&lt;/strong&gt;
&lt;a href="https://aws.amazon.com/efs/pricing/"&gt;EFS pricing&lt;/a&gt; is based on how much you store and how often you access it.
Backups are also enabled by default and they add to the monthly charges.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s go! 🚀&lt;/p&gt;
&lt;h1 id="wait-what-is-efs"&gt;Wait, what is EFS?&lt;/h1&gt;
&lt;p&gt;When you launch a cloud instance (virtual machine) on most clouds, you have different storage options available to you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Block storage:&lt;/strong&gt;
You can add partitions to this storage, create filesystems, or even use LVM.
It looks like someone plugged in a disk to your instance.
You get full control over every single storage block on the volume.
An example of this is &lt;a href="https://aws.amazon.com/ebs/"&gt;Elastic Block Storage (EBS)&lt;/a&gt; on AWS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Object storage:&lt;/strong&gt;
Although you can&amp;rsquo;t mount object storage (typically) within your instance, you can read/write objects to this storage via an API.
You can upload nearly any type of file you can imagine as an object and then download it later.
Objects can also have little bits of metadata attached to them and some of the metadata include &lt;em&gt;prefixes&lt;/em&gt; which give a folder-like experience.
AWS &lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; is a good example of this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shared filesystems:&lt;/strong&gt;
This storage shows up in the instance exactly as it sounds: you get a shared filesystem.
If you&amp;rsquo;re familiar with NFS or Samba (SMB), then you&amp;rsquo;ve used shared filesystems already.
They give you much better performance than object storage but offer less freedom than block storage.
They&amp;rsquo;re also great for sharing the same data between multiple instances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using EFS is almost like having someone else host a network accessible storage (NAS) device within your cloud deployment.&lt;/p&gt;
&lt;h1 id="launching-fedora"&gt;Launching Fedora&lt;/h1&gt;
&lt;p&gt;Every image in AWS has an AMI ID attached to it and you need to know the ID for the image you want in your region.
You can find these quickly for Fedora by visiting the &lt;a href="https://fedoraproject.org/cloud/download/"&gt;Fedora Cloud download page&lt;/a&gt;.
Look for &lt;em&gt;AWS&lt;/em&gt; in the list, click the button on that row, and you&amp;rsquo;ll see a list of Fedora AMI IDs.
Click the rocket (🚀) for your preferred region and you&amp;rsquo;re linked directly to launch that instance in AWS!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m clicking the &lt;a href="https://console.aws.amazon.com/ec2/home?region=us-east-2#launchAmi=ami-00ef4597fd9806efc"&gt;launch link for us-east-2 (Ohio)&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.
To finish quickly, I&amp;rsquo;m choosing all of the default options and using a spot instance (look inside &lt;em&gt;Advanced details&lt;/em&gt; at the bottom of the page).&lt;/p&gt;
&lt;p&gt;Wait for the instance to finish intializing and access it via ssh:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ssh fedora@EXTERNAL_IP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[fedora@ip-172-31-2-38 ~]$&lt;/span&gt; cat /etc/fedora-release 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Fedora release 38 (Thirty Eight)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Success! 🎉&lt;/p&gt;
&lt;h1 id="prepare-your-security-group"&gt;Prepare your security group&lt;/h1&gt;
&lt;p&gt;Before leaving the EC2 console, you need to make a note of the security group that you used for this instance.
That&amp;rsquo;s because EFS uses security groups to guard access to volumes.
Follow these steps to find it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;em&gt;Instances&lt;/em&gt; on the left side of the EC2 console.&lt;/li&gt;
&lt;li&gt;Click on the row showing the instance we just created.&lt;/li&gt;
&lt;li&gt;In the bottom half of the screen, click the &lt;em&gt;Security&lt;/em&gt; tab.&lt;/li&gt;
&lt;li&gt;Look for &lt;em&gt;Security groups&lt;/em&gt; in the security details and copy the security group ID for later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It should be in the format &lt;code&gt;sg-[a-f0-9]*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you click the security group name (after saving it), you&amp;rsquo;ll see the inbound rules associated with that security group.
By default, items in the same security group can&amp;rsquo;t talk to each other.
We need to allow that so our EFS mount will work later.&lt;/p&gt;
&lt;p&gt;Click &lt;em&gt;Edit inbound rules&lt;/em&gt; and do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;em&gt;Add rule&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;em&gt;All traffic&lt;/em&gt; in the &lt;em&gt;Type&lt;/em&gt; column. &lt;em&gt;(You can narrow this down further later.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;In the source box, look for the security group you just created along with your EC2 instance.
If you took the default during the EC2 launch process, it might be named &lt;code&gt;launch-wizard-[0-9]+&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Save rules&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="installing-efs-utils"&gt;Installing efs-utils&lt;/h1&gt;
&lt;p&gt;Let&amp;rsquo;s start by getting the &lt;em&gt;efs-utils&lt;/em&gt; package onto our new Fedora system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dnf -qy install efs-utils
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installed:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; efs-utils-1.35.0-2.fc38.noarch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The package includes some configuration, a watchdog, and a mount helper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; rpm -ql efs-utils
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/etc/amazon
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/etc/amazon/efs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/etc/amazon/efs/efs-utils.conf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/etc/amazon/efs/efs-utils.crt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/bin/amazon-efs-mount-watchdog
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/lib/systemd/system/amazon-efs-mount-watchdog.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/sbin/mount.efs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/doc/efs-utils
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/doc/efs-utils/CONTRIBUTING.md
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/doc/efs-utils/README.md
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/licenses/efs-utils
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/licenses/efs-utils/LICENSE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/usr/share/man/man8/mount.efs.8.gz
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/var/log/amazon/efs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s get the watchdog running so we have that ready later.
The watchdog helps to build and tear down the encrypted connection when you mount and unmount an EFS volume:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now amazon-efs-mount-watchdog.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Created symlink /etc/systemd/system/multi-user.target.wants/amazon-efs-mount-watchdog.service → /usr/lib/systemd/system/amazon-efs-mount-watchdog.service.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl status amazon-efs-mount-watchdog.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;● amazon-efs-mount-watchdog.service - amazon-efs-mount-watchdog
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Loaded: loaded (/usr/lib/systemd/system/amazon-efs-mount-watchdog.service; enabled; preset: disabled)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Drop-In: /usr/lib/systemd/system/service.d
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; └─10-timeout-abort.conf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Active: active (running) since Wed 2023-09-13 18:43:46 UTC; 5s ago
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Main PID: 1258 (amazon-efs-moun)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Tasks: 1 (limit: 4385)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Memory: 13.3M
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CPU: 76ms
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CGroup: /system.slice/amazon-efs-mount-watchdog.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; └─1258 /usr/bin/python3 /usr/bin/amazon-efs-mount-watchdog
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sep 13 18:43:46 ip-172-31-2-38.us-east-2.compute.internal systemd[1]: Started amazon-efs-mount-watchdog.service - amazon-efs-mount-watchdog.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="setting-up-an-efs-volume"&gt;Setting up an EFS volume&lt;/h1&gt;
&lt;p&gt;Start by going over to the &lt;a href="https://us-east-2.console.aws.amazon.com/efs/home?region=us-east-2#"&gt;EFS console&lt;/a&gt; and do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;em&gt;File systems&lt;/em&gt; in the left navigation bar&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click the orange &lt;em&gt;Create file system&lt;/em&gt; button at the top right&lt;/p&gt;
&lt;p&gt;A modal appears with a box for the volume name and a VPC selection.
Select an easy to remember name (I&amp;rsquo;m using &lt;em&gt;testing-efs-for-blog-post&lt;/em&gt;) and select a VPC.
If you&amp;rsquo;re not sure what a VPC is or which one to use, use the default VPC since that&amp;rsquo;s likely where your instance landed as well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;em&gt;Create&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There&amp;rsquo;s a delay while the filesystem initializes and you should see the filesystem show &lt;em&gt;Available&lt;/em&gt; with a green check mark after about 30 seconds.
Click on the filesystem you just created from the list and you&amp;rsquo;ll see the details page for the filesystem.&lt;/p&gt;
&lt;h1 id="security-setup"&gt;Security setup&lt;/h1&gt;
&lt;p&gt;EFS volumes come online with the default security group attached and that&amp;rsquo;s not helpful.
From the EFS filesystem details page, click the &lt;em&gt;Network&lt;/em&gt; tab and then click &lt;em&gt;Manage&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For each availability zone, go to the &lt;em&gt;Security groups&lt;/em&gt; column and add the security group that your instance came up with in the first step.
In my case, I accepted the defaults from EC2 and ended up with a &lt;em&gt;launch-wizard-1&lt;/em&gt; security group.
Remove the &lt;em&gt;default&lt;/em&gt; security group from each.
Click &lt;em&gt;Save&lt;/em&gt;.&lt;/p&gt;
&lt;h1 id="mounting-time"&gt;Mounting time&lt;/h1&gt;
&lt;p&gt;You should still be on the filesystem details page from the previous step.
Click &lt;em&gt;Attach&lt;/em&gt; at the top right and a modal will appear with mount instructions.
The first option should use the EFS mount helper!&lt;/p&gt;
&lt;p&gt;For me, it looks like &lt;code&gt;sudo mount -t efs -o tls fs-0baabc62763375bb1:/ efs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Go back to your Fedora instance, create a mount point, and create the volume:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mkdir /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mount -t efs -o tls fs-0baabc62763375bb1:/ /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; df -hT &lt;span class="p"&gt;|&lt;/span&gt; grep efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;127.0.0.1:/ nfs4 8.0E 0 8.0E 0% /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We did it! 🎉&lt;/p&gt;
&lt;p&gt;We see &lt;code&gt;127.0.0.1&lt;/code&gt; here because efs-utils uses stunnel to handle the encryption between your instance and the EFS storage system.&lt;/p&gt;
&lt;p&gt;The disk was mounted by root, so we can add a &lt;code&gt;-o user=fedora&lt;/code&gt; to give our Fedora user permissions to write files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; umount /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mount -t efs -o &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fedora,tls fs-0baabc62763375bb1:/ /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; touch /mnt/efs/test2.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; stat /mnt/efs/test2.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; File: /mnt/efs/test2.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Size: 0 	Blocks: 8 IO Block: 1048576 regular empty file
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Device: 0,54	Inode: 17657675890899444015 Links: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Access: (0644/-rw-r--r--) Uid: ( 1000/ fedora) Gid: ( 1000/ fedora)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Context: system_u:object_r:nfs_t:s0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Access: 2023-09-13 19:14:23.308000000 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Modify: 2023-09-13 19:14:23.308000000 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Change: 2023-09-13 19:14:23.308000000 +0000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Birth: -
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, &lt;em&gt;efs-utils&lt;/em&gt; uses encrypted communication by default, which is great.
There may be some situations where you don&amp;rsquo;t need encrypted communications or you don&amp;rsquo;t want the overhead.
In that case, drop the &lt;code&gt;-o tls&lt;/code&gt; option from the mount command and you&amp;rsquo;ll mount the volume unencrypted.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo umount /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mount -t efs -o &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fedora fs-0baabc62763375bb1:/ /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; df -hT &lt;span class="p"&gt;|&lt;/span&gt; grep efs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;fs-0baabc62763375bb1.efs.us-east-2.amazonaws.com:/ nfs4 8.0E 0 8.0E 0% /mnt/efs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="extra-credit"&gt;Extra credit&lt;/h1&gt;
&lt;p&gt;You can get fancy with &lt;a href="https://docs.aws.amazon.com/efs/latest/ug/create-access-point.html"&gt;access points&lt;/a&gt; that allow you to carve up your EFS storage and only let certain instances mount certain parts of the filesystem.
So instance A might only be able to mount &lt;code&gt;/files/hr&lt;/code&gt; while instance B can only mount &lt;code&gt;/documents&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It would also be a good idea to take an inventory of your security groups and ensure the least amount of instances can reach your EFS volume as possible.
Much of the work I did in this post was just for testing.
A good plan might be to make a security group for your EFS volume and only allow inbound traffic from security groups which should access it.
That would allow you to gather up all of your instances into different security groups and limit access.&lt;/p&gt;
&lt;p&gt;Also, be aware of the &lt;a href="https://aws.amazon.com/efs/pricing/"&gt;EFS pricing&lt;/a&gt;! 💸&lt;/p&gt;
&lt;p&gt;You are billed not only for how much storage you use, but also on requests.
Different requests are priced differently depending on access frequency.
Backups are also &lt;strong&gt;enabled by default&lt;/strong&gt; at $0.05/GB-month!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Why Ohio?
I&amp;rsquo;m mainly doing it to irritate &lt;a href="https://www.lastweekinaws.com/"&gt;Corey Quinn&lt;/a&gt;. 🤭
Any region you prefer should be fine.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Extra icanhazip services going offline</title><link>https://major.io/p/extra-icanhaz-services-going-offline/</link><pubDate>Thu, 28 Jul 2022 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/extra-icanhaz-services-going-offline/</guid><description>&lt;p&gt;Every great thing has its end, and the extra services I launched along with &lt;a href="https://icanhazip.com"&gt;icanhazip.com&lt;/a&gt; are no exception.
I started &lt;a href="https://icanhazip.com"&gt;icanhazip.com&lt;/a&gt; way back in 2009 and detailed much of the history when I &lt;a href="https://major.io/2021/06/06/a-new-future-for-icanhazip/"&gt;transferred ownership to Cloudflare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The extra services, such as &lt;code&gt;icanhazptr.com&lt;/code&gt;, &lt;code&gt;icanhaztrace.com&lt;/code&gt;, and &lt;code&gt;icanhaztraceroute.com&lt;/code&gt;, came online in 2013 and they weren&amp;rsquo;t part of the Cloudflare transfer.
These services add extra challenges since they need IPv6 connectivity and they don&amp;rsquo;t play well with containers.
Relative to icanhazip.com, these services receive very little traffic.&lt;/p&gt;
&lt;p&gt;As much as I&amp;rsquo;d like to keep running these sites, &lt;strong&gt;the extra services will go offline on August 17, 2022&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="and-if-you-cant-live-without-it"&gt;And if you can&amp;rsquo;t live without it&lt;/h2&gt;
&lt;p&gt;Still need PTR record lookups and traceroutes on your network?
All of the code is on GitHub in &lt;a href="https://github.com/major/icanhaz"&gt;major/icanhaz&lt;/a&gt;.
To run it, simply execute the &lt;code&gt;icanhaz.py&lt;/code&gt; script on your machine.&lt;/p&gt;
&lt;p&gt;You can also use &lt;a href="https://gunicorn.org/"&gt;gunicorn&lt;/a&gt; with a command like this one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gunicorn icanhaz:app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also get very fancy with a systemd unit that exposes a UNIX socket:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Gunicorn instance to serve icanhaz&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/icanhaz&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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/gunicorn --workers 4 --bind unix:icanhaz.sock -m 007 icanhaz:app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then configure &lt;a href="https://www.nginx.com/"&gt;nginx&lt;/a&gt; to serve traffic from the socket:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://unix:/opt/icanhaz/icanhaz.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks for all the support over the last 13 years! 🫂&lt;/p&gt;</description></item><item><title>Install Azure CLI on Fedora 35</title><link>https://major.io/p/install-azure-cli-fedora-35/</link><pubDate>Mon, 01 Nov 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/install-azure-cli-fedora-35/</guid><description>&lt;p&gt;I started work on packaging the &lt;a href="https://github.com/Azure/azure-cli"&gt;Azure CLI&lt;/a&gt; and all of its components in Fedora
back in July 2021 and the work finally finished just as the Fedora 35
development cycled ended. This required plenty of packaging work and I was
thankful for all the advice I received along the way from experienced Fedora
packagers.&lt;/p&gt;
&lt;h2 id="installing-azure-cli"&gt;Installing Azure CLI&lt;/h2&gt;
&lt;p&gt;Make sure you&amp;rsquo;re on Fedora 35 or later first. Then install &lt;code&gt;azure-cli&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dnf -y install azure-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; az --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;azure-cli 2.29.0 *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;core 2.29.0 *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;telemetry 1.0.6
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Extensions:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;aks-preview 0.5.29
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Python location &amp;#39;/usr/bin/python3&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Extensions directory &amp;#39;/home/major/.azure/cliextensions&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="authenticate-with-azure"&gt;Authenticate with Azure&lt;/h2&gt;
&lt;p&gt;You have two methods for authenticating with Azure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;via a web browser (good for desktops and workstations)&lt;/li&gt;
&lt;li&gt;via a device code (good for remote servers or virtual machines)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To authenticate with a browser, type &lt;code&gt;az login&lt;/code&gt; and complete the steps in the
browser window that appears.&lt;/p&gt;
&lt;p&gt;Otherwise, run &lt;code&gt;az login --use-device-code&lt;/code&gt; and complete the steps manually
using the URL and the access code provided on the command line.&lt;/p&gt;
&lt;p&gt;If everything works well, you should get a message saying &lt;code&gt;You have logged in.&lt;/code&gt;
followed by some information about your account in JSON format.&lt;/p&gt;
&lt;h2 id="to-the-cloud"&gt;To the cloud!&lt;/h2&gt;
&lt;p&gt;Most resources in Azure live inside a resource group, so let&amp;rsquo;s try to create one
to ensure the CLI is working and authenticated properly:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ az group create --location eastus --resource-group major-testing-eastus
{
 &amp;#34;id&amp;#34;: &amp;#34;/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/major-testing-eastus&amp;#34;,
 &amp;#34;location&amp;#34;: &amp;#34;eastus&amp;#34;,
 &amp;#34;managedBy&amp;#34;: null,
 &amp;#34;name&amp;#34;: &amp;#34;major-testing-eastus&amp;#34;,
 &amp;#34;properties&amp;#34;: {
 &amp;#34;provisioningState&amp;#34;: &amp;#34;Succeeded&amp;#34;
 },
 &amp;#34;tags&amp;#34;: null,
 &amp;#34;type&amp;#34;: &amp;#34;Microsoft.Resources/resourceGroups&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Perfect! 🎉&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/-mMoKrWFBjw"&gt;Sergi Marló&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>A new future for icanhazip</title><link>https://major.io/p/a-new-future-for-icanhazip/</link><pubDate>Sun, 06 Jun 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/a-new-future-for-icanhazip/</guid><description>&lt;p&gt;In the summer of 2009, I had an idea. My workdays were spent deploying tons of
cloud infrastructure as Rackspace acquired Slicehost and we rushed to keep up
with the constant demands for new infrastructure from our customers. Working
quickly led to challenges with hardware and networking.&lt;/p&gt;
&lt;p&gt;That was a time where the &lt;a href="https://en.wikipedia.org/wiki/I_Can_Has_Cheezburger%3F"&gt;I Can Has Cheeseburger&lt;/a&gt; meme was red hot just about
everywhere. We needed a way to quickly check the public-facing IP address of
lots of backend infrastructure and our customers sometimes needed that
information, too.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when &lt;a href="https://icanhazip.com"&gt;icanhazip.com&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;It has always been simple site that returns your external IP address and nothing
else. No ads. No trackers. No goofy requirements. Sure, if you looked hard
enough, you could spot my attempt at jokes in the HTTP headers. Other than that,
the site had a narrow use case and started out mainly as an internal tool.&lt;/p&gt;
&lt;h2 id="thats-when-things-got-a-little-crazy"&gt;That&amp;rsquo;s when things got a little crazy&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.lifehacker.com.au/2011/03/find-your-public-ip-anywhere-with-icanhazip-com/"&gt;Lifehacker&amp;rsquo;s Australian site&lt;/a&gt; featured a post about icanhazip.com and traffic
went through the roof. My little Slicehost instance was inundated and I quickly
realized my Apache and Python setup was not going to work long term.&lt;/p&gt;
&lt;p&gt;I migrated to nginx and set up nginx to answer the requests by itself and
removed the Python scripts. The load on my small cloud instances came down
quickly and I figured the issue would be resolved for a while.&lt;/p&gt;
&lt;p&gt;Fast forward to 2015 and icanhazip.com was serving well over 100M requests per
day. My cloud instances were getting crushed again, so I deployed more with
round robin DNS. &lt;em&gt;(My budget for icanhazip is tiny.)&lt;/em&gt; Once that was overloaded,
I moved to Hetzner in Germany since I could get physical servers there with
better network cards along with unlimited traffic.&lt;/p&gt;
&lt;p&gt;The Hetzner servers were not expensive, but I was paying almost $200/month to
keep the site afloat and the site made no money. I met some people who worked
for Packet.net (now Equinix Metal) and they offered to sponsor the site. This
brought my expenses down a lot and I deployed icanhazip.com on one server at
Packet.&lt;/p&gt;
&lt;p&gt;The site soon crossed 500M requests per day and I deployed a second server.
Traffic was still overloading the servers. I didn&amp;rsquo;t want to spin up more servers
at Packet since they were already helping me out quite a bit, so I decided to
look under the hood of the kernel and make some improvements.&lt;/p&gt;
&lt;p&gt;I learned more than I ever wanted to know about TCP backlogs, TCP/VLAN
offloading, packet coalescing, IRQ balancing, and a hundred other things. Some
Red Hat network experts helped me (before I joined the company) to continue
tweaking. The site was running well after that and I was thankful for the
support.&lt;/p&gt;
&lt;h2 id="even-crazier-still"&gt;Even crazier still&lt;/h2&gt;
&lt;p&gt;Soon the site exceeded 1B requests per day. I went back to the people who helped
me at Red Hat and after they looked through everything I sent, their response
was similar to the well-known line from Jaws: &lt;em&gt;&amp;ldquo;You&amp;rsquo;re gonna need a bigger
boat.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I languished on Twitter about how things were getting out of control and someone
from Cloudflare reached out to help. We configured Cloudflare to filter traffic
in front of the site and this reduced the impact from SYN floods, half-open TLS
connections, and other malicious clients that I couldn&amp;rsquo;t even see when I hosted
the site on my own.&lt;/p&gt;
&lt;p&gt;Later, Cloudflare launched workers and my contact there said I should consider
it since my responses were fairly simple and the workers product would handle it
well. The cost for workers looked horrifying at my traffic levels, but the folks
at Cloudflare offered to run my workers for free. Their new product was getting
bucket loads of traffic and I was able to scale the site even further.&lt;/p&gt;
&lt;p&gt;In 2021, the traffic I once received in a month started arriving in 24 hours.
The site went from 1B requests per day to 30-35B requests per day over a
weekend. Almost all of that traffic came from several network blocks in China.
Through all of this, Cloudflare&amp;rsquo;s workers kept chugging along and my response
times barely moved. I was grateful for the help.&lt;/p&gt;
&lt;p&gt;Cloudflare was doing a lot for me and I wanted to curb some of the malicious
traffic to reduce the load on their products. I tried many times to reach out to
the email addresses on the Chinese ASNs and couldn&amp;rsquo;t make contact with anyone.
Some former coworkers told me that my chances of changing that traffic or
getting a response to an abuse request was near zero.&lt;/p&gt;
&lt;h2 id="malware-almost-ended-everything"&gt;Malware almost ended everything&lt;/h2&gt;
&lt;p&gt;There was a phase for a few years where malware authors kept writing malware
that would call out to icanhazip.com to find out what they had infected. If they
could find out the external IP address of the systems they had compromised, they
could quickly assess the value of the target. Upatre was the first, but many
followed after that.&lt;/p&gt;
&lt;p&gt;I received emails from companies, US state governments, and even US three letter
agencies (TLA). Most were very friendly and they had lots of questions. I explained how the site worked and rarely heard a lot more communication after that.&lt;/p&gt;
&lt;p&gt;Not all of the interactions were positive, however. One CISO of a US state
emailed me and threatened all kinds of legal action claiming that icanhazip.com
was involved in a malware infection in his state&amp;rsquo;s computer systems. I tried
repeatedly to explain how the site worked and that the malware authors were
calling out to my site and I was powerless to stop it.&lt;/p&gt;
&lt;p&gt;Along the way, many of my hosting providers received abuse emails about the
site. I was using a colocation provider in Dallas for a while and the tech
called me about an abuse email:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;So we got another abuse email for you,&amp;rdquo; they said.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;For icanhazip.com?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Yes. I didn&amp;rsquo;t know that was running here, I use it all the time!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Thanks! What do we do?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Your site just returns IP addresses, right?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Yes, that&amp;rsquo;s it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;You know what, I&amp;rsquo;ll write up a generic response and just start replying to
these idiots for you from now on.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There were many times where I saw a big traffic jump and I realized the traffic
was coming from the same ASN, and likely from the same company. I tried reaching
out to these companies when I saw it but they rarely ever replied. Some even
became extremely hostile to my emails.&lt;/p&gt;
&lt;p&gt;The passion left in my passion project started shrinking by the day.&lt;/p&gt;
&lt;h2 id="the-fun-totally-dried-up"&gt;The fun totally dried up&lt;/h2&gt;
&lt;p&gt;Seeing that over 90% of my traffic load was malicious and abusive was
frustrating. Dealing with the abuse emails and complaints was worse.&lt;/p&gt;
&lt;p&gt;I built the site originally as just a utility for my team to use, but then it
grew and it was fun to find new ways to handle the load without increasing cost.
Seeing 2 petabytes of data flowing out per month and knowing that almost all of
it was garbage pushed me over the line. I knew I needed a change.&lt;/p&gt;
&lt;p&gt;I received a few small offers from various small companies ($5,000 or less), but
I realized that the money wasn&amp;rsquo;t what I was after. I wanted someone to run the
site and help the information security industry to stop some of these malicious
actors.&lt;/p&gt;
&lt;h2 id="icanhazipcom-lives-on-at-cloudflare"&gt;icanhazip.com lives on at Cloudflare&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve worked closely with my contacts at Cloudflare for a long time and they&amp;rsquo;ve
always jumped in to help me when something wasn&amp;rsquo;t working well. Their
sponsorship of icanhazip.com has saved me tens of thousands of dollars per
month. It has also managed to keep the site alive even under horrific traffic
load.&lt;/p&gt;
&lt;p&gt;I made this decision because Cloudflare has always done right by me and they&amp;rsquo;ve
pledged not only to keep the site running, but to work through the traffic load
and determine how to stop the malicious traffic. Their coordinated work with
other companies to stop compromised machines from degrading the performance of
so many sites was a great selling point for me.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious, Cloudflare did pay me for the site. We made a deal for them
to pay me $8.03; the cost of the domain registration. The goal was never to make
money from the site (although I did get about $75 in total donations from 2009 to
2021). The goal was to provide a service to the internet. Cloudflare has helped
me do that and they will continue to do it as the new owners and operators of
icanhazip.com.&lt;/p&gt;
&lt;h2 id="gratitude"&gt;Gratitude&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d like to thank everyone who has helped me with icanhazip.com along the way.
Tons of people stepped up to help with hosting and server optimization. Hosting
providers helped me field an onslaught of abuse requests and DDoS attacks. Most
of all, thanks to the people who used the site and helped to promote it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/6p6WDodvR2Y"&gt;Sebastien Gabriel on Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Efficient emojis with rofimoji</title><link>https://major.io/p/efficient-emojis-with-rofimoji/</link><pubDate>Sat, 15 May 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/efficient-emojis-with-rofimoji/</guid><description>&lt;p&gt;Emojis brighten up any message or document. They also serve as excellent methods
for testing whether your application handles strings appropriately. &lt;em&gt;(This can
be a lot of fun.)&lt;/em&gt; 🤭&lt;/p&gt;
&lt;p&gt;I constantly obsess with efficiency and shortening the time and effort required
to get my work done. I noticed that I could type short text emoticons like &lt;em&gt;:)&lt;/em&gt;
and &lt;em&gt;;)&lt;/em&gt; so much faster than I could use emojis. This simply would not do. 😉&lt;/p&gt;
&lt;h2 id="first-attempts"&gt;First attempts&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.emojicopy.com/"&gt;Emoji Copy&lt;/a&gt; was my first try at getting the emojis I needed quickly. The site
also offers a native emoji mode which allows you to see if your system is
handling emojis correctly. The site loads quickly and the search finds emojis in
a flash, but it was annoying to open a browser tab just to find an emoji. 🤦🏻‍♂️&lt;/p&gt;
&lt;p&gt;The GNOME Extension called &lt;a href="https://extensions.gnome.org/extension/1162/emoji-selector/"&gt;Emoji Selector&lt;/a&gt; made the selection process faster,
but I moved from GNOME to i3 and lost my GNOME extensions. 🤷🏻‍♂️&lt;/p&gt;
&lt;p&gt;Other methods, such as the &lt;a href="https://fedoramagazine.org/boost-typing-emoji-fedora-28-workstation/"&gt;Emoji input method&lt;/a&gt; and the &lt;a href="https://mike-fabian.github.io/ibus-typing-booster/"&gt;ibus-typing-booster&lt;/a&gt;,
also worked, but I knew there had to be something more efficient than those. 🤔&lt;/p&gt;
&lt;h2 id="enter-rofimoji"&gt;Enter rofimoji&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt; launcher quickly become part of my core workflow in i3 (replacing
&lt;a href="https://tools.suckless.org/dmenu/"&gt;dmenu&lt;/a&gt;) and I was pleasantly surprised to find &lt;a href="https://github.com/fdw/rofimoji"&gt;rofimoji&lt;/a&gt; in GitHub. 🤗&lt;/p&gt;
&lt;p&gt;The rofimoji launcher follows in rofi&amp;rsquo;s footsteps and gives you quick access to
emojis. Using rofimoji is easy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bind a key combination to run &lt;code&gt;rofimoji&lt;/code&gt; &lt;em&gt;(I use Mod+E)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Type in a search term to find the perfect emoji&lt;/li&gt;
&lt;li&gt;Press enter to input it directly in the active window or shift+enter to copy
it to the clipboard&lt;/li&gt;
&lt;li&gt;🎉&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Depending on the application you&amp;rsquo;re using, you might need to mess around with
roflmoji&amp;rsquo;s &lt;code&gt;--action&lt;/code&gt; parameter. Some applications will take the emoji directly
as if you typed it from a keyboard, but most of the ones I use seem to like a
copy/paste method via the clipboard. 📋&lt;/p&gt;
&lt;p&gt;I use the &lt;code&gt;--action clipboard&lt;/code&gt; parameter and it works well across browsers and
terminals. Here&amp;rsquo;s the line from the i3 configuration file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym $mod+e exec --no-startup-id rofimoji --skin-tone light --action clipboard --rofi-args=&amp;#39;-theme solarized -font &amp;#34;hack 12&amp;#34; -width 800&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="rpms-for-fedora-centos-and-rhel"&gt;RPMs for Fedora, CentOS, and RHEL&lt;/h2&gt;
&lt;p&gt;At the moment, rofimoji is not packaged for Fedora, CentOS, or Red Hat
Enterprise Linux (RHEL). However, you can install it from my &lt;a href="https://copr.fedorainfracloud.org/coprs/mhayden/packages/"&gt;COPR packages
repository&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo dnf copr enable mhayden/packages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo dnf install python3-rofimoji
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enjoy! 🍰&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/dGrfSEcwK74"&gt;Kelvin Yan on Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Changes in RHEL 7 Security Technical Implementation Guide Version 1, Release 3</title><link>https://major.io/p/changes-in-rhel-7-security-technical-implementation-guide-version-1-release-3/</link><pubDate>Thu, 02 Nov 2017 15:00:25 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/changes-in-rhel-7-security-technical-implementation-guide-version-1-release-3/</guid><description>&lt;p&gt;The latest release of the Red Hat Enterprise Linux Security Technical Implementation Guide (STIG) &lt;a href="https://public.cyber.mil/stigs/"&gt;was published last week&lt;/a&gt;.
This release is Version 1, Release 3, and it contains four main changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V-77819 - Multifactor authentication is required for graphical logins&lt;/li&gt;
&lt;li&gt;V-77821 - Datagram Congestion Control Protocol (DCCP) kernel module must be disabled&lt;/li&gt;
&lt;li&gt;V-77823 - Single user mode must require user authentication&lt;/li&gt;
&lt;li&gt;V-77825 - Address space layout randomization (ASLR) must be enabled&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deep-dive"&gt;Deep dive&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s break down this list to understand what each one means.&lt;/p&gt;
&lt;h3 id="v-77819---multifactor-authentication-is-required-for-graphical-logins"&gt;V-77819 - Multifactor authentication is required for graphical logins&lt;/h3&gt;
&lt;p&gt;This requirement improves security for graphical logins and extends the existing requirements for multifactor authentication for logins (see V-71965, V-72417, and V-72427). The STIG recommends smartcards (since the US Government often uses &lt;a href="https://en.wikipedia.org/wiki/Common_Access_Card"&gt;CAC cards&lt;/a&gt; for multifactor authentication), and this is a good idea for high security systems.&lt;/p&gt;
&lt;p&gt;I use &lt;a href="https://www.yubico.com/products/yubikey-hardware/yubikey4/"&gt;Yubikey 4&amp;rsquo;s&lt;/a&gt; as smartcards in most situations and they work anywhere you have available USB slots.&lt;/p&gt;
&lt;h3 id="v-77821---datagram-congestion-control-protocol-dccp-kernel-module-must-be-disabled"&gt;V-77821 - Datagram Congestion Control Protocol (DCCP) kernel module must be disabled&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Datagram_Congestion_Control_Protocol"&gt;DCCP&lt;/a&gt; is often used as a congestion control mechanism for UDP traffic, but it isn&amp;rsquo;t used that often in modern networks. There have been &lt;a href="https://threatpost.com/impact-of-new-linux-kernel-dccp-vulnerability-limited/123863/"&gt;vulnerabilities&lt;/a&gt; in the past that are mitigated by disabling DCCP, so it&amp;rsquo;s a good idea to disable it unless you have a strong reason for keeping it enabled.&lt;/p&gt;
&lt;p&gt;The ansible-hardening role has been updated to &lt;a href="https://docs.openstack.org/ansible-hardening/latest/rhel7/domains/kernel.html#v-77821"&gt;disable the DCCP kernel module by default&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="v-77823---single-user-mode-must-require-user-authentication"&gt;V-77823 - Single user mode must require user authentication&lt;/h3&gt;
&lt;p&gt;Single user mode is often used in emergency situations where the server cannot boot properly or an issue must be repaired without a fully booted server. This mode can only be used at the server&amp;rsquo;s physical console, serial port, or via out-of-band management (DRAC, iLO, and IPMI). Allowing single-user mode access without authentication is a serious security risk.&lt;/p&gt;
&lt;p&gt;Fortunately, every distribution supported by the ansible-hardening role already has authentication requirements for single user mode in place. The ansible-hardening role does not make any adjustments to the single user mode unit file since any untested adjustment could cause a system to have problems booting.&lt;/p&gt;
&lt;h3 id="v-77825---address-space-layout-randomization-aslr-must-be-enabled"&gt;V-77825 - Address space layout randomization (ASLR) must be enabled&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Address_space_layout_randomization"&gt;ASLR&lt;/a&gt; is a handy technology that makes it more difficult for attackers to guess where a particular program is storing data in memory. It&amp;rsquo;s not perfect, but it certainly raises the difficulty for an attacker. There are multiple settings for this variable and the &lt;a href="https://www.kernel.org/doc/Documentation/sysctl/kernel.txt"&gt;kernel documentation for sysctl&lt;/a&gt; has some brief explanations for each setting (search for &lt;code&gt;randomize_va_space&lt;/code&gt; on the page).&lt;/p&gt;
&lt;p&gt;Every distribution supported by the ansible-hardening role is already setting &lt;code&gt;kernel.randomize_va_space=2&lt;/code&gt; by default, which applies randomization for the basic parts of process memory (such as shared libraries and the stack) as well as the heap. The ansible-hardening role will ensure that the default setting is maintained.&lt;/p&gt;
&lt;h2 id="ansible-hardening-is-already-up-to-date"&gt;ansible-hardening is already up to date&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re already using the ansible-hardening role&amp;rsquo;s master branch, these changes are &lt;a href="https://github.com/openstack/ansible-hardening/commit/782bb48c14c03aedaefcaf421fd5935ef5f561b8"&gt;already in place&lt;/a&gt;! Try out the new updates and &lt;a href="https://bugs.launchpad.net/openstack-ansible/+filebug"&gt;open a bug report&lt;/a&gt; if you find any problems.&lt;/p&gt;</description></item><item><title>Old role, new name: ansible-hardening</title><link>https://major.io/p/old-role-new-name-ansible-hardening/</link><pubDate>Tue, 27 Jun 2017 20:49:44 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/old-role-new-name-ansible-hardening/</guid><description>&lt;p&gt;The interest in the &lt;a href="https://github.com/openstack/openstack-ansible-security"&gt;openstack-ansible-security&lt;/a&gt; role has taken off faster than I expected, and one piece of constant feedback I received was around the name of the role. Some users were unsure if they needed to use the role in an OpenStack cloud or if the OpenStack-Ansible project was required.&lt;/p&gt;
&lt;p&gt;The role works everywhere - OpenStack cloud or not. I started a &lt;a href="http://lists.openstack.org/pipermail/openstack-dev/2017-May/116922.html"&gt;mailing list thread&lt;/a&gt; on the topic and we eventually settled on a new name: &lt;a href="https://github.com/openstack/ansible-hardening"&gt;ansible-hardening&lt;/a&gt;! The updated documentation is &lt;a href="https://docs.openstack.org/developer/ansible-hardening/"&gt;already available&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The old openstack-ansible-security role is being retired and it will not receive any additional updates. Moving to the new role is easy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;em&gt;ansible-hardening&lt;/em&gt; with &lt;code&gt;ansible-galaxy&lt;/code&gt; (or &lt;code&gt;git clone&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Change your playbooks to use the ansible-hardening role&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There&amp;rsquo;s no need to change any variable names or tags - they are all kept the same in the new role.&lt;/p&gt;
&lt;p&gt;As always, if you have questions or comments about the role, drop by &lt;code&gt;#openstack-ansible&lt;/code&gt; on Freenode IRC or &lt;a href="https://bugs.launchpad.net/openstack-ansible/+filebug"&gt;open a bug in Launchpad&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Fixing OpenStack noVNC consoles that ignore keyboard input</title><link>https://major.io/p/fixing-openstack-novnc-consoles-that-ignore-keyboard-input/</link><pubDate>Thu, 18 May 2017 16:58:56 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/fixing-openstack-novnc-consoles-that-ignore-keyboard-input/</guid><description>&lt;p&gt;I opened up a noVNC console to a virtual machine today in my OpenStack cloud but found that the console wouldn&amp;rsquo;t take keyboard input. The &lt;strong&gt;Send Ctrl-Alt-Del&lt;/strong&gt; button in the top right of the window worked just fine, but I couldn&amp;rsquo;t type anywhere in the console. This happened on an Ocata OpenStack cloud deployed with &lt;a href="https://github.com/openstack/openstack-ansible"&gt;OpenStack-Ansible&lt;/a&gt; on CentOS 7.&lt;/p&gt;
&lt;h2 id="test-the-network-path"&gt;Test the network path&lt;/h2&gt;
&lt;p&gt;The network path to the console is a little deep for this deployment, but here&amp;rsquo;s a quick explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My laptop connects to HAProxy&lt;/li&gt;
&lt;li&gt;HAProxy sends the traffic to the nova-novncproxy service&lt;/li&gt;
&lt;li&gt;nova-novncproxy connects to the correct VNC port on the right hypervisor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If all of that works, I get a working console! I knew the network path was set up correctly because I could see the console in my browser.&lt;/p&gt;
&lt;p&gt;My next troubleshooting step was to dump network traffic with &lt;code&gt;tcpdump&lt;/code&gt; on the hypervisor itself. I dumped the traffic on port 5900 (which was the VNC port for this particular instance) and watched the output. Whenever I wiggled the mouse over the noVNC console in my browser, I saw a flurry of network traffic. The same thing happened if I punched lots of keys on the keyboard. At this point, it was clear that the keyboard input was making it to the hypervisor, but it wasn&amp;rsquo;t being handled correctly.&lt;/p&gt;
&lt;h2 id="test-the-console"&gt;Test the console&lt;/h2&gt;
&lt;p&gt;Next, I opened up &lt;code&gt;virt-manager&lt;/code&gt;, connected to the hypervisor, and opened a connection to the instance. The keyboard input worked fine there. I opened up &lt;code&gt;remmina&lt;/code&gt; and connected via plain old VNC. The keyboard input worked fine there, too!&lt;/p&gt;
&lt;h2 id="investigate-in-the-virtual-machine"&gt;Investigate in the virtual machine&lt;/h2&gt;
&lt;p&gt;The system journal in the virtual machine had some interesting output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kernel: atkbd serio0: Unknown key released (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
kernel: atkbd serio0: Unknown key released (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
kernel: atkbd serio0: Unknown key pressed (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
kernel: atkbd serio0: Unknown key pressed (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
kernel: atkbd serio0: Unknown key released (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
kernel: atkbd serio0: Unknown key released (translated set 2, code 0x0 on isa0060/serio0).
kernel: atkbd serio0: Use &amp;#39;setkeycodes 00 &amp;lt;keycode&amp;gt;&amp;#39; to make it known.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It seems like my keyboard input was being lost in translation - literally. I have a US layout keyboard (Thinkpad X1 Carbon) and the virtual machine was configured with the &lt;code&gt;en-us&lt;/code&gt; keymap:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# virsh dumpxml 4 | grep vnc
 &amp;lt;graphics type=&amp;#39;vnc&amp;#39; port=&amp;#39;5900&amp;#39; autoport=&amp;#39;yes&amp;#39; listen=&amp;#39;192.168.250.41&amp;#39; keymap=&amp;#39;en-us&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A thorough Googling session revealed that it is &lt;a href="https://github.com/novnc/noVNC/issues/666#issuecomment-248303186"&gt;not recommended to set a keymap for virtual machines&lt;/a&gt; in libvirt in most situations. I set the &lt;code&gt;nova_console_keymap&lt;/code&gt; variable in &lt;code&gt;/etc/openstack_deploy/user_variables.yml&lt;/code&gt; to an empty string:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nova_console_keymap: &amp;#39;&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I redeployed the nova service using the OpenStack-Ansible playbooks:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openstack-ansible os-nova-install.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that was done, I powered off the virtual machine and powered it back on. (This is needed to ensure that the libvirt changes go into effect for the virtual machine.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Great success!&lt;/strong&gt; The keyboard was working in the noVNC console once again!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://commons.wikimedia.org/wiki/File:Televideo925Terminal.jpg"&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>RHEL 7 STIG v1 updates for openstack-ansible-security</title><link>https://major.io/p/rhel-7-stig-v1-updates-for-openstack-ansible-security/</link><pubDate>Wed, 05 Apr 2017 17:46:17 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/rhel-7-stig-v1-updates-for-openstack-ansible-security/</guid><description>&lt;p&gt;DISA&amp;rsquo;s final release of the Red Hat Enterprise Linux (RHEL) 7 Security Technical Implementation Guide (STIG) &lt;a href="https://public.cyber.mil/stigs/"&gt;came out a few weeks ago&lt;/a&gt; and it has plenty of improvements and changes. The openstack-ansible-security role has already been updated with these changes.&lt;/p&gt;
&lt;p&gt;Quite a few duplicated STIG controls were removed and a few new ones were added. Some of the controls in the pre-release were difficult to implement, especially those that changed parameters for PKI-based authentication.&lt;/p&gt;
&lt;p&gt;The biggest challenge overall was the renumbering. The pre-release STIG used an unusual numbering convention: RHEL-07-123456. The final version used the more standardized &amp;ldquo;V&amp;rdquo; numbers, such as V-72225. This change required a &lt;a href="https://github.com/openstack/openstack-ansible-security/commit/dccce1d5cc06985a58f0ecba4fd0d977388592b2"&gt;substantial patch&lt;/a&gt; to bring the Ansible role inline with the new STIG release.&lt;/p&gt;
&lt;p&gt;All of the &lt;a href="https://docs.openstack.org/developer/openstack-ansible-security/controls-rhel7.html"&gt;role&amp;rsquo;s documentation&lt;/a&gt; is now updated to reflect the new numbering scheme and STIG changes. The key thing to remember is that you&amp;rsquo;ll need to use &lt;code&gt;--skip-tag&lt;/code&gt; with the new STIG numbers if you need to skip certain tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; These changes won&amp;rsquo;t be backported to the &lt;code&gt;stable/ocata&lt;/code&gt; branch, so you need to use the &lt;code&gt;master&lt;/code&gt; branch to get these changes.&lt;/p&gt;
&lt;p&gt;Have feedback? Found a bug? Let us know!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IRC: &lt;code&gt;#openstack-ansible&lt;/code&gt; on Freenode IRC&lt;/li&gt;
&lt;li&gt;Bugs: &lt;a href="https://bugs.launchpad.net/openstack-ansible"&gt;LaunchPad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;a href="mailto:openstack-dev@lists.rackspace.com"&gt;openstack-dev@lists.rackspace.com&lt;/a&gt; with the subject line &lt;code&gt;[openstack-ansible][security]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Power 8 to the people</title><link>https://major.io/p/power-8-to-the-people/</link><pubDate>Thu, 22 Sep 2016 00:00:21 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/power-8-to-the-people/</guid><description>&lt;p&gt;IBM Edge 2016 is almost over and I&amp;rsquo;ve learned a lot about Power 8 this week. The performance arguments sound really interesting and some of the choices in AIX&amp;rsquo;s design seem to make a lot of sense.&lt;/p&gt;
&lt;p&gt;However, there&amp;rsquo;s one remaining barrier for me: &lt;strong&gt;Power 8 isn&amp;rsquo;t really accessible for a tinkerer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="3" loading="lazy" src="https://major.io/p/power-8-to-the-people/IBMPowerSystemE870-2.jpg"&gt;&lt;/p&gt;
&lt;h2 id="tinkering"&gt;Tinkering?&lt;/h2&gt;
&lt;p&gt;Google defines &lt;em&gt;tinkering&lt;/em&gt; as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;attempt to repair or improve something in a casual or desultory way,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;often to no useful effect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;he spent hours tinkering with the car&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When I come across a new piece of technology, I really enjoy learning how it works. I like to find its strengths and its limitations. I use that information to figure out how I might use the technology later and when I would recommend the technology for someone else to use it.&lt;/p&gt;
&lt;p&gt;To me, tinkering is simply messing around with something until I have a better understanding of how it works. Tinkering doesn&amp;rsquo;t have a finish line. Tinkering may not have a well-defined goal. However, it&amp;rsquo;s tinkering that leads to a more robust community around a particular technology.&lt;/p&gt;
&lt;p&gt;For example, take a look at the Raspberry Pi. There were plenty of other ARM systems on the market before the Pi and there are still a lot of them now. What makes the Pi different is that it&amp;rsquo;s highly accessible. You can get the newest model for $35 and there are tons of guides for running various operating systems on it. There are even more guides for how to integrate it with other items, such as sprinkler systems, webcams, door locks, and automobiles.&lt;/p&gt;
&lt;p&gt;Another example is the Intel NUC. Although the NUC isn&amp;rsquo;t the most cost-effective way to get an Intel chip on your desk, it&amp;rsquo;s powerful enough to be a small portable server that you can take with you. This opens up the door for software developers to test code wherever they are (we use them for OpenStack development), run demos at a customer location, or make multi-node clusters that fit in a laptop bag.&lt;/p&gt;
&lt;h3 id="what-makes-power-8-inaccessible-to-tinkerers"&gt;What makes Power 8 inaccessible to tinkerers?&lt;/h3&gt;
&lt;p&gt;One of the first aspects that most people notice is the cost. The &lt;a href="http://www-03.ibm.com/systems/power/hardware/s821lc/index.html"&gt;S821LC&lt;/a&gt; currently starts at around $6,000 on IBM&amp;rsquo;s site, which is a bit steep for someone who wants to learn a platform.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not saying this server should cost less - the pricing seems quite reasonable when you consider that it comes with dual 8-core Power 8 processors in a 1U form factor. It also has plenty of high speed interconnects ready for GPUs and CAPI chips. With all of that considered, $6,000 for a server like this &lt;strong&gt;sounds very reasonable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There are other considerations as well. A stripped down S821LC with two 8-core CPUs will &lt;a href="http://www-912.ibm.com/see/EnergyEstimator"&gt;consume about 406 Watts at 50% utilization&lt;/a&gt;. That&amp;rsquo;s a fair amount of power draw for a tinkerer and I&amp;rsquo;d definitely think twice about running something like that at home. When you consider the cooling that&amp;rsquo;s required, it&amp;rsquo;s even more difficult to justify.&lt;/p&gt;
&lt;h3 id="what-about-aix"&gt;What about AIX?&lt;/h3&gt;
&lt;p&gt;AIX provides some nice benefits on Power 8 systems, but it&amp;rsquo;s difficult to access as well. Put &amp;ldquo;learning AIX&amp;rdquo; into a Google search and &lt;a href="https://www.google.com/search?q=learning+AIX"&gt;look at the results&lt;/a&gt;. The first link is a &lt;a href="http://www.linuxquestions.org/questions/aix-43/cheapest-way-to-learn-aix-4175534982/"&gt;thread on LinuxQuestions.org&lt;/a&gt; where the original poster is given a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buy some IBM hardware&lt;/li&gt;
&lt;li&gt;Get in some legal/EULA gray areas with VMware&lt;/li&gt;
&lt;li&gt;Find an old Power 5/6 server that is coming offline at a business that is doing a refresh&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having access to AIX is definitely useful for tinkering, but it could be very useful for software developers. For example, if I write a script in Python and I want to add AIX support, I&amp;rsquo;ll need access to a system running AIX. It wouldn&amp;rsquo;t necessarily need to be a system with tons of performance, but it would need the functionality of a basic AIX environment.&lt;/p&gt;
&lt;h2 id="potential-solutions"&gt;Potential solutions&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d suggest two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get AIX into an accessible format, perhaps on a public cloud&lt;/li&gt;
&lt;li&gt;Make a more tinker-friendly Power 8 hardware platform&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s start with AIX. I&amp;rsquo;d gladly work with AIX in a public cloud environment where I pay some amount for the virtual machine itself plus additional licensing for AIX. It would still be valuable even if the version of AIX had limiters so that it couldn&amp;rsquo;t be used for production workloads. I would be able to access the full functionality of a running AIX environment.&lt;/p&gt;
&lt;p&gt;The hardware side leads to challenges. However, if it&amp;rsquo;s possible to do a single Power 8 SMT2 CPU in a smaller form factor, this could become possible. Perhaps these could even be CPUs with some type of defect where one or more cores are disabled. That could reduce cost while still providing the full functionality to someone who wants to tinker with Power 8.&lt;/p&gt;
&lt;p&gt;Some might argue that this defeats the point of Power 8 since it&amp;rsquo;s a high performance, purpose-built chip that crunches through some of the world&amp;rsquo;s biggest workloads. That&amp;rsquo;s a totally valid argument.&lt;/p&gt;
&lt;p&gt;However, that&amp;rsquo;s not the point.&lt;/p&gt;
&lt;p&gt;The point is to get a fully-functional Power 8 CPU - even if it has serious performance limitations - into the hands of developers who want to do amazing things with it. My hope would be that these small tests will later turn into new ways to utilize POWER systems.&lt;/p&gt;
&lt;p&gt;It could also be a way for more system administrators and developers to get experience with AIX. Companies would be able to find more people with a base level of AIX knowledge as well.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;IBM has something truly unique with Power 8. The raw performance of the chip itself is great and the door is open for even more performance through NVlink and CAPI accelerators. These features are game changers for businesses that are struggling to keep up with customer demands. A wider audience could learn about this game-changing technology if it becomes more accessible for tinkering.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://commons.wikimedia.org/wiki/File:IBMPowerSystemE870-2.jpg"&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Preventing critical services from deploying on the same OpenStack host</title><link>https://major.io/p/preventing-critical-services-from-deploying-on-the-same-openstack-host/</link><pubDate>Tue, 09 Aug 2016 17:07:35 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/preventing-critical-services-from-deploying-on-the-same-openstack-host/</guid><description>&lt;p&gt;OpenStack&amp;rsquo;s compute service, nova, manages all of the virtual machines within a OpenStack cloud. When you ask nova to build an instance, or a group of instances, nova&amp;rsquo;s scheduler system determines which hypervisors should run each instance. The scheduler uses &lt;a href="http://docs.openstack.org/mitaka/config-reference/compute/scheduler.html#filters"&gt;filters&lt;/a&gt; to figure out where each instance belongs.&lt;/p&gt;
&lt;p&gt;However, there are situations where the scheduler might put more than one of your instances on the same host, especially when resources are constrained. This can be a problem when you deploy certain highly available applications, like MariaDB and Galera. If more than one of your database instances landed on the same physical host, a failure of that physical host could take down more than one database instance.&lt;/p&gt;
&lt;h2 id="filters-to-the-rescue"&gt;Filters to the rescue&lt;/h2&gt;
&lt;p&gt;The scheduler offers the &lt;a href="http://docs.openstack.org/mitaka/config-reference/compute/scheduler.html#servergroupantiaffinityfilter"&gt;ServerGroupAntiAffinityFilter&lt;/a&gt; filter for these deployments. This allows a user to create a server group, apply a policy to the group, and then begin adding servers to that group.&lt;/p&gt;
&lt;p&gt;If the scheduler filter can&amp;rsquo;t find a way to fulfill the anti-affinity request (which often happens if the hosts are low on resources), it will fail the entire build transaction with an error. In other words, unless the entire request can be fulfilled, it won&amp;rsquo;t be deployed.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how this works in action on an OpenStack Mitaka cloud deployed with &lt;a href="http://docs.openstack.org/developer/openstack-ansible/"&gt;OpenStack-Ansible&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="creating-a-server-group"&gt;Creating a server group&lt;/h2&gt;
&lt;p&gt;We can use the &lt;a href="http://docs.openstack.org/user-guide/common/cli-install-openstack-command-line-clients.html"&gt;openstackclient&lt;/a&gt; tool to create our server group:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ openstack server group create --policy anti-affinity db-production
+----------+--------------------------------------+
| Field | Value |
+----------+--------------------------------------+
| id | cd234914-980a-42f2-b77c-602a7cc0080f |
| members | |
| name | db-production |
| policies | anti-affinity |
+----------+--------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ve told nova that we want all of the instances in the &lt;code&gt;db-production&lt;/code&gt; group to land on different OpenStack hosts. I&amp;rsquo;ll copy the &lt;code&gt;id&lt;/code&gt; to my clipboard since I&amp;rsquo;ll need that UUID for the next step.&lt;/p&gt;
&lt;h2 id="adding-hosts-to-the-group"&gt;Adding hosts to the group&lt;/h2&gt;
&lt;p&gt;My small OpenStack cloud has four hypervisors, so I can add four instances to this server group:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ openstack server create \
 --flavor m1.small \
 --image &amp;#34;Fedora 24&amp;#34; \
 --nic net-id=bc8895ab-98f7-478f-a54a-36b121f7bb3f \
 --key-name personal_servers \
 --hint &amp;#34;group=cd234914-980a-42f2-b77c-602a7cc0080f&amp;#34; \
 --max 4
 prod-db
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This &lt;code&gt;server create&lt;/code&gt; command looks fairly standard, but I&amp;rsquo;ve added the &lt;code&gt;--hint&lt;/code&gt; parameter to specify that we want these servers scheduled as part of the group we just created. Also, I&amp;rsquo;ve requested for four servers to be built at the same time. After a few moments, we should have four servers active:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ openstack server list --name prod-db -c ID -c Name -c Status
+--------------------------------------+-----------+--------+
| ID | Name | Status |
+--------------------------------------+-----------+--------+
| 7e7a81f3-eb02-4751-93c1-a0de999b8423 | prod-db-4 | ACTIVE |
| b742fb58-8ea4-4e26-bfbf-645a698fbb26 | prod-db-3 | ACTIVE |
| 78c7a43c-4deb-40da-a419-e62db37ab41a | prod-db-2 | ACTIVE |
| 7b8af038-6441-40c0-87c8-0a1acced17a6 | prod-db-1 | ACTIVE |
+--------------------------------------+-----------+--------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we check the instances, they should be on different hosts:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ for i in {1..4}; do openstack server show prod-db-${i} -c hostId -f shell; done
hostid=&amp;#34;5fea4e5862f82f051e26caf926fe34bd3a9f1439b08a464f817b4c61&amp;#34;
hostid=&amp;#34;65d87faf6d9baa110afa5f2e0308781dde4629142170b2c9af1f090b&amp;#34;
hostid=&amp;#34;243f833055303efe838b3233f7ba6e1993fb28895ae11c724f10cc73&amp;#34;
hostid=&amp;#34;54df76a1e66bd8585cc3c1f8f38e8f4937456394f2409daf2a8b4c2e&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Success!&lt;/p&gt;
&lt;p&gt;If we try to build one more instance, it should fail since the scheduler cannot fulfill the anti-affinity policy applied to server group:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ openstack server create \
 --flavor m1.small \
 --image &amp;#34;Fedora 24&amp;#34; \
 --nic net-id=bc8895ab-98f7-478f-a54a-36b121f7bb3f \
 --key-name personal_servers \
 --hint &amp;#34;group=cd234914-980a-42f2-b77c-602a7cc0080f&amp;#34; \
 --wait \
 prod-db-5
Error creating server: prod-db-5
Error creating server
$ openstack server show prod-db-5 -c fault -f shell
fault=&amp;#34;{u&amp;#39;message&amp;#39;: u&amp;#39;No valid host was found. There are not enough hosts available.&amp;#39;, u&amp;#39;code&amp;#39;: 500...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The scheduler couldn&amp;rsquo;t find a valid host for a fifth server in the anti-affinity group.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://flic.kr/p/aBNPrV"&gt;&amp;ldquo;crowded houses&amp;rdquo; from jesuscm on Flickr&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>OpenStack instances come online with multiple network ports attached</title><link>https://major.io/p/openstack-instances-come-online-with-multiple-network-ports-attached/</link><pubDate>Wed, 03 Aug 2016 14:40:16 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/openstack-instances-come-online-with-multiple-network-ports-attached/</guid><description>&lt;p&gt;I ran into an interesting problem recently in my production OpenStack deployment that runs the Mitaka release. On various occasions, instances were coming online with multiple network ports attached, even though I only asked for one network port.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;If I issued a build request for ten instances, I&amp;rsquo;d usually end up with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6 instances with one network port attached&lt;/li&gt;
&lt;li&gt;2-3 instances with two network ports attached &lt;em&gt;(not what I want)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;1-2 instances with three or four network ports attached &lt;em&gt;(&lt;strong&gt;definitely&lt;/strong&gt; not what I want)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I examined the instances with multiple network ports attached, I found that one of the network ports would be marked as &lt;em&gt;up&lt;/em&gt; while the others would be marked as &lt;em&gt;down&lt;/em&gt;. However, the IP addresses associated with those extra ports would still be associated with the instance in horizon and via the nova API. All of the network ports seemed to be fully configured on the neutron side.&lt;/p&gt;
&lt;h2 id="digging-into-neutron"&gt;Digging into neutron&lt;/h2&gt;
&lt;p&gt;The neutron API logs are fairly chatty, especially while instances are building, but I found two interesting log lines for one of my instances:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;172.29.236.41,172.29.236.21 - - [02/Aug/2016 14:03:11] &amp;#34;GET /v2.0/ports.json?tenant_id=a7b0519330ed481884431102a72dd04c&amp;amp;device_id=05eef1bb-5356-43d9-86c9-4d9854d4d46b HTTP/1.1&amp;#34; 200 2137 0.025282
172.29.236.11,172.29.236.21 - - [02/Aug/2016 14:03:15] &amp;#34;GET /v2.0/ports.json?tenant_id=a7b0519330ed481884431102a72dd04c&amp;amp;device_id=05eef1bb-5356-43d9-86c9-4d9854d4d46b HTTP/1.1&amp;#34; 200 3098 0.027803
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are two requests to create network ports for this instance and neutron is allocating ports to both requests. This would normally be just fine, but I only asked for one network port on this instance.&lt;/p&gt;
&lt;p&gt;The IP addresses making the requests are unusual, though. &lt;code&gt;172.29.236.11&lt;/code&gt; and &lt;code&gt;172.29.236.41&lt;/code&gt; are two of the hypervisors within my cloud. Why are both of them asking neutron for network ports? Only one of those hypervisors should be building my instance, not both. After checking both hypervisors, I verified that the instance was only provisioned on one of the hosts and not both.&lt;/p&gt;
&lt;h2 id="looking-at-nova-compute"&gt;Looking at nova-compute&lt;/h2&gt;
&lt;p&gt;The instance ended up on the &lt;code&gt;172.29.236.11&lt;/code&gt; hypervisor once it finished building and the logs on that hypervisor looked fine:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nova.virt.libvirt.driver [-] [instance: 05eef1bb-5356-43d9-86c9-4d9854d4d46b] Instance spawned successfully.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I logged into the &lt;code&gt;172.29.236.41&lt;/code&gt; hypervisor since it was the one that asked neutron for a port but it never built the instance. The logs there had a much different story:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[instance: 05eef1bb-5356-43d9-86c9-4d9854d4d46b] Instance failed to spawn
Traceback (most recent call last):
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/compute/manager.py&amp;#34;, line 2218, in _build_resources
 yield resources
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/compute/manager.py&amp;#34;, line 2064, in _build_and_run_instance
 block_device_info=block_device_info)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/driver.py&amp;#34;, line 2773, in spawn
 admin_pass=admin_password)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/driver.py&amp;#34;, line 3191, in _create_image
 instance, size, fallback_from_host)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/driver.py&amp;#34;, line 6765, in _try_fetch_image_cache
 size=size)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/imagebackend.py&amp;#34;, line 251, in cache
 *args, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/imagebackend.py&amp;#34;, line 591, in create_image
 prepare_template(target=base, max_size=size, *args, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/oslo_concurrency/lockutils.py&amp;#34;, line 271, in inner
 return f(*args, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/imagebackend.py&amp;#34;, line 241, in fetch_func_sync
 fetch_func(target=target, *args, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/libvirt/utils.py&amp;#34;, line 429, in fetch_image
 max_size=max_size)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/images.py&amp;#34;, line 120, in fetch_to_raw
 max_size=max_size)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/virt/images.py&amp;#34;, line 110, in fetch
 IMAGE_API.download(context, image_href, dest_path=path)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/image/api.py&amp;#34;, line 182, in download
 dst_path=dest_path)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/image/glance.py&amp;#34;, line 383, in download
 _reraise_translated_image_exception(image_id)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/image/glance.py&amp;#34;, line 682, in _reraise_translated_image_exception
 six.reraise(new_exc, None, exc_trace)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/image/glance.py&amp;#34;, line 381, in download
 image_chunks = self._client.call(context, 1, &amp;#39;data&amp;#39;, image_id)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/nova/image/glance.py&amp;#34;, line 250, in call
 result = getattr(client.images, method)(*args, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/glanceclient/v1/images.py&amp;#34;, line 148, in data
 % urlparse.quote(str(image_id)))
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/glanceclient/common/http.py&amp;#34;, line 275, in get
 return self._request(&amp;#39;GET&amp;#39;, url, **kwargs)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/glanceclient/common/http.py&amp;#34;, line 267, in _request
 resp, body_iter = self._handle_response(resp)
 File &amp;#34;/openstack/venvs/nova-13.3.0/lib/python2.7/site-packages/glanceclient/common/http.py&amp;#34;, line 83, in _handle_response
 raise exc.from_response(resp, resp.content)
ImageNotFound: Image 8feacda9-91fd-48ce-b983-54f7b6de6650 could not be found.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is one of those occasions where I was glad to find an exception in the log. The image that couldn&amp;rsquo;t be found is an image I&amp;rsquo;ve used regularly in the environment before, and I know it exists.&lt;/p&gt;
&lt;h2 id="gandering-at-glance"&gt;Gandering at glance&lt;/h2&gt;
&lt;p&gt;First off, I asked glance what it knew about the image:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ openstack image show 8feacda9-91fd-48ce-b983-54f7b6de6650
+------------------+------------------------------------------------------+
| Field | Value |
+------------------+------------------------------------------------------+
| checksum | 8de08e3fe24ee788e50a6a508235aa64 |
| container_format | bare |
| created_at | 2016-08-03T01:25:34Z |
| disk_format | qcow2 |
| file | /v2/images/8feacda9-91fd-48ce-b983-54f7b6de6650/file |
| id | 8feacda9-91fd-48ce-b983-54f7b6de6650 |
| min_disk | 0 |
| min_ram | 0 |
| name | Fedora 24 |
| owner | a7b0519330ed481884431102a72dd04c |
| properties | description=&amp;#39;&amp;#39; |
| protected | False |
| schema | /v2/schemas/image |
| size | 204590080 |
| status | active |
| tags | |
| updated_at | 2016-08-03T01:25:39Z |
| virtual_size | None |
| visibility | public |
+------------------+------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If glance knows about the image, why can&amp;rsquo;t that hypervisor build an instance with that image? While I was scratching my head, &lt;a href="https://twitter.com/cloudnull"&gt;Kevin Carter&lt;/a&gt; walked by my desk and joined in the debugging.&lt;/p&gt;
&lt;p&gt;He asked about how I had deployed glance and what storage backend I was using. I was using the regular file storage backend since I don&amp;rsquo;t have swift deployed in the environment. He asked me how many glance nodes I had (I had two) and if I was doing anything to sync the images between the glance nodes.&lt;/p&gt;
&lt;p&gt;Then it hit me.&lt;/p&gt;
&lt;p&gt;&lt;img alt="stitch_frustrated.gif" loading="lazy" src="https://major.io/p/openstack-instances-come-online-with-multiple-network-ports-attached/stitch_frustrated.gif"&gt;&lt;/p&gt;
&lt;p&gt;Although both glance nodes knew about the image (since that data is in the database), &lt;strong&gt;only one of the glance nodes had the actual image content (the actual qcow2 file) stored&lt;/strong&gt;. That means that if a hypervisor requests the image from a glance node that knows about the image but doesn&amp;rsquo;t have it stored, the hypervisor won&amp;rsquo;t be able to retrieve the image.&lt;/p&gt;
&lt;p&gt;Unfortunately, the checks go in this order on the nova-compute side:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask glance if this image exists and if this tenant can use it&lt;/li&gt;
&lt;li&gt;Configure the network&lt;/li&gt;
&lt;li&gt;Retrieve the image&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If a hypervisor rolls through steps one and two without issues, but then fails on step 3, the network port will be provisioned but won&amp;rsquo;t come up on the instance. There&amp;rsquo;s nothing that cleans up that port in the Mitaka release, so it requires manual intervention.&lt;/p&gt;
&lt;h2 id="the-fix"&gt;The fix&lt;/h2&gt;
&lt;p&gt;As a temporary workaround, I took one of the glance nodes offline so that only one glance node is being used. After hundreds of builds, all of the instances came up with only one network port attached!&lt;/p&gt;
&lt;p&gt;There are a few options for long-term fixes.&lt;/p&gt;
&lt;p&gt;I could deploy swift and put glance images into swift. That would allow me to use multiple glance nodes with the same swift backend. Another option would be to use an existing swift deployment, such as Rackspace&amp;rsquo;s Cloud Files product.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m not eager to deploy swift in my environment for now, I decided to remove the second glance node and reconfigure nova to use only one glance node. That means I&amp;rsquo;m running with only one glance node and a failure there could be highly annoying. However, that trade-off is fine with me until I can get around to deploying swift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; I&amp;rsquo;ve opened &lt;a href="https://bugs.launchpad.net/nova/+bug/1609526"&gt;a bug&lt;/a&gt; for nova so that the network ports are cleaned up if the instance fails to build.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://flic.kr/p/tfpXk"&gt;Flickr: pascalcharest&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Talk recap: The friendship of OpenStack and Ansible</title><link>https://major.io/p/talk-recap/</link><pubDate>Wed, 29 Jun 2016 03:43:21 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/talk-recap/</guid><description>&lt;p&gt;The 2016 Red Hat Summit is underway in San Francisco this week and I delivered a talk with &lt;a href="https://twitter.com/robynbergeron"&gt;Robyn Bergeron&lt;/a&gt; earlier today. Our talk, &lt;em&gt;&lt;a href="https://rh2016.smarteventscloud.com/connect/sessionDetail.ww?SESSION_ID=75675"&gt;When flexibility met simplicity: The friendship of OpenStack and Ansible&lt;/a&gt;&lt;/em&gt;, explained how Ansible can reduce the complexity of OpenStack environments without sacrificing the flexibility that private clouds offer.&lt;/p&gt;
&lt;p&gt;The talk started at the same time as lunch began and the Partner Pavilion first opened, so we had some stiff competition for attendees&amp;rsquo; attention. However, the live demo worked without issues and we had some good questions when the talk was finished.&lt;/p&gt;
&lt;p&gt;This post will cover some of the main points from the talk and I&amp;rsquo;ll share some links for the talk itself and some of the playbooks we ran during the live demo.&lt;/p&gt;
&lt;h2 id="it-is-complex-and-difficult"&gt;IT is complex and difficult&lt;/h2&gt;
&lt;p&gt;Getting resources for projects at many companies is challenging. OpenStack makes this a little easier by delivering compute, network, and storage resources on demand. However, OpenStack&amp;rsquo;s flexibility is a double-edged sword. It makes it very easy to obtain virtual machines, but it can be challenging to install and configure.&lt;/p&gt;
&lt;p&gt;Ansible reduces some of that complexity without sacrificing flexibility. Ansible comes with plenty of pre-written modules that manage an OpenStack cloud at multiple levels for multiple types of users. Consumers, operators, and deployers can save time and reduce errors by using these modules and providing the parameters that fit their environment.&lt;/p&gt;
&lt;h2 id="ansible-and-openstack"&gt;Ansible and OpenStack&lt;/h2&gt;
&lt;p&gt;Ansible and OpenStack are both open source projects that are heavily based on Python. Many of the same dependencies needed for Ansible are needed for OpenStack, so there is very little additional software required. Ansible tasks are written in YAML and the user only needs to pass some simple parameters to an existing module to get something done.&lt;/p&gt;
&lt;p&gt;Operators are in a unique position since they can use Ansible to perform typical IT tasks, like creating projects and users. They can also assign fine-grained permissions to users with roles via reusable and extensible playbooks. Deployers can use projects like &lt;a href="https://github.com/openstack/openstack-ansible"&gt;OpenStack-Ansible&lt;/a&gt; to deploy a production-ready OpenStack cloud.&lt;/p&gt;
&lt;h2 id="lets-build-something"&gt;Let&amp;rsquo;s build something&lt;/h2&gt;
&lt;p&gt;In the talk, we went through a scenario for a live demo. In the scenario, the marketing team needed a new website for a new campaign. The IT department needed to create a project and user for them, and then the marketing team needed to build a server. This required some additional tasks, such as adding ssh keys, creating a security group (with rules) and adding a new private network.&lt;/p&gt;
&lt;p&gt;The files from the live demo are up on GitHub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/major/ansible-openstack-summit-demo"&gt;major/ansible-openstack-summit-demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the &lt;code&gt;operator-prep.yml&lt;/code&gt;, we created a project and added a user to the project. That user was given the admin role so that the marketing team could have full access to their own project.&lt;/p&gt;
&lt;p&gt;From there, we went through the tasks as if we were a member of the marketing team. The &lt;code&gt;marketing.yml&lt;/code&gt; playbook went through all of the tasks to prepare for building an instance, actually building the instance, and then adding that instance to the dynamic inventory in Ansible. That playbook also verified the instance was up and performed additional configuration of the virtual machine itself - all in the same playbook.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;Robyn shared lots of ways to &lt;a href="https://www.ansible.com/community"&gt;get involved in the Ansible community&lt;/a&gt;. &lt;a href="https://www.ansible.com/ansiblefest"&gt;AnsibleFest 2016&lt;/a&gt; is rapidly approaching and the &lt;a href="https://www.openstack.org/summit/barcelona-2016/"&gt;OpenStack Summit in Barcelona&lt;/a&gt; is happening this October.&lt;/p&gt;
&lt;h2 id="downloads"&gt;Downloads&lt;/h2&gt;
&lt;p&gt;The presentation is available in a few formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://majorhayden.com/presentations/When%20flexibility%20met%20simplicity-%20The%20friendship%20of%20OpenStack%20and%20Ansible%20-%20Red%20Hat%20Summit%202016.pdf"&gt;PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.slideshare.net/MajorHayden/when-flexibility-met-simplicity-the-friendship-of-openstack-and-ansible"&gt;Slideshare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Getting started with gertty</title><link>https://major.io/p/getting-started-gertty/</link><pubDate>Wed, 11 May 2016 13:45:53 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/getting-started-gertty/</guid><description>&lt;p&gt;When you&amp;rsquo;re ready to commit code in an OpenStack project, your patch will eventually land in a &lt;a href="https://www.gerritcodereview.com/"&gt;Gerrit&lt;/a&gt; queue for review. The web interface works well for most users, but it can be challenging to use when you have a large amount of projects to monitor. I recently became a core developer on the OpenStack-Ansible project and I searched for a better solution to handle lots of active reviews.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://github.com/openstack/gertty"&gt;gertty&lt;/a&gt; can help. It&amp;rsquo;s a console-based application that helps you navigate reviews efficiently. I&amp;rsquo;ll walk you through the installation and configuration process in the remainder of this post.&lt;/p&gt;
&lt;h2 id="installing-gertty"&gt;Installing gertty&lt;/h2&gt;
&lt;p&gt;The gertty package is available via &lt;a href="https://pypi.python.org/pypi/gertty"&gt;pip&lt;/a&gt;, &lt;a href="https://github.com/openstack/gertty"&gt;GitHub&lt;/a&gt;, and various package managers for certain Linux distributions. If you&amp;rsquo;re on Fedora, just install &lt;code&gt;python-gertty&lt;/code&gt; via &lt;code&gt;dnf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this example, we will use pip:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install gertty
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;
&lt;p&gt;You will need a &lt;code&gt;.gertty.yaml&lt;/code&gt; file in your home directory for gertty to run. I have an &lt;a href="https://gist.github.com/major/6449c2eb3b17a446c3a42e34b976f6df"&gt;example on GitHub&lt;/a&gt; that gives you a good start:&lt;/p&gt;
&lt;p&gt;Be sure to change the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; parts to match your Gerrit username and password. For OpenStack&amp;rsquo;s gerrit server, you can get these credentials in the &lt;a href="https://review.openstack.org/#/settings/http-password"&gt;user settings area&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="getting-synchronized"&gt;Getting synchronized&lt;/h2&gt;
&lt;p&gt;Now that gertty is configured, start it up on the console:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ gertty
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Type a capital L (SHIFT + L) and wait for the list of projects to appear on the screen. You can choose projects to subscribe to (note that these are different than Gerrit&amp;rsquo;s watched projects) by pressing your &amp;rsquo;s&amp;rsquo; key.&lt;/p&gt;
&lt;p&gt;However, if you need to follow quite a few projects that match a certain pattern, there&amp;rsquo;s an easier way. Quit gertty (CTRL - q) and adjust the sqlite database that gertty uses:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sqlite3 .gertty.db
SQLite version 3.8.6 2014-08-15 11:46:33
Enter &amp;#34;.help&amp;#34; for usage hints.
sqlite&amp;gt; SELECT count(*) FROM project WHERE name LIKE &amp;#39;%openstack-ansible%&amp;#39;;
39
sqlite&amp;gt; UPDATE project SET subscribed=1 WHERE name LIKE &amp;#39;%openstack-ansible%&amp;#39;;
sqlite&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, I&amp;rsquo;ve subscribed to all projects that contain the string &lt;code&gt;openstack-ansible&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I can start gertty once more and wait for it to sync my new projects down to my local workstation. Keep an eye on the &lt;code&gt;Sync:&lt;/code&gt; status at the top right of the screen. It will count up as it enumerates reviews to retrieve and then count down as those reviews are downloaded.&lt;/p&gt;
&lt;p&gt;You can also create custom dashboards for gertty based on custom queries. In my example configuration file above, I have a special dashboard that contains all OpenStack-Ansible reviews. That dashboard appears whenever I press F5. You can customize these dashboards to include any custom queries that you need for your projects.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://www.flickr.com/photos/dirtyf/2191026054"&gt;Frank Taillandier&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Automated Let’s Encrypt DNS challenges with Rackspace Cloud DNS</title><link>https://major.io/p/automated-lets-encrypt-dns-challenges-with-rackspace-cloud-dns/</link><pubDate>Thu, 31 Mar 2016 19:39:50 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/automated-lets-encrypt-dns-challenges-with-rackspace-cloud-dns/</guid><description>&lt;p&gt;&lt;a href="https://letsencrypt.org/"&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; has taken the world by storm by providing free SSL certificates that can be renewed via automated methods. They have issued &lt;a href="https://letsencrypt.org/stats/"&gt;over 1.4 million certificates&lt;/a&gt; since launch in the fall of 2015.&lt;/p&gt;
&lt;p&gt;If you are not familiar with how Let&amp;rsquo;s Encrypt operates, here is an &lt;em&gt;extremely&lt;/em&gt; simple explanation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a private key&lt;/li&gt;
&lt;li&gt;Make a request for a new certificate&lt;/li&gt;
&lt;li&gt;Complete the challenge process&lt;/li&gt;
&lt;li&gt;You have a certificate!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is highly simplified, but there is &lt;a href="https://letsencrypt.org/how-it-works/"&gt;plenty of detail available&lt;/a&gt; on how the whole system works.&lt;/p&gt;
&lt;p&gt;One of the most popular challenge methods is HTTP. That involves getting a challenge string from Let&amp;rsquo;s Encrypt, placing the string at a known URL on your domain, and then waiting for verification of the challenge. The process is quick and Let&amp;rsquo;s Encrypt &lt;a href="https://letsencrypt.org/getting-started/"&gt;provides tools&lt;/a&gt; that automate much of the process for you.&lt;/p&gt;
&lt;h2 id="a-challenger-appears"&gt;A challenger appears&lt;/h2&gt;
&lt;p&gt;A DNS challenge is available in addition to the HTTP challenge. As you might imagine, this involves creating a DNS record with a string provided by Let&amp;rsquo;s Encrypt. Once the DNS record is in place, it is verified and certificates are issued. The process goes something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request a new certificate&lt;/li&gt;
&lt;li&gt;Get a challenge string&lt;/li&gt;
&lt;li&gt;Add a DNS TXT record on your domain with the challenge string as the data&lt;/li&gt;
&lt;li&gt;Wait for DNS records to appear on your DNS server&lt;/li&gt;
&lt;li&gt;Let&amp;rsquo;s Encrypt checks for the DNS record&lt;/li&gt;
&lt;li&gt;Clean up the DNS record&lt;/li&gt;
&lt;li&gt;Get a certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Wrapping automation around this method is often easier than using the HTTP method since it does not require any changes on web servers. If someone has 500 web servers but they change their DNS records through a single API with a DNS provider, it quickly becomes apparent that adding a single DNS record is much easier.&lt;/p&gt;
&lt;p&gt;In addition, the HTTP challenge method creates problems for websites which are not entirely publicly accessible yet. A stealth startup or a pre-release site could acquire a certificate without needing to allow any access into the webserver. This is also helpful for sites which will never be public facing, such as those on intranets.&lt;/p&gt;
&lt;h2 id="automating-the-process"&gt;Automating the process&lt;/h2&gt;
&lt;p&gt;After some research, I stumbled upon a project in GitHub called &lt;a href="https://github.com/lukas2511/letsencrypt.sh"&gt;letsencrypt.sh&lt;/a&gt;. The project consists of a bash script that makes all the necessary requests to Let&amp;rsquo;s Encrypt&amp;rsquo;s API for requesting and obtaining SSL certificates. However, DNS records are tricky since they are usually managed via an API or other non-trivial methods.&lt;/p&gt;
&lt;p&gt;The project provides a hook feature which allows anyone to write a script that receives data and does the necessary DNS adjustments to complete the challenge process. I wrote a hook that interfaces with &lt;a href="https://www.rackspace.com/en-us/cloud/dns"&gt;Rackspace&amp;rsquo;s Cloud DNS API&lt;/a&gt; and handles the creation of DNS records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/major/letsencrypt-rackspace-hook"&gt;GitHub: letsencrypt-rackspace-hook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the installation and configuration instructions are in the &lt;a href="https://github.com/major/letsencrypt-rackspace-hook/blob/master/README.rst"&gt;main README file&lt;/a&gt; within the repository. You can begin issuing certificates with DNS challenges in a few minutes.&lt;/p&gt;
&lt;p&gt;The hook works like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;letsencrypt.sh hands off the domain name and a challenge string to the hook&lt;/li&gt;
&lt;li&gt;The hook adds a DNS record to Rackspace&amp;rsquo;s DNS servers via the API&lt;/li&gt;
&lt;li&gt;The hook keeps checking to see if the DNS record is publicly accessible&lt;/li&gt;
&lt;li&gt;Once the DNS record appears, control is handed back to letsencrypt.sh&lt;/li&gt;
&lt;li&gt;letsencrypt.sh tells Let&amp;rsquo;s Encrypt to verify the challenge&lt;/li&gt;
&lt;li&gt;Let&amp;rsquo;s Encrypt verifies the challenge&lt;/li&gt;
&lt;li&gt;The hook cleans up the DNS record and displays the paths to the new certificates and keys.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From there, you can configure your configuration management software to push out the new certificate and keys to your production servers. Let&amp;rsquo;s Encrypt certificates are currently limited to a 90-day duration, so be sure to configure this automation via a cron job. At the very least, set a calendar reminder for yourself a week or two in advance of the expiration.&lt;/p&gt;
&lt;p&gt;Keep in mind that Let&amp;rsquo;s Encrypt and Rackspace&amp;rsquo;s DNS service are completely free. Free is a good thing.&lt;/p&gt;
&lt;p&gt;Let me know what you think of the script! Feel free to make pull requests or issues if you find bugs. I am still working on some automated testing for the script and I hope to have that available in the next week or two.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credit: &lt;a href="https://www.flickr.com/photos/137399762@N06/25476786513/"&gt;Aphernai&lt;/a&gt; via &lt;a href="http://compfight.com"&gt;Compfight&lt;/a&gt; &lt;a href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;cc&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Segmentation faults with sphinx and pyenv</title><link>https://major.io/p/segmentation-faults-with-sphinx-and-pyenv/</link><pubDate>Tue, 09 Feb 2016 14:09:44 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/segmentation-faults-with-sphinx-and-pyenv/</guid><description>&lt;p&gt;I&amp;rsquo;m a big fan of the &lt;a href="https://github.com/yyuu/pyenv"&gt;pyenv&lt;/a&gt; project because it makes installing multiple python versions a simple process. However, I kept stumbling into a segmentation fault whenever I tried to build documentation with sphinx in Python 2.7.11:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;writing output... [100%] unreleased
[app] emitting event: &amp;#39;doctree-resolved&amp;#39;(&amp;lt;document: &amp;lt;section &amp;#34;current series release notes&amp;#34;...&amp;gt;&amp;gt;, u&amp;#39;unreleased&amp;#39;)
[app] emitting event: &amp;#39;html-page-context&amp;#39;(u&amp;#39;unreleased&amp;#39;, &amp;#39;page.html&amp;#39;, {&amp;#39;file_suffix&amp;#39;: &amp;#39;.html&amp;#39;, &amp;#39;has_source&amp;#39;: True, &amp;#39;show_sphinx&amp;#39;: True, &amp;#39;last

generating indices... genindex[app] emitting event: &amp;#39;html-page-context&amp;#39;(&amp;#39;genindex&amp;#39;, &amp;#39;genindex.html&amp;#39;, {&amp;#39;pathto&amp;#39;: &amp;lt;function pathto at 0x7f4279d51230&amp;gt;, &amp;#39;file_suffix&amp;#39;: &amp;#39;.html&amp;#39;
Segmentation fault (core dumped)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I tried a few different versions of sphinx, but the segmentation fault persisted. I did a quick reinstallation of Python 2.7.11 in the hopes that a system update of gcc/glibc was causing the problem:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pyenv install 2.7.11
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same segmentation fault showed up again. After a ton of Google searching, I found that the &lt;code&gt;--enable-shared&lt;/code&gt; option allows pyenv to use shared Python libraries at compile time:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;env PYTHON_CONFIGURE_OPTS=&amp;#34;--enable-shared CC=clang&amp;#34; pyenv install -vk 2.7.11
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That worked! I&amp;rsquo;m now able to run sphinx without segmentation faults.&lt;/p&gt;</description></item><item><title>Enabling kwallet after accidentally disabling it</title><link>https://major.io/p/enabling-kwallet-after-accidentally-disabling-it/</link><pubDate>Thu, 28 Jan 2016 16:27:44 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/enabling-kwallet-after-accidentally-disabling-it/</guid><description>&lt;p&gt;Although I use GNOME 3 as my desktop environment, I prefer KDE&amp;rsquo;s &lt;a href="https://www.kde.org/applications/system/kwalletmanager/"&gt;kwallet&lt;/a&gt; service to gnome-keyring for some functions. The user interface is a little easier to use and it&amp;rsquo;s easier to link up to the &lt;a href="https://pypi.python.org/pypi/keyring"&gt;keyring module&lt;/a&gt; in Python.&lt;/p&gt;
&lt;h2 id="accidentally-disabling-kwallet"&gt;Accidentally disabling kwallet&lt;/h2&gt;
&lt;p&gt;A few errant mouse clicks caused me to accidentally disable the kwalletd service earlier today and I was struggling to get it running again. The daemon is usually started by dbus and I wasn&amp;rsquo;t entirely sure how to start it properly.&lt;/p&gt;
&lt;p&gt;If I start kwalletmanager, I see the kwallet icon in the top bar. However, it&amp;rsquo;s unresponsive to clicks. Starting kwalletmanager on the command line leads to lots of errors in the console:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kwalletmanager(20406)/kdeui (Wallet): The kwalletd service has been disabled
kwalletmanager(20406)/kdeui (Wallet): The kwalletd service has been disabled
kwalletmanager(20406)/kdeui (Wallet): The kwalletd service has been disabled
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Manually running kwalletd in the console wasn&amp;rsquo;t successful either.&lt;/p&gt;
&lt;h2 id="using-kcmshell"&gt;Using kcmshell&lt;/h2&gt;
&lt;p&gt;KDE provides a utility called kcmshell that allows you to start a configuration panel without running the entire KDE environment. If you disable kwallet accidentally like I did, this will bring up the configuration panel and allow you to re-enable it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kcmshell4 kwalletconfig
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You should see kwallet&amp;rsquo;s configuration panel appear:&lt;/p&gt;
&lt;p&gt;&lt;img alt="kwallet-control-module-e1453998029696.png" loading="lazy" src="https://major.io/p/enabling-kwallet-after-accidentally-disabling-it/kwallet-control-module-e1453998029696.png" title="KDE wallet control module for kwallet"&gt;&lt;/p&gt;
&lt;p&gt;Click on &lt;strong&gt;Enable the KDE wallet subsystem&lt;/strong&gt; and then click OK. Once the window closes, start kwalletmanager and you should be able to access your secrets in kwallet again.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credit: &lt;a href="https://www.flickr.com/photos/73589829@N00/14283880173/"&gt;Wei&lt;/a&gt; via &lt;a href="http://compfight.com"&gt;Compfight&lt;/a&gt; &lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/"&gt;cc&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Nobody is using your software project. Now what?</title><link>https://major.io/p/nobody-using-software-project-now/</link><pubDate>Fri, 15 Jan 2016 17:35:48 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/nobody-using-software-project-now/</guid><description>&lt;p&gt;Working with open source software is an amazing experience. The collaborative process around creation, refinement, and even maintenance, drives more developers to work on open source software more often. However, every developer finds themselves writing code that very few people &lt;em&gt;actually use&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For some developers, this can be really bothersome. You offer your code up to the world only to find that the world is much less interested than you expected. We see projects that fit the &lt;a href="https://en.wikipedia.org/wiki/Field_of_Dreams"&gt;&amp;ldquo;build it, and they will come&amp;rdquo;&lt;/a&gt; methodology all the time, but it can hurt when our projects don&amp;rsquo;t have the same impact.&lt;/p&gt;
&lt;p&gt;Start by asking yourself a question:&lt;/p&gt;
&lt;h2 id="does-it-matter"&gt;Does it matter?&lt;/h2&gt;
&lt;p&gt;Many of us write software that has a very limited audience. Perhaps we wrote something that worked around a temporary problem or solved an issue that very few poeple would see. Sometimes we write software to work with a project that doesn&amp;rsquo;t have a large user base.&lt;/p&gt;
&lt;p&gt;In these situations, it often doesn&amp;rsquo;t matter if other contributors don&amp;rsquo;t show up to collaborate.&lt;/p&gt;
&lt;p&gt;However, if you&amp;rsquo;re eager to build a community around an open source project, here are some tips that have worked well for me.&lt;/p&gt;
&lt;h2 id="make-it-approachable"&gt;Make it approachable&lt;/h2&gt;
&lt;p&gt;Sites like &lt;a href="http://stackoverflow.com/"&gt;StackOverflow&lt;/a&gt; became immensely popular over time because they provide simple, approachable solutions that normally come with a small amount of explanation. Not all of the code snippets are examples of high quality software development, but that&amp;rsquo;s not the point here. People can search, review something, and get on their way.&lt;/p&gt;
&lt;p&gt;Making software more approachable is completely based on your audience. Complicated software, like the &lt;a href="https://cryptography.io/en/latest/"&gt;cryptography&lt;/a&gt; Python library, has an approach towards experienced software developers who want a robust method for handling cryptographic operations. Compare that to the &lt;a href="http://docs.python-requests.org/en/latest/"&gt;requests&lt;/a&gt; Python library. The developers on that project have an audience of Python developers of all skill levels and they lead off with a simple example and very approachable documentation.&lt;/p&gt;
&lt;p&gt;Both of those approaches are very different but extremely effective to their respective audiences.&lt;/p&gt;
&lt;p&gt;Once you know your audience, make these changes to make your software more approachable to them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Describe your project&amp;rsquo;s sweet spot.&lt;/strong&gt; what does it do better than every other project?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What does your project not do well?&lt;/strong&gt; This could clue developers into better projects for their needs or entice them to submit patches for improvements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How do developers get started?&lt;/strong&gt; This should include simple ways to install the software, test it after installation, and examples of ways to quickly begin using it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How do you want to receive improvements?&lt;/strong&gt; If someone finds a bug or area for improvement, how should they submit it and what should their expectations be?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you haven&amp;rsquo;t figured it out already, &lt;strong&gt;documentation is required&lt;/strong&gt;. Projects without documentation are quickly skipped over by most developers for a good reason: if you haven&amp;rsquo;t taken the time to help people understand how to use your project, why should they take the time to understand it themselves. Projects without documentation are often assumed to be less mature and not production-ready.&lt;/p&gt;
&lt;p&gt;Once you make it this far, it&amp;rsquo;s time to &lt;a href="http://www.psychotactics.com/the-main-difference-between-extroverts-and-introverts/"&gt;charge your extrovert battery&lt;/a&gt; and promote it.&lt;/p&gt;
&lt;h2 id="promoting-the-project"&gt;Promoting the project&lt;/h2&gt;
&lt;p&gt;Some of the best-written software projects with the best documentation often find themselves limited by the fact that nobody knows they exist. This can become a challenging problem to solve because it involves actively reaching out to the audience you&amp;rsquo;ve identified in the previous steps.&lt;/p&gt;
&lt;p&gt;The first step is to do some writing about your software project and what problems it tries to solve. The type of writing and the medium for sharing it is completely up to you.&lt;/p&gt;
&lt;p&gt;Some people prefer &lt;a href="http://blog.rackspace.com/why-technical-people-should-blog-but-dont/"&gt;writing blog posts&lt;/a&gt; on their own blog. You may be able to get additional readers by publishing it on external sites, such as &lt;a href="http://medium.com"&gt;Medium&lt;/a&gt;, or as a guest author on another site. For example, &lt;a href="http://opensource.com"&gt;opensource.com&lt;/a&gt; invites guest authors to write about various software projects or solutions provided by open source software. If your project is closely affiliated with another large software project, you may be able to publish a post as a guest author on their project site.&lt;/p&gt;
&lt;p&gt;Social media can be helpful if it&amp;rsquo;s used wisely with the right audience. Your followers must be able to get some value from whatever you link them to in your social media posts. Steer clear of clickbait-type posts and be genuine. If you want to build a community, your integrity is your most important asset.&lt;/p&gt;
&lt;h2 id="technical-talks"&gt;Technical talks&lt;/h2&gt;
&lt;p&gt;The most effective method for sharing a project is to do it in person. Yes, this means giving a technical talk to an audience. That means standing in front of people. It&amp;rsquo;s the kind of thing that make an introvert pause. However, if you care about your project, you can &lt;a href="http://www.slideshare.net/MajorHayden/taming-the-technical-talk"&gt;tame that technical talk&lt;/a&gt; and make a great connection with your audience.&lt;/p&gt;
&lt;p&gt;The return on investment in technical talks often takes the form of a &lt;a href="https://en.wikipedia.org/wiki/Dynamics_(music)#Gradual_changes"&gt;decrescendo&lt;/a&gt;. Feedback flows in quickly as soon as the talk is over and gradually decreases over time. The rate of decrease largely depends on the impact you make on your audience. A high-impact, emotionally appealing presentation will yield a long tail of feedback that decreases very slowly. Your project might appear in presentations made by other people and you&amp;rsquo;ll often get additional feedback and involvement from those talks as well.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credit: &lt;a href="https://www.flickr.com/photos/56944665@N00/8627526293/"&gt;Wanaku&lt;/a&gt; via &lt;a href="http://compfight.com"&gt;Compfight&lt;/a&gt; &lt;a href="https://creativecommons.org/licenses/by-nc-nd/2.0/"&gt;cc&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Automatic package updates with dnf</title><link>https://major.io/p/automatic-package-updates-with-dnf/</link><pubDate>Tue, 12 May 2015 01:22:10 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/automatic-package-updates-with-dnf/</guid><description>&lt;p&gt;&lt;img alt="1" loading="lazy" src="https://major.io/wp-content/uploads/2015/05/12428002945_bc47ae3529_b-e1431393503428.jpg"&gt;&lt;/p&gt;
&lt;p&gt;With Fedora 22&amp;rsquo;s release date &lt;a href="https://fedoraproject.org/wiki/Releases/22/Schedule"&gt;quickly approaching&lt;/a&gt;, it&amp;rsquo;s time to familiarize yourself with dnf. It&amp;rsquo;s especially important since clean installs of Fedora 22 won&amp;rsquo;t have yum.&lt;/p&gt;
&lt;p&gt;Almost all of the command line arguments are the same but automated updates are a little different. If you&amp;rsquo;re used to yum-updatesd, then you&amp;rsquo;ll want to look into &lt;a href="http://dnf.readthedocs.org/en/latest/automatic.html"&gt;dnf-automatic&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Getting the python code and systemd unit files for automated dnf updates is a quick process:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dnf -y install dnf-automatic
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s only one configuration file to review and most of the defaults are quite sensible. Open up &lt;code&gt;/etc/dnf/automatic.conf&lt;/code&gt; with your favorite text editor and review the available options. The only adjustment I made was to change the &lt;strong&gt;emit_via&lt;/strong&gt; option to &lt;em&gt;email&lt;/em&gt; as opposed to the &lt;em&gt;stdio&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You may want to change the &lt;strong&gt;email_to&lt;/strong&gt; option if you want to redirect email elsewhere. In my case, I already have an email forward for the root user.&lt;/p&gt;
&lt;h2 id="dnf-automation"&gt;dnf Automation&lt;/h2&gt;
&lt;p&gt;If you look at the contents of the dnf-automatic package, you&amp;rsquo;ll find some python code, configuration files, and two important systemd files:&lt;/p&gt;
&lt;p&gt;For Fedora 25 and earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# rpm -ql dnf-automatic | grep systemd
/usr/lib/systemd/system/dnf-automatic.service
/usr/lib/systemd/system/dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For Fedora 26 and later:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# rpm -ql dnf-automatic | grep systemd
/usr/lib/systemd/system/dnf-automatic-download.service
/usr/lib/systemd/system/dnf-automatic-download.timer
/usr/lib/systemd/system/dnf-automatic-install.service
/usr/lib/systemd/system/dnf-automatic-install.timer
/usr/lib/systemd/system/dnf-automatic-notifyonly.service
/usr/lib/systemd/system/dnf-automatic-notifyonly.timer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These systemd files are what makes dnf-automatic run. The service file contains the instructions so that systemd knows what to run. The timer file contains the frequency of the update checks (defaults to one day). We need to enable the timer and then start it.&lt;/p&gt;
&lt;p&gt;For Fedora 25 and earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl enable dnf-automatic.timer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For Fedora 26 and later:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl enable dnf-automatic-install.timer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check your work:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# systemctl list-timers *dnf*
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2015-05-12 19:57:30 CDT 23h left Mon 2015-05-11 19:57:29 CDT 14min ago dnf-automatic.timer dnf-automatic.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The output here shows that the dnf-automatic job last ran at 19:57 on May 11th and it&amp;rsquo;s set to run at the same time tomorrow, May 12th. Be sure to disable and stop your yum-updatesd service if you still have it running on your system from a previous version of Fedora.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo Credit: &lt;a href="https://www.flickr.com/photos/50899563@N07/12428002945/"&gt;Outer Rim Emperor&lt;/a&gt; via &lt;a href="http://compfight.com"&gt;Compfight&lt;/a&gt; &lt;a href="https://www.flickr.com/help/general/#147"&gt;cc&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item><item><title>virt-manager: ‘NoneType’ object has no attribute ‘cpus’</title><link>https://major.io/p/virt-manager-nonetype-object-has-no-attribute-cpus/</link><pubDate>Thu, 06 Mar 2014 18:44:58 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/virt-manager-nonetype-object-has-no-attribute-cpus/</guid><description>&lt;p&gt;After upgrading my Fedora 20 Xen hypervisor to virt-manager 1.0.0, I noticed that I couldn&amp;rsquo;t open the console or VM details for any of my guests. Running &lt;code&gt;virt-manager --debug&lt;/code&gt; gave me the following traceback:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Traceback (most recent call last):
 File &amp;#34;/usr/share/virt-manager/virtManager/engine.py&amp;#34;, line 803, in _show_vm_helper
 details = self._get_details_dialog(uri, uuid)
 File &amp;#34;/usr/share/virt-manager/virtManager/engine.py&amp;#34;, line 760, in _get_details_dialog
 obj = vmmDetails(con.get_vm(uuid))
 File &amp;#34;/usr/share/virt-manager/virtManager/details.py&amp;#34;, line 530, in __init__
 self.init_details()
 File &amp;#34;/usr/share/virt-manager/virtManager/details.py&amp;#34;, line 990, in init_details
 for name in [c.model for c in cpu_values.cpus]:
AttributeError: &amp;#39;NoneType&amp;#39; object has no attribute &amp;#39;cpus&amp;#39;
[Tue, 04 Mar 2014 22:13:31 virt-manager 21019] DEBUG (error:84) error dialog message:
summary=Error launching details: &amp;#39;NoneType&amp;#39; object has no attribute &amp;#39;cpus&amp;#39;
details=Error launching details: &amp;#39;NoneType&amp;#39; object has no attribute &amp;#39;cpus&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1072704"&gt;opened a bug report&lt;/a&gt; and the fix was &lt;a href="https://git.fedorahosted.org/cgit/virt-manager.git/commit/?id=b078ba8c3d69b62fe748d9182babef8971914277"&gt;committed upstream&lt;/a&gt; today. If you want to make these updates to your Fedora 20 server before the update package is available, just snag the &lt;a href="http://koji.fedoraproject.org/koji/buildinfo?buildID=502966"&gt;three RPM&amp;rsquo;s from koji&lt;/a&gt; and install them:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir /tmp/virt-manager
cd /tmp/virt-manager
wget http://kojipkgs.fedoraproject.org/packages/virt-manager/1.0.0/4.fc20/noarch/virt-install-1.0.0-4.fc20.noarch.rpm
wget http://kojipkgs.fedoraproject.org/packages/virt-manager/1.0.0/4.fc20/noarch/virt-manager-1.0.0-4.fc20.noarch.rpm
wget http://kojipkgs.fedoraproject.org/packages/virt-manager/1.0.0/4.fc20/noarch/virt-manager-common-1.0.0-4.fc20.noarch.rpm
yum localinstall *.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; Thanks to Cole&amp;rsquo;s comment below, you can actually pull in the RPM&amp;rsquo;s using koji directly:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;koji download-build virt-manager-1.0.0-4.fc20
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Relocating a python virtual environment</title><link>https://major.io/p/relocating-a-python-virtual-environment/</link><pubDate>Sun, 25 Nov 2012 21:27:47 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/relocating-a-python-virtual-environment/</guid><description>&lt;p&gt;Python&amp;rsquo;s &lt;a href="http://pypi.python.org/pypi/virtualenv"&gt;virtual environment capability&lt;/a&gt; is extremely handy for situations where you don&amp;rsquo;t want the required modules for a particular python project to get mixed up with your system-wide installed modules. If you work on large python projects (like &lt;a href="http://openstack.org/"&gt;OpenStack&lt;/a&gt;), you&amp;rsquo;ll find that the applications may require certain versions of python modules to operate properly. If these versions differ from the system-wide python modules you already have installed, you might get unexpected results when you try to run the unit tests.&lt;/p&gt;
&lt;p&gt;If you build a virtual environment and inspect the files found within the &lt;em&gt;bin&lt;/em&gt; directory of the virtual environment, you&amp;rsquo;ll find that the first line in the executable scripts is set to use the python version specific to that virtual environment. Here&amp;rsquo;s an example from a virtual environment containing the OpenStack glance project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/home/major/glance/.venv/bin/python&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# EASY-INSTALL-SCRIPT: &amp;#39;glance==2013.1&amp;#39;,&amp;#39;glance-api&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;__requires__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;glance==2013.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pkg_resources&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pkg_resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;glance==2013.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;glance-api&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, what if I wanted to take this virtual environment and place it somewhere else on the server where multiple people could use it? The path in the first line of the scripts in &lt;em&gt;bin&lt;/em&gt; will surely break.&lt;/p&gt;
&lt;p&gt;The first option is to make the virtual environment relocatable. This can produce unexpected results for some software projects, so be sure to test it out before trying to use it in a production environment.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ virtualenv --relocatable .venv
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick check of the same python file now shows this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python2.6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;activate_this&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;activate_this.py&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;execfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activate_this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;activate_this&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activate_this&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# EASY-INSTALL-SCRIPT: &amp;#39;glance==2013.1&amp;#39;,&amp;#39;glance-api&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows for the path to the activate_this.py script to be determined at runtime and allows you to move your virtual environment wherever you like.&lt;/p&gt;
&lt;p&gt;In situations where one script within &lt;em&gt;bin&lt;/em&gt; would import another script within &lt;em&gt;bin&lt;/em&gt;, things can get a little dicey. These are edge cases, of course, but you can get a similar effect by adjusting the path in the first line of each file within &lt;em&gt;bin&lt;/em&gt; to the new location of the virtual environment. If you move the virtual environment again, be sure to alter the paths again with &lt;code&gt;sed&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Keep tabs on OpenStack development with OpenStack Watch on Twitter</title><link>https://major.io/p/keep-tabs-on-openstack-development-with-openstack-watch-on-twitter/</link><pubDate>Fri, 08 Jun 2012 12:19:26 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/keep-tabs-on-openstack-development-with-openstack-watch-on-twitter/</guid><description>&lt;p&gt;It&amp;rsquo;s no secret that I&amp;rsquo;m a fan of &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; and &lt;a href="http://openstack.org/"&gt;OpenStack&lt;/a&gt;. I found myself needing a better way to follow the rapid pace of OpenStack development and I figured that a Twitter bot would be a pretty good method for staying up to date.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to invite you to check out &lt;a href="http://twitter.com/openstackwatch"&gt;@openstackwatch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First things first, it&amp;rsquo;s a completely unofficial project that I worked on during my spare time and it&amp;rsquo;s not affiliated with OpenStack in any way. If it breaks, it&amp;rsquo;s most likely my fault.&lt;/p&gt;
&lt;p&gt;The bot watches for ticket status changes in &lt;a href="http://review.openstack.org/"&gt;OpenStack&amp;rsquo;s Gerrit server&lt;/a&gt; and makes a tweet about the change within a few minutes. Every tweet contains the commit&amp;rsquo;s project, owner, status, and a brief summary of the change. In addition, you&amp;rsquo;ll get a link directly to the review page on the Gerrit server. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div id="attachment_3452" style="width: 457px" class="wp-caption aligncenter"&gt;
 &lt;a href="https://major.io/wp-content/uploads/2012/06/openstackwatchtweet.jpg"&gt;&lt;img src="https://major.io/wp-content/uploads/2012/06/openstackwatchtweet.jpg" alt="" title="openstackwatchtweet" width="447" height="154" class="size-full wp-image-3452" srcset="https://major.io/wp-content/uploads/2012/06/openstackwatchtweet.jpg 447w, https://major.io/wp-content/uploads/2012/06/openstackwatchtweet-300x103.jpg 300w" sizes="(max-width: 447px) 100vw, 447px" /&gt;&lt;/a&gt;
 &lt;p class="wp-caption-text"&gt;
 Hey! It's Dan!
 &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If you&amp;rsquo;re not a fan of Twitter, there&amp;rsquo;s a link to the RSS feed in the bio section, or you can just add this URL to your RSS feed reader:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://api.twitter.com/1/statuses/user_timeline.rss?screen_name=openstackwatch"&gt;http://api.twitter.com/1/statuses/user_timeline.rss?screen_name=openstackwatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you can come up with any ideas for improvements, please &lt;a href="http://twitter.com/rackerhacker"&gt;let me know&lt;/a&gt;!&lt;/p&gt;</description></item><item><title>mysql-json-bridge: a simple JSON API for MySQL</title><link>https://major.io/p/mysql-json-bridge-a-simple-json-api-for-mysql/</link><pubDate>Thu, 29 Mar 2012 02:34:53 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mysql-json-bridge-a-simple-json-api-for-mysql/</guid><description>&lt;p&gt;My quest to get better at &lt;a href="http://python.org"&gt;Python&lt;/a&gt; led me to create a new project on GitHub. It&amp;rsquo;s called &lt;a href="https://github.com/rackerhacker/mysql-json-bridge"&gt;mysql-json-bridge&lt;/a&gt; and it&amp;rsquo;s ready for you to use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do we need a JSON API for MySQL?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The real need sprang from a situation I was facing daily at &lt;a href="http://rackspace.com/"&gt;Rackspace&lt;/a&gt;. We have a lot of production and pre-production environments which are in flux but we need a way to query data from various MySQL servers for multiple purposes. Some folks need data in ruby or python scripts while others need to drag in data with .NET and Java. Wrestling with the various adapters and all of the user privileges on disparate database servers behind different firewalls on different networks was less than enjoyable.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where this bridge comes in.&lt;/p&gt;
&lt;p&gt;The bridge essentially gives anyone the ability to talk to multiple database servers across different environments by talking to a single endpoint with easily configurable security and encryption. As long as the remote user can make an HTTP POST and parse some JSON, they can query data from multiple MySQL endpoints.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It all starts with a simple HTTP POST. I&amp;rsquo;ve become a big fan of the Python &lt;a href="http://python-requests.org"&gt;requests&lt;/a&gt; module. If you&amp;rsquo;re using it, this is all you need to submit a query:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import requests
payload = {&amp;#39;sql&amp;#39;: &amp;#39;SELECT * FROM some_tables WHERE some_column=some_value&amp;#39;}
url = &amp;#34;http://localhost:5000/my_environment/my_database&amp;#34;
r = requests.post(url, data=payload)
print r.text
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The bridge takes your query and feeds it into the corresponding MySQL server. When the results come back, they&amp;rsquo;re converted to JSON and returned via the same HTTP connection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What technology does it use?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt; does the heavy lifting for the HTTP requests and &lt;a href="https://github.com/facebook/tornado/blob/master/tornado/database.py"&gt;Facebook&amp;rsquo;s Tornado database class&lt;/a&gt; wraps the &lt;a href="http://mysql-python.sourceforge.net/"&gt;MySQLdb&lt;/a&gt; module in something a little more user friendly. Other than those modules, &lt;a href="http://pyyaml.org/"&gt;PyYAML&lt;/a&gt; and &lt;a href="http://python-requests.org"&gt;requests&lt;/a&gt; are the only other modules not provided by the standard Python libraries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it fast?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. I haven&amp;rsquo;t done any detailed benchmarks on it yet, but the overhead is quite low even with a lot of concurrency. The biggest slowdowns come from network latency between you and the bridge or between the bridge and the database server. Keep in mind that gigantic result sets will take a longer time to transfer across the network and get transformed into JSON.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I found a bug. I have an idea for an improvement. You&amp;rsquo;re terrible at Python.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All feedback (and every pull request) is welcome. I&amp;rsquo;m still getting the hang of Python (hey, I&amp;rsquo;ve only been writing in it seriously for a few weeks!) and I&amp;rsquo;m always eager to learn a new or better way to accomplish something. Feel free to create an issue in GitHub or submit a pull request with a patch.&lt;/p&gt;</description></item></channel></rss>